Type erased materials (#19667)

# Objective

Closes #18075

In order to enable a number of patterns for dynamic materials in the
engine, it's necessary to decouple the renderer from the `Material`
trait.

This opens the possibility for:
- Materials that aren't coupled to `AsBindGroup`.
- 2d using the underlying 3d bindless infrastructure.
- Dynamic materials that can change their layout at runtime.
- Materials that aren't even backed by a Rust struct at all.

## Solution

In short, remove all trait bounds from render world material systems and
resources. This means moving a bunch of stuff onto `MaterialProperties`
and engaging in some hacks to make specialization work. Rather than
storing the bind group data in `MaterialBindGroupAllocator`, right now
we're storing it in a closure on `MaterialProperties`. TBD if this has
bad performance characteristics.

## Benchmarks

- `many_cubes`:
`cargo run --example many_cubes --release --features=bevy/trace_tracy --
--vary-material-data-per-instance`:
![Screenshot 2025-06-26
235426](https://github.com/user-attachments/assets/10a0ee29-9932-4f91-ab43-33518b117ac5)

- @DGriffin91's Caldera
`cargo run --release --features=bevy/trace_tracy -- --random-materials`

![image](https://github.com/user-attachments/assets/ef91ba6a-8e88-4922-a73f-acb0af5b0dbc)


- @DGriffin91's Caldera with 20 unique material types (i.e.
`MaterialPlugin<M>`) and random materials per mesh
`cargo run --release --features=bevy/trace_tracy -- --random-materials`
![Screenshot 2025-06-27
000425](https://github.com/user-attachments/assets/9561388b-881d-46cf-8c3d-b15b3e9aedc7)


### TODO

- We almost certainly lost some parallelization from removing the type
params that could be gained back from smarter iteration.
- Test all the things that could have broken.
- ~Fix meshlets~

## Showcase

See [the
example](https://github.com/bevyengine/bevy/pull/19667/files#diff-9d768cfe1c3aa81eff365d250d3cbe5a63e8df63e81dd85f64c3c3cd993f6d94)
for a custom material implemented without the use of the `Material`
trait and thus `AsBindGroup`.


![image](https://github.com/user-attachments/assets/e3fcca7c-e04e-4a4e-9d89-39d697a9e3b8)

---------

Co-authored-by: IceSentry <IceSentry@users.noreply.github.com>
Co-authored-by: IceSentry <c.giguere42@gmail.com>
This commit is contained in:
charlotte 🌸 2025-06-27 15:57:24 -07:00 committed by GitHub
parent 73a313200d
commit e6ba9a6d18
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
33 changed files with 1672 additions and 945 deletions

View File

@ -579,7 +579,7 @@ ron = "0.10"
flate2 = "1.0"
serde = { version = "1", features = ["derive"] }
serde_json = "1.0.140"
bytemuck = "1.7"
bytemuck = "1"
bevy_render = { path = "crates/bevy_render", version = "0.17.0-dev", default-features = false }
# The following explicit dependencies are needed for proc macros to work inside of examples as they are part of the bevy crate itself.
bevy_ecs = { path = "crates/bevy_ecs", version = "0.17.0-dev", default-features = false }
@ -1051,6 +1051,17 @@ description = "Showcases different blend modes"
category = "3D Rendering"
wasm = true
[[example]]
name = "manual_material"
path = "examples/3d/manual_material.rs"
doc-scrape-examples = true
[package.metadata.example.manual_material]
name = "Manual Material Implementation"
description = "Demonstrates how to implement a material manually using the mid-level render APIs"
category = "3D Rendering"
wasm = true
[[example]]
name = "edit_material_on_gltf"
path = "examples/3d/edit_material_on_gltf.rs"

View File

@ -0,0 +1,11 @@
#import bevy_pbr::forward_io::VertexOutput
@group(3) @binding(0) var material_color_texture: texture_2d<f32>;
@group(3) @binding(1) var material_color_sampler: sampler;
@fragment
fn fragment(
mesh: VertexOutput,
) -> @location(0) vec4<f32> {
return textureSample(material_color_texture, material_color_sampler, mesh.uv);
}

View File

@ -50,9 +50,7 @@ impl Plugin for LineGizmo3dPlugin {
.init_resource::<SpecializedRenderPipelines<LineJointGizmoPipeline>>()
.configure_sets(
Render,
GizmoRenderSystems::QueueLineGizmos3d
.in_set(RenderSystems::Queue)
.ambiguous_with(bevy_pbr::queue_material_meshes::<bevy_pbr::StandardMaterial>),
GizmoRenderSystems::QueueLineGizmos3d.in_set(RenderSystems::Queue),
)
.add_systems(
Render,

View File

@ -51,7 +51,7 @@ bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-fea
] }
# other
bitflags = "2.3"
bitflags = { version = "2.3", features = ["bytemuck"] }
fixedbitset = "0.5"
thiserror = { version = "2", default-features = false }
derive_more = { version = "2", default-features = false, features = ["from"] }

View File

@ -1,6 +1,6 @@
use alloc::borrow::Cow;
use bevy_asset::{Asset, Handle};
use bevy_asset::Asset;
use bevy_ecs::system::SystemParamItem;
use bevy_platform::{collections::HashSet, hash::FixedHasher};
use bevy_reflect::{impl_type_path, Reflect};
@ -9,8 +9,8 @@ use bevy_render::{
mesh::MeshVertexBufferLayoutRef,
render_resource::{
AsBindGroup, AsBindGroupError, BindGroupLayout, BindGroupLayoutEntry, BindlessDescriptor,
BindlessResourceType, BindlessSlabResourceLimit, RenderPipelineDescriptor, Shader,
ShaderRef, SpecializedMeshPipelineError, UnpreparedBindGroup,
BindlessResourceType, BindlessSlabResourceLimit, RenderPipelineDescriptor, ShaderRef,
SpecializedMeshPipelineError, UnpreparedBindGroup,
},
renderer::RenderDevice,
};
@ -19,10 +19,6 @@ use crate::{Material, MaterialPipeline, MaterialPipelineKey, MeshPipeline, MeshP
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> {
@ -150,12 +146,19 @@ where
}
}
#[derive(bytemuck::Pod, bytemuck::Zeroable, Copy, Clone, PartialEq, Eq, Hash)]
#[repr(C, packed)]
pub struct MaterialExtensionBindGroupData<B, E> {
pub base: B,
pub extension: E,
}
// 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 Data = MaterialExtensionBindGroupData<B::Data, E::Data>;
type Param = (<B as AsBindGroup>::Param, <E as AsBindGroup>::Param);
fn bindless_slot_count() -> Option<BindlessSlabResourceLimit> {
@ -179,20 +182,24 @@ impl<B: Material, E: MaterialExtension> AsBindGroup for ExtendedMaterial<B, E> {
}
}
fn bind_group_data(&self) -> Self::Data {
MaterialExtensionBindGroupData {
base: self.base.bind_group_data(),
extension: self.extension.bind_group_data(),
}
}
fn unprepared_bind_group(
&self,
layout: &BindGroupLayout,
render_device: &RenderDevice,
(base_param, extended_param): &mut SystemParamItem<'_, '_, Self::Param>,
mut force_non_bindless: bool,
) -> Result<UnpreparedBindGroup<Self::Data>, AsBindGroupError> {
) -> Result<UnpreparedBindGroup, AsBindGroupError> {
force_non_bindless = force_non_bindless || Self::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(
let UnpreparedBindGroup { mut bindings } = B::unprepared_bind_group(
&self.base,
layout,
render_device,
@ -209,10 +216,7 @@ impl<B: Material, E: MaterialExtension> AsBindGroup for ExtendedMaterial<B, E> {
bindings.extend(extended_bindgroup.bindings.0);
Ok(UnpreparedBindGroup {
bindings,
data: (base_data, extended_bindgroup.data),
})
Ok(UnpreparedBindGroup { bindings })
}
fn bind_group_layout_entries(
@ -373,57 +377,28 @@ impl<B: Material, E: MaterialExtension> Material for ExtendedMaterial<B, E> {
}
fn specialize(
pipeline: &MaterialPipeline<Self>,
pipeline: &MaterialPipeline,
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,
bind_group_data: key.bind_group_data.base,
};
B::specialize(&base_pipeline, descriptor, layout, base_key)?;
B::specialize(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,
mesh_pipeline: pipeline.mesh_pipeline.clone(),
},
descriptor,
layout,
MaterialExtensionKey {
mesh_key: key.mesh_key,
bind_group_data: key.bind_group_data.1,
bind_group_data: key.bind_group_data.extension,
},
)
}

View File

@ -239,6 +239,9 @@ impl Plugin for PbrPlugin {
use_gpu_instance_buffer_builder: self.use_gpu_instance_buffer_builder,
debug_flags: self.debug_flags,
},
MaterialsPlugin {
debug_flags: self.debug_flags,
},
MaterialPlugin::<StandardMaterial> {
prepass_enabled: self.prepass_enabled,
debug_flags: self.debug_flags,

File diff suppressed because it is too large Load Diff

View File

@ -4,8 +4,7 @@
//! allocator manages each bind group, assigning slots to materials as
//! appropriate.
use core::{cmp::Ordering, iter, marker::PhantomData, mem, ops::Range};
use crate::Material;
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{
resource::Resource,
@ -13,6 +12,7 @@ use bevy_ecs::{
};
use bevy_platform::collections::{HashMap, HashSet};
use bevy_reflect::{prelude::ReflectDefault, Reflect};
use bevy_render::render_resource::BindlessSlabResourceLimit;
use bevy_render::{
render_resource::{
BindGroup, BindGroupEntry, BindGroupLayout, BindingNumber, BindingResource,
@ -26,11 +26,14 @@ use bevy_render::{
settings::WgpuFeatures,
texture::FallbackImage,
};
use bevy_utils::default;
use bevy_utils::{default, TypeIdMap};
use bytemuck::Pod;
use core::hash::Hash;
use core::{cmp::Ordering, iter, mem, ops::Range};
use tracing::{error, trace};
use crate::Material;
#[derive(Resource, Deref, DerefMut, Default)]
pub struct MaterialBindGroupAllocators(TypeIdMap<MaterialBindGroupAllocator>);
/// A resource that places materials into bind groups and tracks their
/// resources.
@ -38,25 +41,20 @@ use crate::Material;
/// Internally, Bevy has separate allocators for bindless and non-bindless
/// materials. This resource provides a common interface to the specific
/// allocator in use.
#[derive(Resource)]
pub enum MaterialBindGroupAllocator<M>
where
M: Material,
{
pub enum MaterialBindGroupAllocator {
/// The allocator used when the material is bindless.
Bindless(Box<MaterialBindGroupBindlessAllocator<M>>),
Bindless(Box<MaterialBindGroupBindlessAllocator>),
/// The allocator used when the material is non-bindless.
NonBindless(Box<MaterialBindGroupNonBindlessAllocator<M>>),
NonBindless(Box<MaterialBindGroupNonBindlessAllocator>),
}
/// The allocator that places bindless materials into bind groups and tracks
/// their resources.
pub struct MaterialBindGroupBindlessAllocator<M>
where
M: Material,
{
pub struct MaterialBindGroupBindlessAllocator {
/// The label of the bind group allocator to use for allocated buffers.
label: Option<&'static str>,
/// The slabs, each of which contains a bind group.
slabs: Vec<MaterialBindlessSlab<M>>,
slabs: Vec<MaterialBindlessSlab>,
/// The layout of the bind groups that we produce.
bind_group_layout: BindGroupLayout,
/// Information about the bindless resources in the material.
@ -79,10 +77,7 @@ where
}
/// A single bind group and the bookkeeping necessary to allocate into it.
pub struct MaterialBindlessSlab<M>
where
M: Material,
{
pub struct MaterialBindlessSlab {
/// The current bind group, if it's up to date.
///
/// If this is `None`, then the bind group is dirty and needs to be
@ -98,7 +93,7 @@ where
///
/// Because the slab binary searches this table, the entries within must be
/// sorted by bindless index.
bindless_index_tables: Vec<MaterialBindlessIndexTable<M>>,
bindless_index_tables: Vec<MaterialBindlessIndexTable>,
/// The binding arrays containing samplers.
samplers: HashMap<BindlessResourceType, MaterialBindlessBindingArray<Sampler>>,
@ -110,12 +105,6 @@ where
/// `#[data]` attribute of `AsBindGroup`).
data_buffers: HashMap<BindlessIndex, MaterialDataBuffer>,
/// Holds extra CPU-accessible data that the material provides.
///
/// Typically, this data is used for constructing the material key, for
/// pipeline specialization purposes.
extra_data: Vec<Option<M::Data>>,
/// A list of free slot IDs.
free_slots: Vec<MaterialBindGroupSlot>,
/// The total number of materials currently allocated in this slab.
@ -130,10 +119,7 @@ where
/// This is conventionally assigned to bind group binding 0, but it can be
/// changed by altering the [`Self::binding_number`], which corresponds to the
/// `#[bindless(index_table(binding(B)))]` attribute in `AsBindGroup`.
struct MaterialBindlessIndexTable<M>
where
M: Material,
{
struct MaterialBindlessIndexTable {
/// The buffer containing the mappings.
buffer: RetainedRawBufferVec<u32>,
/// The range of bindless indices that this bindless index table covers.
@ -146,7 +132,6 @@ where
index_range: Range<BindlessIndex>,
/// The binding number that this index table is assigned to in the shader.
binding_number: BindingNumber,
phantom: PhantomData<M>,
}
/// A single binding array for storing bindless resources and the bookkeeping
@ -189,13 +174,12 @@ where
}
/// The allocator that stores bind groups for non-bindless materials.
pub struct MaterialBindGroupNonBindlessAllocator<M>
where
M: Material,
{
pub struct MaterialBindGroupNonBindlessAllocator {
/// The label of the bind group allocator to use for allocated buffers.
label: Option<&'static str>,
/// A mapping from [`MaterialBindGroupIndex`] to the bind group allocated in
/// each slot.
bind_groups: Vec<Option<MaterialNonBindlessAllocatedBindGroup<M>>>,
bind_groups: Vec<Option<MaterialNonBindlessAllocatedBindGroup>>,
/// The bind groups that are dirty and need to be prepared.
///
/// To prepare the bind groups, call
@ -203,15 +187,11 @@ where
to_prepare: HashSet<MaterialBindGroupIndex>,
/// A list of free bind group indices.
free_indices: Vec<MaterialBindGroupIndex>,
phantom: PhantomData<M>,
}
/// A single bind group that a [`MaterialBindGroupNonBindlessAllocator`] is
/// currently managing.
enum MaterialNonBindlessAllocatedBindGroup<M>
where
M: Material,
{
enum MaterialNonBindlessAllocatedBindGroup {
/// An unprepared bind group.
///
/// The allocator prepares all outstanding unprepared bind groups when
@ -219,13 +199,13 @@ where
/// called.
Unprepared {
/// The unprepared bind group, including extra data.
bind_group: UnpreparedBindGroup<M::Data>,
bind_group: UnpreparedBindGroup,
/// The layout of that bind group.
layout: BindGroupLayout,
},
/// A bind group that's already been prepared.
Prepared {
bind_group: PreparedBindGroup<M::Data>,
bind_group: PreparedBindGroup,
#[expect(dead_code, reason = "These buffers are only referenced by bind groups")]
uniform_buffers: Vec<Buffer>,
},
@ -351,35 +331,27 @@ trait GetBindingResourceId {
}
/// The public interface to a slab, which represents a single bind group.
pub struct MaterialSlab<'a, M>(MaterialSlabImpl<'a, M>)
where
M: Material;
pub struct MaterialSlab<'a>(MaterialSlabImpl<'a>);
/// The actual implementation of a material slab.
///
/// This has bindless and non-bindless variants.
enum MaterialSlabImpl<'a, M>
where
M: Material,
{
enum MaterialSlabImpl<'a> {
/// The implementation of the slab interface we use when the slab
/// is bindless.
Bindless(&'a MaterialBindlessSlab<M>),
Bindless(&'a MaterialBindlessSlab),
/// The implementation of the slab interface we use when the slab
/// is non-bindless.
NonBindless(MaterialNonBindlessSlab<'a, M>),
NonBindless(MaterialNonBindlessSlab<'a>),
}
/// A single bind group that the [`MaterialBindGroupNonBindlessAllocator`]
/// manages.
enum MaterialNonBindlessSlab<'a, M>
where
M: Material,
{
enum MaterialNonBindlessSlab<'a> {
/// A slab that has a bind group.
Prepared(&'a PreparedBindGroup<M::Data>),
Prepared(&'a PreparedBindGroup),
/// A slab that doesn't yet have a bind group.
Unprepared(&'a UnpreparedBindGroup<M::Data>),
Unprepared,
}
/// Manages an array of untyped plain old data on GPU and allocates individual
@ -480,26 +452,33 @@ impl GetBindingResourceId for TextureView {
}
}
impl<M> MaterialBindGroupAllocator<M>
where
M: Material,
{
impl MaterialBindGroupAllocator {
/// Creates a new [`MaterialBindGroupAllocator`] managing the data for a
/// single material.
fn new(render_device: &RenderDevice) -> MaterialBindGroupAllocator<M> {
if material_uses_bindless_resources::<M>(render_device) {
pub fn new(
render_device: &RenderDevice,
label: Option<&'static str>,
bindless_descriptor: Option<BindlessDescriptor>,
bind_group_layout: BindGroupLayout,
slab_capacity: Option<BindlessSlabResourceLimit>,
) -> MaterialBindGroupAllocator {
if let Some(bindless_descriptor) = bindless_descriptor {
MaterialBindGroupAllocator::Bindless(Box::new(MaterialBindGroupBindlessAllocator::new(
render_device,
label,
bindless_descriptor,
bind_group_layout,
slab_capacity,
)))
} else {
MaterialBindGroupAllocator::NonBindless(Box::new(
MaterialBindGroupNonBindlessAllocator::new(),
MaterialBindGroupNonBindlessAllocator::new(label),
))
}
}
/// Returns the slab with the given index, if one exists.
pub fn get(&self, group: MaterialBindGroupIndex) -> Option<MaterialSlab<M>> {
pub fn get(&self, group: MaterialBindGroupIndex) -> Option<MaterialSlab> {
match *self {
MaterialBindGroupAllocator::Bindless(ref bindless_allocator) => bindless_allocator
.get(group)
@ -520,7 +499,7 @@ where
/// you need to prepare the bind group yourself.
pub fn allocate_unprepared(
&mut self,
unprepared_bind_group: UnpreparedBindGroup<M::Data>,
unprepared_bind_group: UnpreparedBindGroup,
bind_group_layout: &BindGroupLayout,
) -> MaterialBindingId {
match *self {
@ -545,7 +524,7 @@ where
/// this method if you need to prepare the bind group yourself.
pub fn allocate_prepared(
&mut self,
prepared_bind_group: PreparedBindGroup<M::Data>,
prepared_bind_group: PreparedBindGroup,
) -> MaterialBindingId {
match *self {
MaterialBindGroupAllocator::Bindless(_) => {
@ -613,14 +592,11 @@ where
}
}
impl<M> MaterialBindlessIndexTable<M>
where
M: Material,
{
impl MaterialBindlessIndexTable {
/// Creates a new [`MaterialBindlessIndexTable`] for a single slab.
fn new(
bindless_index_table_descriptor: &BindlessIndexTableDescriptor,
) -> MaterialBindlessIndexTable<M> {
) -> MaterialBindlessIndexTable {
// Preallocate space for one bindings table, so that there will always be a buffer.
let mut buffer = RetainedRawBufferVec::new(BufferUsages::STORAGE);
for _ in *bindless_index_table_descriptor.indices.start
@ -633,7 +609,6 @@ where
buffer,
index_range: bindless_index_table_descriptor.indices.clone(),
binding_number: bindless_index_table_descriptor.binding_number,
phantom: PhantomData,
}
}
@ -747,15 +722,16 @@ where
}
}
impl<M> MaterialBindGroupBindlessAllocator<M>
where
M: Material,
{
impl MaterialBindGroupBindlessAllocator {
/// Creates a new [`MaterialBindGroupBindlessAllocator`] managing the data
/// for a single bindless material.
fn new(render_device: &RenderDevice) -> MaterialBindGroupBindlessAllocator<M> {
let bindless_descriptor = M::bindless_descriptor()
.expect("Non-bindless materials should use the non-bindless allocator");
fn new(
render_device: &RenderDevice,
label: Option<&'static str>,
bindless_descriptor: BindlessDescriptor,
bind_group_layout: BindGroupLayout,
slab_capacity: Option<BindlessSlabResourceLimit>,
) -> MaterialBindGroupBindlessAllocator {
let fallback_buffers = bindless_descriptor
.buffers
.iter()
@ -776,11 +752,12 @@ where
.collect();
MaterialBindGroupBindlessAllocator {
label,
slabs: vec![],
bind_group_layout: M::bind_group_layout(render_device),
bind_group_layout,
bindless_descriptor,
fallback_buffers,
slab_capacity: M::bindless_slot_count()
slab_capacity: slab_capacity
.expect("Non-bindless materials should use the non-bindless allocator")
.resolve(),
}
@ -796,7 +773,7 @@ where
/// created, and the material is allocated into it.
fn allocate_unprepared(
&mut self,
mut unprepared_bind_group: UnpreparedBindGroup<M::Data>,
mut unprepared_bind_group: UnpreparedBindGroup,
) -> MaterialBindingId {
for (slab_index, slab) in self.slabs.iter_mut().enumerate() {
trace!("Trying to allocate in slab {}", slab_index);
@ -842,7 +819,7 @@ where
///
/// A [`MaterialBindGroupIndex`] can be fetched from a
/// [`MaterialBindingId`].
fn get(&self, group: MaterialBindGroupIndex) -> Option<&MaterialBindlessSlab<M>> {
fn get(&self, group: MaterialBindGroupIndex) -> Option<&MaterialBindlessSlab> {
self.slabs.get(group.0 as usize)
}
@ -858,6 +835,7 @@ where
for slab in &mut self.slabs {
slab.prepare(
render_device,
self.label,
&self.bind_group_layout,
fallback_bindless_resources,
&self.fallback_buffers,
@ -878,20 +856,7 @@ where
}
}
impl<M> FromWorld for MaterialBindGroupAllocator<M>
where
M: Material,
{
fn from_world(world: &mut World) -> Self {
let render_device = world.resource::<RenderDevice>();
MaterialBindGroupAllocator::new(render_device)
}
}
impl<M> MaterialBindlessSlab<M>
where
M: Material,
{
impl MaterialBindlessSlab {
/// Attempts to allocate the given unprepared bind group in this slab.
///
/// If the allocation succeeds, this method returns the slot that the
@ -900,9 +865,9 @@ where
/// so that it can try to allocate again.
fn try_allocate(
&mut self,
unprepared_bind_group: UnpreparedBindGroup<M::Data>,
unprepared_bind_group: UnpreparedBindGroup,
slot_capacity: u32,
) -> Result<MaterialBindGroupSlot, UnpreparedBindGroup<M::Data>> {
) -> Result<MaterialBindGroupSlot, UnpreparedBindGroup> {
// Locate pre-existing resources, and determine how many free slots we need.
let Some(allocation_candidate) = self.check_allocation(&unprepared_bind_group) else {
return Err(unprepared_bind_group);
@ -942,12 +907,6 @@ where
bindless_index_table.set(slot, &allocated_resource_slots);
}
// Insert extra data.
if self.extra_data.len() < (*slot as usize + 1) {
self.extra_data.resize_with(*slot as usize + 1, || None);
}
self.extra_data[*slot as usize] = Some(unprepared_bind_group.data);
// Invalidate the cached bind group.
self.bind_group = None;
@ -958,7 +917,7 @@ where
/// bind group can be allocated in this slab.
fn check_allocation(
&self,
unprepared_bind_group: &UnpreparedBindGroup<M::Data>,
unprepared_bind_group: &UnpreparedBindGroup,
) -> Option<BindlessAllocationCandidate> {
let mut allocation_candidate = BindlessAllocationCandidate {
pre_existing_resources: HashMap::default(),
@ -1189,9 +1148,6 @@ where
}
}
// Clear out the extra data.
self.extra_data[slot.0 as usize] = None;
// Invalidate the cached bind group.
self.bind_group = None;
@ -1204,6 +1160,7 @@ where
fn prepare(
&mut self,
render_device: &RenderDevice,
label: Option<&'static str>,
bind_group_layout: &BindGroupLayout,
fallback_bindless_resources: &FallbackBindlessResources,
fallback_buffers: &HashMap<BindlessIndex, Buffer>,
@ -1224,6 +1181,7 @@ where
// Create the bind group if needed.
self.prepare_bind_group(
render_device,
label,
bind_group_layout,
fallback_bindless_resources,
fallback_buffers,
@ -1238,6 +1196,7 @@ where
fn prepare_bind_group(
&mut self,
render_device: &RenderDevice,
label: Option<&'static str>,
bind_group_layout: &BindGroupLayout,
fallback_bindless_resources: &FallbackBindlessResources,
fallback_buffers: &HashMap<BindlessIndex, Buffer>,
@ -1304,11 +1263,8 @@ where
});
}
self.bind_group = Some(render_device.create_bind_group(
M::label(),
bind_group_layout,
&bind_group_entries,
));
self.bind_group =
Some(render_device.create_bind_group(label, bind_group_layout, &bind_group_entries));
}
/// Writes any buffers that we're managing to the GPU.
@ -1548,19 +1504,11 @@ where
self.bind_group.as_ref()
}
/// Returns the extra data associated with this material.
fn get_extra_data(&self, slot: MaterialBindGroupSlot) -> &M::Data {
self.extra_data
.get(slot.0 as usize)
.and_then(|data| data.as_ref())
.expect("Extra data not present")
}
/// Returns the bindless index table containing the given bindless index.
fn get_bindless_index_table(
&self,
bindless_index: BindlessIndex,
) -> Option<&MaterialBindlessIndexTable<M>> {
) -> Option<&MaterialBindlessIndexTable> {
let table_index = self
.bindless_index_tables
.binary_search_by(|bindless_index_table| {
@ -1689,15 +1637,12 @@ where
})
}
impl<M> MaterialBindlessSlab<M>
where
M: Material,
{
impl MaterialBindlessSlab {
/// Creates a new [`MaterialBindlessSlab`] for a material with the given
/// bindless descriptor.
///
/// We use this when no existing slab could hold a material to be allocated.
fn new(bindless_descriptor: &BindlessDescriptor) -> MaterialBindlessSlab<M> {
fn new(bindless_descriptor: &BindlessDescriptor) -> MaterialBindlessSlab {
let mut buffers = HashMap::default();
let mut samplers = HashMap::default();
let mut textures = HashMap::default();
@ -1780,7 +1725,7 @@ where
let bindless_index_tables = bindless_descriptor
.index_tables
.iter()
.map(|bindless_index_table| MaterialBindlessIndexTable::new(bindless_index_table))
.map(MaterialBindlessIndexTable::new)
.collect();
MaterialBindlessSlab {
@ -1790,7 +1735,6 @@ where
textures,
buffers,
data_buffers,
extra_data: vec![],
free_slots: vec![],
live_allocation_count: 0,
allocated_resource_count: 0,
@ -1822,18 +1766,15 @@ impl FromWorld for FallbackBindlessResources {
}
}
impl<M> MaterialBindGroupNonBindlessAllocator<M>
where
M: Material,
{
impl MaterialBindGroupNonBindlessAllocator {
/// Creates a new [`MaterialBindGroupNonBindlessAllocator`] managing the
/// bind groups for a single non-bindless material.
fn new() -> MaterialBindGroupNonBindlessAllocator<M> {
fn new(label: Option<&'static str>) -> MaterialBindGroupNonBindlessAllocator {
MaterialBindGroupNonBindlessAllocator {
label,
bind_groups: vec![],
to_prepare: HashSet::default(),
free_indices: vec![],
phantom: PhantomData,
}
}
@ -1842,10 +1783,7 @@ where
///
/// The returned [`MaterialBindingId`] can later be used to fetch the bind
/// group.
fn allocate(
&mut self,
bind_group: MaterialNonBindlessAllocatedBindGroup<M>,
) -> MaterialBindingId {
fn allocate(&mut self, bind_group: MaterialNonBindlessAllocatedBindGroup) -> MaterialBindingId {
let group_id = self
.free_indices
.pop()
@ -1874,7 +1812,7 @@ where
/// [`MaterialBindingId`].
fn allocate_unprepared(
&mut self,
unprepared_bind_group: UnpreparedBindGroup<M::Data>,
unprepared_bind_group: UnpreparedBindGroup,
bind_group_layout: BindGroupLayout,
) -> MaterialBindingId {
self.allocate(MaterialNonBindlessAllocatedBindGroup::Unprepared {
@ -1885,10 +1823,7 @@ where
/// Inserts an prepared bind group into this allocator and returns a
/// [`MaterialBindingId`].
fn allocate_prepared(
&mut self,
prepared_bind_group: PreparedBindGroup<M::Data>,
) -> MaterialBindingId {
fn allocate_prepared(&mut self, prepared_bind_group: PreparedBindGroup) -> MaterialBindingId {
self.allocate(MaterialNonBindlessAllocatedBindGroup::Prepared {
bind_group: prepared_bind_group,
uniform_buffers: vec![],
@ -1905,15 +1840,15 @@ where
}
/// Returns a wrapper around the bind group with the given index.
fn get(&self, group: MaterialBindGroupIndex) -> Option<MaterialNonBindlessSlab<M>> {
fn get(&self, group: MaterialBindGroupIndex) -> Option<MaterialNonBindlessSlab> {
self.bind_groups[group.0 as usize]
.as_ref()
.map(|bind_group| match bind_group {
MaterialNonBindlessAllocatedBindGroup::Prepared { bind_group, .. } => {
MaterialNonBindlessSlab::Prepared(bind_group)
}
MaterialNonBindlessAllocatedBindGroup::Unprepared { bind_group, .. } => {
MaterialNonBindlessSlab::Unprepared(bind_group)
MaterialNonBindlessAllocatedBindGroup::Unprepared { .. } => {
MaterialNonBindlessSlab::Unprepared
}
})
}
@ -1972,7 +1907,7 @@ where
// Create the bind group.
let bind_group = render_device.create_bind_group(
M::label(),
self.label,
&bind_group_layout,
&bind_group_entries,
);
@ -1982,7 +1917,6 @@ where
bind_group: PreparedBindGroup {
bindings: unprepared_bind_group.bindings,
bind_group,
data: unprepared_bind_group.data,
},
uniform_buffers,
});
@ -1990,28 +1924,7 @@ where
}
}
impl<'a, M> MaterialSlab<'a, M>
where
M: Material,
{
/// Returns the extra data associated with this material.
///
/// When deriving `AsBindGroup`, this data is given by the
/// `#[bind_group_data(DataType)]` attribute on the material structure.
pub fn get_extra_data(&self, slot: MaterialBindGroupSlot) -> &M::Data {
match self.0 {
MaterialSlabImpl::Bindless(material_bindless_slab) => {
material_bindless_slab.get_extra_data(slot)
}
MaterialSlabImpl::NonBindless(MaterialNonBindlessSlab::Prepared(
prepared_bind_group,
)) => &prepared_bind_group.data,
MaterialSlabImpl::NonBindless(MaterialNonBindlessSlab::Unprepared(
unprepared_bind_group,
)) => &unprepared_bind_group.data,
}
}
impl<'a> MaterialSlab<'a> {
/// Returns the [`BindGroup`] corresponding to this slab, if it's been
/// prepared.
///
@ -2026,7 +1939,7 @@ where
MaterialSlabImpl::NonBindless(MaterialNonBindlessSlab::Prepared(
prepared_bind_group,
)) => Some(&prepared_bind_group.bind_group),
MaterialSlabImpl::NonBindless(MaterialNonBindlessSlab::Unprepared(_)) => None,
MaterialSlabImpl::NonBindless(MaterialNonBindlessSlab::Unprepared) => None,
}
}
}

View File

@ -1,8 +1,8 @@
use super::{meshlet_mesh_manager::MeshletMeshManager, MeshletMesh, MeshletMesh3d};
use crate::{
material::DUMMY_MESH_MATERIAL, Material, MaterialBindingId, MeshFlags, MeshTransforms,
MeshUniform, NotShadowCaster, NotShadowReceiver, PreviousGlobalTransform,
RenderMaterialBindings, RenderMaterialInstances,
material::DUMMY_MESH_MATERIAL, MaterialBindingId, MeshFlags, MeshTransforms, MeshUniform,
NotShadowCaster, NotShadowReceiver, PreviousGlobalTransform, RenderMaterialBindings,
RenderMaterialInstances,
};
use bevy_asset::{AssetEvent, AssetServer, Assets, UntypedAssetId};
use bevy_ecs::{
@ -40,9 +40,9 @@ pub struct InstanceManager {
/// Per-view per-instance visibility bit. Used for [`RenderLayers`] and [`NotShadowCaster`] support.
pub view_instance_visibility: EntityHashMap<StorageBuffer<Vec<u32>>>,
/// Next material ID available for a [`Material`].
/// Next material ID available.
next_material_id: u32,
/// Map of [`Material`] to material ID.
/// Map of material asset to material ID.
material_id_lookup: HashMap<UntypedAssetId, u32>,
/// Set of material IDs used in the scene.
material_ids_present_in_scene: HashSet<u32>,
@ -272,7 +272,7 @@ pub fn extract_meshlet_mesh_entities(
/// For each entity in the scene, record what material ID its material was assigned in the `prepare_material_meshlet_meshes` systems,
/// and note that the material is used by at least one entity in the scene.
pub fn queue_material_meshlet_meshes<M: Material>(
pub fn queue_material_meshlet_meshes(
mut instance_manager: ResMut<InstanceManager>,
render_material_instances: Res<RenderMaterialInstances>,
) {
@ -280,16 +280,14 @@ pub fn queue_material_meshlet_meshes<M: Material>(
for (i, (instance, _, _)) in instance_manager.instances.iter().enumerate() {
if let Some(material_instance) = render_material_instances.instances.get(instance) {
if let Ok(material_asset_id) = material_instance.asset_id.try_typed::<M>() {
if let Some(material_id) = instance_manager
.material_id_lookup
.get(&material_asset_id.untyped())
{
instance_manager
.material_ids_present_in_scene
.insert(*material_id);
instance_manager.instance_material_ids.get_mut()[i] = *material_id;
}
if let Some(material_id) = instance_manager
.material_id_lookup
.get(&material_instance.asset_id)
{
instance_manager
.material_ids_present_in_scene
.insert(*material_id);
instance_manager.instance_material_ids.get_mut()[i] = *material_id;
}
}
}

View File

@ -2,11 +2,7 @@ use super::{
instance_manager::InstanceManager, resource_manager::ResourceManager,
MESHLET_MESH_MATERIAL_SHADER_HANDLE,
};
use crate::{
environment_map::EnvironmentMapLight, irradiance_volume::IrradianceVolume,
material_bind_groups::MaterialBindGroupAllocator, *,
};
use bevy_asset::AssetServer;
use crate::{environment_map::EnvironmentMapLight, irradiance_volume::IrradianceVolume, *};
use bevy_core_pipeline::{
core_3d::Camera3d,
prepass::{DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass},
@ -14,14 +10,13 @@ use bevy_core_pipeline::{
};
use bevy_derive::{Deref, DerefMut};
use bevy_platform::collections::{HashMap, HashSet};
use bevy_render::erased_render_asset::ErasedRenderAssets;
use bevy_render::{
camera::TemporalJitter,
mesh::{Mesh, MeshVertexBufferLayout, MeshVertexBufferLayoutRef, MeshVertexBufferLayouts},
render_asset::RenderAssets,
render_resource::*,
view::ExtractedView,
};
use core::hash::Hash;
/// A list of `(Material ID, Pipeline, BindGroup)` for a view for use in [`super::MeshletMainOpaquePass3dNode`].
#[derive(Component, Deref, DerefMut, Default)]
@ -29,17 +24,16 @@ pub struct MeshletViewMaterialsMainOpaquePass(pub Vec<(u32, CachedRenderPipeline
/// Prepare [`Material`] pipelines for [`super::MeshletMesh`] entities for use in [`super::MeshletMainOpaquePass3dNode`],
/// and register the material with [`InstanceManager`].
pub fn prepare_material_meshlet_meshes_main_opaque_pass<M: Material>(
pub fn prepare_material_meshlet_meshes_main_opaque_pass(
resource_manager: ResMut<ResourceManager>,
mut instance_manager: ResMut<InstanceManager>,
mut cache: Local<HashMap<MeshPipelineKey, CachedRenderPipelineId>>,
pipeline_cache: Res<PipelineCache>,
material_pipeline: Res<MaterialPipeline<M>>,
material_pipeline: Res<MaterialPipeline>,
mesh_pipeline: Res<MeshPipeline>,
render_materials: Res<RenderAssets<PreparedMaterial<M>>>,
render_materials: Res<ErasedRenderAssets<PreparedMaterial>>,
render_material_instances: Res<RenderMaterialInstances>,
material_bind_group_allocator: Res<MaterialBindGroupAllocator<M>>,
asset_server: Res<AssetServer>,
material_bind_group_allocators: Res<MaterialBindGroupAllocators>,
mut mesh_vertex_buffer_layouts: ResMut<MeshVertexBufferLayouts>,
mut views: Query<
(
@ -62,9 +56,7 @@ pub fn prepare_material_meshlet_meshes_main_opaque_pass<M: Material>(
),
With<Camera3d>,
>,
) where
M::Data: PartialEq + Eq + Hash + Clone,
{
) {
let fake_vertex_buffer_layout = &fake_vertex_buffer_layout(&mut mesh_vertex_buffer_layouts);
for (
@ -151,17 +143,12 @@ pub fn prepare_material_meshlet_meshes_main_opaque_pass<M: Material>(
for material_id in render_material_instances
.instances
.values()
.flat_map(|instance| instance.asset_id.try_typed::<M>().ok())
.map(|instance| instance.asset_id)
.collect::<HashSet<_>>()
{
let Some(material) = render_materials.get(material_id) else {
continue;
};
let Some(material_bind_group) =
material_bind_group_allocator.get(material.binding.group)
else {
continue;
};
if material.properties.render_method != OpaqueRendererMethod::Forward
|| material.properties.alpha_mode != AlphaMode::Opaque
@ -170,15 +157,18 @@ pub fn prepare_material_meshlet_meshes_main_opaque_pass<M: Material>(
continue;
}
let Ok(material_pipeline_descriptor) = material_pipeline.specialize(
MaterialPipelineKey {
mesh_key: view_key,
bind_group_data: material_bind_group
.get_extra_data(material.binding.slot)
.clone(),
},
fake_vertex_buffer_layout,
) else {
let erased_key = ErasedMaterialPipelineKey {
mesh_key: view_key,
material_key: material.properties.material_key.clone(),
type_id: material_id.type_id(),
};
let material_pipeline_specializer = MaterialPipelineSpecializer {
pipeline: material_pipeline.clone(),
properties: material.properties.clone(),
};
let Ok(material_pipeline_descriptor) =
material_pipeline_specializer.specialize(erased_key, fake_vertex_buffer_layout)
else {
continue;
};
let material_fragment = material_pipeline_descriptor.fragment.unwrap();
@ -191,7 +181,12 @@ pub fn prepare_material_meshlet_meshes_main_opaque_pass<M: Material>(
layout.main_layout.clone(),
layout.binding_array_layout.clone(),
resource_manager.material_shade_bind_group_layout.clone(),
material_pipeline.material_layout.clone(),
material
.properties
.material_layout
.as_ref()
.unwrap()
.clone(),
];
let pipeline_descriptor = RenderPipelineDescriptor {
@ -214,10 +209,9 @@ pub fn prepare_material_meshlet_meshes_main_opaque_pass<M: Material>(
}),
multisample: MultisampleState::default(),
fragment: Some(FragmentState {
shader: match M::meshlet_mesh_fragment_shader() {
ShaderRef::Default => MESHLET_MESH_MATERIAL_SHADER_HANDLE,
ShaderRef::Handle(handle) => handle,
ShaderRef::Path(path) => asset_server.load(path),
shader: match material.properties.get_shader(MeshletFragmentShader) {
Some(shader) => shader.clone(),
None => MESHLET_MESH_MATERIAL_SHADER_HANDLE,
},
shader_defs,
entry_point: material_fragment.entry_point,
@ -225,8 +219,12 @@ pub fn prepare_material_meshlet_meshes_main_opaque_pass<M: Material>(
}),
zero_initialize_workgroup_memory: false,
};
let material_id = instance_manager.get_material_id(material_id.untyped());
let Some(material_bind_group_allocator) =
material_bind_group_allocators.get(&material_id.type_id())
else {
continue;
};
let material_id = instance_manager.get_material_id(material_id);
let pipeline_id = *cache.entry(view_key).or_insert_with(|| {
pipeline_cache.queue_render_pipeline(pipeline_descriptor.clone())
@ -258,17 +256,16 @@ pub struct MeshletViewMaterialsDeferredGBufferPrepass(
/// Prepare [`Material`] pipelines for [`super::MeshletMesh`] entities for use in [`super::MeshletPrepassNode`],
/// and [`super::MeshletDeferredGBufferPrepassNode`] and register the material with [`InstanceManager`].
pub fn prepare_material_meshlet_meshes_prepass<M: Material>(
pub fn prepare_material_meshlet_meshes_prepass(
resource_manager: ResMut<ResourceManager>,
mut instance_manager: ResMut<InstanceManager>,
mut cache: Local<HashMap<MeshPipelineKey, CachedRenderPipelineId>>,
pipeline_cache: Res<PipelineCache>,
prepass_pipeline: Res<PrepassPipeline<M>>,
render_materials: Res<RenderAssets<PreparedMaterial<M>>>,
prepass_pipeline: Res<PrepassPipeline>,
material_bind_group_allocators: Res<MaterialBindGroupAllocators>,
render_materials: Res<ErasedRenderAssets<PreparedMaterial>>,
render_material_instances: Res<RenderMaterialInstances>,
mut mesh_vertex_buffer_layouts: ResMut<MeshVertexBufferLayouts>,
material_bind_group_allocator: Res<MaterialBindGroupAllocator<M>>,
asset_server: Res<AssetServer>,
mut views: Query<
(
&mut MeshletViewMaterialsPrepass,
@ -278,9 +275,7 @@ pub fn prepare_material_meshlet_meshes_prepass<M: Material>(
),
With<Camera3d>,
>,
) where
M::Data: PartialEq + Eq + Hash + Clone,
{
) {
let fake_vertex_buffer_layout = &fake_vertex_buffer_layout(&mut mesh_vertex_buffer_layouts);
for (
@ -305,14 +300,14 @@ pub fn prepare_material_meshlet_meshes_prepass<M: Material>(
for material_id in render_material_instances
.instances
.values()
.flat_map(|instance| instance.asset_id.try_typed::<M>().ok())
.map(|instance| instance.asset_id)
.collect::<HashSet<_>>()
{
let Some(material) = render_materials.get(material_id) else {
continue;
};
let Some(material_bind_group) =
material_bind_group_allocator.get(material.binding.group)
let Some(material_bind_group_allocator) =
material_bind_group_allocators.get(&material_id.type_id())
else {
continue;
};
@ -333,15 +328,18 @@ pub fn prepare_material_meshlet_meshes_prepass<M: Material>(
continue;
}
let Ok(material_pipeline_descriptor) = prepass_pipeline.specialize(
MaterialPipelineKey {
mesh_key: view_key,
bind_group_data: material_bind_group
.get_extra_data(material.binding.slot)
.clone(),
},
fake_vertex_buffer_layout,
) else {
let erased_key = ErasedMaterialPipelineKey {
mesh_key: view_key,
material_key: material.properties.material_key.clone(),
type_id: material_id.type_id(),
};
let material_pipeline_specializer = PrepassPipelineSpecializer {
pipeline: prepass_pipeline.clone(),
properties: material.properties.clone(),
};
let Ok(material_pipeline_descriptor) =
material_pipeline_specializer.specialize(erased_key, fake_vertex_buffer_layout)
else {
continue;
};
let material_fragment = material_pipeline_descriptor.fragment.unwrap();
@ -359,13 +357,19 @@ pub fn prepare_material_meshlet_meshes_prepass<M: Material>(
};
let fragment_shader = if view_key.contains(MeshPipelineKey::DEFERRED_PREPASS) {
M::meshlet_mesh_deferred_fragment_shader()
material
.properties
.get_shader(MeshletDeferredFragmentShader)
.unwrap_or(MESHLET_MESH_MATERIAL_SHADER_HANDLE)
} else {
M::meshlet_mesh_prepass_fragment_shader()
material
.properties
.get_shader(MeshletPrepassFragmentShader)
.unwrap_or(MESHLET_MESH_MATERIAL_SHADER_HANDLE)
};
let entry_point = match fragment_shader {
ShaderRef::Default => "prepass_fragment".into(),
let entry_point = match &fragment_shader {
x if x == &MESHLET_MESH_MATERIAL_SHADER_HANDLE => "prepass_fragment".into(),
_ => material_fragment.entry_point,
};
@ -374,7 +378,12 @@ pub fn prepare_material_meshlet_meshes_prepass<M: Material>(
layout: vec![
view_layout,
resource_manager.material_shade_bind_group_layout.clone(),
prepass_pipeline.internal.material_layout.clone(),
material
.properties
.material_layout
.as_ref()
.unwrap()
.clone(),
],
push_constant_ranges: vec![],
vertex: VertexState {
@ -393,11 +402,7 @@ pub fn prepare_material_meshlet_meshes_prepass<M: Material>(
}),
multisample: MultisampleState::default(),
fragment: Some(FragmentState {
shader: match fragment_shader {
ShaderRef::Default => MESHLET_MESH_MATERIAL_SHADER_HANDLE,
ShaderRef::Handle(handle) => handle,
ShaderRef::Path(path) => asset_server.load(path),
},
shader: fragment_shader,
shader_defs,
entry_point,
targets: material_fragment.targets,
@ -405,7 +410,7 @@ pub fn prepare_material_meshlet_meshes_prepass<M: Material>(
zero_initialize_workgroup_memory: false,
};
let material_id = instance_manager.get_material_id(material_id.untyped());
let material_id = instance_manager.get_material_id(material_id);
let pipeline_id = *cache.entry(view_key).or_insert_with(|| {
pipeline_cache.queue_render_pipeline(pipeline_descriptor.clone())

View File

@ -283,6 +283,10 @@ impl Plugin for MeshletPlugin {
.in_set(RenderSystems::ManageViews),
prepare_meshlet_per_frame_resources.in_set(RenderSystems::PrepareResources),
prepare_meshlet_view_bind_groups.in_set(RenderSystems::PrepareBindGroups),
queue_material_meshlet_meshes.in_set(RenderSystems::QueueMeshes),
prepare_material_meshlet_meshes_main_opaque_pass
.in_set(RenderSystems::QueueMeshes)
.before(queue_material_meshlet_meshes),
),
);
}

View File

@ -1185,7 +1185,8 @@ impl AsBindGroupShaderType<StandardMaterialUniform> for StandardMaterial {
bitflags! {
/// The pipeline key for `StandardMaterial`, packed into 64 bits.
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
#[repr(C)]
#[derive(Clone, Copy, PartialEq, Eq, Hash, bytemuck::Pod, bytemuck::Zeroable)]
pub struct StandardMaterialKey: u64 {
const CULL_FRONT = 0x000001;
const CULL_BACK = 0x000002;
@ -1404,7 +1405,7 @@ impl Material for StandardMaterial {
}
fn specialize(
_pipeline: &MaterialPipeline<Self>,
_pipeline: &MaterialPipeline,
descriptor: &mut RenderPipelineDescriptor,
_layout: &MeshVertexBufferLayoutRef,
key: MaterialPipelineKey<Self>,

View File

@ -2,12 +2,13 @@ mod prepass_bindings;
use crate::{
alpha_mode_pipeline_key, binding_arrays_are_usable, buffer_layout,
collect_meshes_for_gpu_building, material_bind_groups::MaterialBindGroupAllocator,
queue_material_meshes, set_mesh_motion_vector_flags, setup_morph_and_skinning_defs, skin,
DrawMesh, EntitySpecializationTicks, Material, MaterialPipeline, MaterialPipelineKey,
collect_meshes_for_gpu_building, set_mesh_motion_vector_flags, setup_morph_and_skinning_defs,
skin, DeferredDrawFunction, DeferredFragmentShader, DeferredVertexShader, DrawMesh,
EntitySpecializationTicks, ErasedMaterialPipelineKey, MaterialPipeline, MaterialProperties,
MeshLayouts, MeshPipeline, MeshPipelineKey, OpaqueRendererMethod, PreparedMaterial,
RenderLightmaps, RenderMaterialInstances, RenderMeshInstanceFlags, RenderMeshInstances,
RenderPhaseType, SetMaterialBindGroup, SetMeshBindGroup, ShadowView, StandardMaterial,
PrepassDrawFunction, PrepassFragmentShader, PrepassVertexShader, RenderLightmaps,
RenderMaterialInstances, RenderMeshInstanceFlags, RenderMeshInstances, RenderPhaseType,
SetMaterialBindGroup, SetMeshBindGroup, ShadowView,
};
use bevy_app::{App, Plugin, PreUpdate};
use bevy_render::{
@ -24,7 +25,7 @@ use bevy_render::{
};
pub use prepass_bindings::*;
use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer, Handle};
use bevy_asset::{embedded_asset, load_embedded_asset, Handle};
use bevy_core_pipeline::{
core_3d::CORE_3D_DEPTH_FORMAT, deferred::*, prelude::Camera3d, prepass::*,
};
@ -55,30 +56,22 @@ use crate::meshlet::{
MeshletMesh3d,
};
use alloc::sync::Arc;
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::component::Tick;
use bevy_ecs::system::SystemChangeTick;
use bevy_platform::collections::HashMap;
use bevy_render::erased_render_asset::ErasedRenderAssets;
use bevy_render::sync_world::MainEntityHashMap;
use bevy_render::view::RenderVisibleEntities;
use bevy_render::RenderSystems::{PrepareAssets, PrepareResources};
use core::{hash::Hash, marker::PhantomData};
/// Sets up everything required to use the prepass pipeline.
///
/// This does not add the actual prepasses, see [`PrepassPlugin`] for that.
pub struct PrepassPipelinePlugin<M: Material>(PhantomData<M>);
pub struct PrepassPipelinePlugin;
impl<M: Material> Default for PrepassPipelinePlugin<M> {
fn default() -> Self {
Self(Default::default())
}
}
impl<M: Material> Plugin for PrepassPipelinePlugin<M>
where
M::Data: PartialEq + Eq + Hash + Clone,
{
impl Plugin for PrepassPipelinePlugin {
fn build(&self, app: &mut App) {
embedded_asset!(app, "prepass.wgsl");
@ -93,10 +86,9 @@ where
render_app
.add_systems(
Render,
prepare_prepass_view_bind_group::<M>.in_set(RenderSystems::PrepareBindGroups),
prepare_prepass_view_bind_group.in_set(RenderSystems::PrepareBindGroups),
)
.init_resource::<SpecializedMeshPipelines<PrepassPipeline<M>>>()
.allow_ambiguous_resource::<SpecializedMeshPipelines<PrepassPipeline<M>>>();
.init_resource::<SpecializedMeshPipelines<PrepassPipelineSpecializer>>();
}
fn finish(&self, app: &mut App) {
@ -105,34 +97,27 @@ where
};
render_app
.init_resource::<PrepassPipeline<M>>()
.init_resource::<PrepassPipeline>()
.init_resource::<PrepassViewBindGroup>();
}
}
/// Sets up the prepasses for a [`Material`].
/// Sets up the prepasses for a material.
///
/// This depends on the [`PrepassPipelinePlugin`].
pub struct PrepassPlugin<M: Material> {
pub struct PrepassPlugin {
/// Debugging flags that can optionally be set when constructing the renderer.
pub debug_flags: RenderDebugFlags,
pub phantom: PhantomData<M>,
}
impl<M: Material> PrepassPlugin<M> {
impl PrepassPlugin {
/// Creates a new [`PrepassPlugin`] with the given debug flags.
pub fn new(debug_flags: RenderDebugFlags) -> Self {
PrepassPlugin {
debug_flags,
phantom: PhantomData,
}
PrepassPlugin { debug_flags }
}
}
impl<M: Material> Plugin for PrepassPlugin<M>
where
M::Data: PartialEq + Eq + Hash + Clone,
{
impl Plugin for PrepassPlugin {
fn build(&self, app: &mut App) {
let no_prepass_plugin_loaded = app
.world()
@ -174,36 +159,30 @@ where
render_app
.init_resource::<ViewPrepassSpecializationTicks>()
.init_resource::<ViewKeyPrepassCache>()
.init_resource::<SpecializedPrepassMaterialPipelineCache<M>>()
.add_render_command::<Opaque3dPrepass, DrawPrepass<M>>()
.add_render_command::<AlphaMask3dPrepass, DrawPrepass<M>>()
.add_render_command::<Opaque3dDeferred, DrawPrepass<M>>()
.add_render_command::<AlphaMask3dDeferred, DrawPrepass<M>>()
.init_resource::<SpecializedPrepassMaterialPipelineCache>()
.add_render_command::<Opaque3dPrepass, DrawPrepass>()
.add_render_command::<AlphaMask3dPrepass, DrawPrepass>()
.add_render_command::<Opaque3dDeferred, DrawPrepass>()
.add_render_command::<AlphaMask3dDeferred, DrawPrepass>()
.add_systems(
Render,
(
check_prepass_views_need_specialization.in_set(PrepareAssets),
specialize_prepass_material_meshes::<M>
specialize_prepass_material_meshes
.in_set(RenderSystems::PrepareMeshes)
.after(prepare_assets::<PreparedMaterial<M>>)
.after(prepare_assets::<RenderMesh>)
.after(collect_meshes_for_gpu_building)
.after(set_mesh_motion_vector_flags),
queue_prepass_material_meshes::<M>
.in_set(RenderSystems::QueueMeshes)
.after(prepare_assets::<PreparedMaterial<M>>)
// queue_material_meshes only writes to `material_bind_group_id`, which `queue_prepass_material_meshes` doesn't read
.ambiguous_with(queue_material_meshes::<StandardMaterial>),
queue_prepass_material_meshes.in_set(RenderSystems::QueueMeshes),
),
);
#[cfg(feature = "meshlet")]
render_app.add_systems(
Render,
prepare_material_meshlet_meshes_prepass::<M>
prepare_material_meshlet_meshes_prepass
.in_set(RenderSystems::QueueMeshes)
.after(prepare_assets::<PreparedMaterial<M>>)
.before(queue_material_meshlet_meshes::<M>)
.before(queue_material_meshlet_meshes)
.run_if(resource_exists::<InstanceManager>),
);
}
@ -261,24 +240,20 @@ pub fn update_mesh_previous_global_transforms(
}
}
#[derive(Resource)]
pub struct PrepassPipeline<M: Material> {
#[derive(Resource, Clone)]
pub struct PrepassPipeline {
pub internal: PrepassPipelineInternal,
pub material_pipeline: MaterialPipeline<M>,
pub material_pipeline: MaterialPipeline,
}
/// Internal fields of the `PrepassPipeline` that don't need the generic bound
/// This is done as an optimization to not recompile the same code multiple time
#[derive(Clone)]
pub struct PrepassPipelineInternal {
pub view_layout_motion_vectors: BindGroupLayout,
pub view_layout_no_motion_vectors: BindGroupLayout,
pub mesh_layouts: MeshLayouts,
pub empty_layout: BindGroupLayout,
pub material_layout: BindGroupLayout,
pub prepass_material_vertex_shader: Option<Handle<Shader>>,
pub prepass_material_fragment_shader: Option<Handle<Shader>>,
pub deferred_material_vertex_shader: Option<Handle<Shader>>,
pub deferred_material_fragment_shader: Option<Handle<Shader>>,
pub default_prepass_shader: Handle<Shader>,
/// Whether skins will use uniform buffers on account of storage buffers
@ -292,12 +267,10 @@ pub struct PrepassPipelineInternal {
pub binding_arrays_are_usable: bool,
}
impl<M: Material> FromWorld for PrepassPipeline<M> {
impl FromWorld for PrepassPipeline {
fn from_world(world: &mut World) -> Self {
let render_device = world.resource::<RenderDevice>();
let render_adapter = world.resource::<RenderAdapter>();
let asset_server = world.resource::<AssetServer>();
let visibility_ranges_buffer_binding_type = render_device
.get_supported_read_only_binding_type(VISIBILITY_RANGES_STORAGE_BUFFER_COUNT);
@ -358,28 +331,7 @@ impl<M: Material> FromWorld for PrepassPipeline<M> {
view_layout_motion_vectors,
view_layout_no_motion_vectors,
mesh_layouts: mesh_pipeline.mesh_layouts.clone(),
prepass_material_vertex_shader: match M::prepass_vertex_shader() {
ShaderRef::Default => None,
ShaderRef::Handle(handle) => Some(handle),
ShaderRef::Path(path) => Some(asset_server.load(path)),
},
prepass_material_fragment_shader: match M::prepass_fragment_shader() {
ShaderRef::Default => None,
ShaderRef::Handle(handle) => Some(handle),
ShaderRef::Path(path) => Some(asset_server.load(path)),
},
deferred_material_vertex_shader: match M::deferred_vertex_shader() {
ShaderRef::Default => None,
ShaderRef::Handle(handle) => Some(handle),
ShaderRef::Path(path) => Some(asset_server.load(path)),
},
deferred_material_fragment_shader: match M::deferred_fragment_shader() {
ShaderRef::Default => None,
ShaderRef::Handle(handle) => Some(handle),
ShaderRef::Path(path) => Some(asset_server.load(path)),
},
default_prepass_shader: load_embedded_asset!(world, "prepass.wgsl"),
material_layout: M::bind_group_layout(render_device),
skins_use_uniform_buffers: skin::skins_use_uniform_buffers(render_device),
depth_clip_control_supported,
binding_arrays_are_usable: binding_arrays_are_usable(render_device, render_adapter),
@ -387,16 +339,18 @@ impl<M: Material> FromWorld for PrepassPipeline<M> {
};
PrepassPipeline {
internal,
material_pipeline: world.resource::<MaterialPipeline<M>>().clone(),
material_pipeline: world.resource::<MaterialPipeline>().clone(),
}
}
}
impl<M: Material> SpecializedMeshPipeline for PrepassPipeline<M>
where
M::Data: PartialEq + Eq + Hash + Clone,
{
type Key = MaterialPipelineKey<M>;
pub struct PrepassPipelineSpecializer {
pub(crate) pipeline: PrepassPipeline,
pub(crate) properties: Arc<MaterialProperties>,
}
impl SpecializedMeshPipeline for PrepassPipelineSpecializer {
type Key = ErasedMaterialPipelineKey;
fn specialize(
&self,
@ -404,17 +358,27 @@ where
layout: &MeshVertexBufferLayoutRef,
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
let mut shader_defs = Vec::new();
if self.material_pipeline.bindless {
if self.properties.bindless {
shader_defs.push("BINDLESS".into());
}
let mut descriptor = self
.internal
.specialize(key.mesh_key, shader_defs, layout)?;
let mut descriptor = self.pipeline.internal.specialize(
key.mesh_key,
shader_defs,
layout,
&self.properties,
)?;
// This is a bit risky because it's possible to change something that would
// break the prepass but be fine in the main pass.
// Since this api is pretty low-level it doesn't matter that much, but it is a potential issue.
M::specialize(&self.material_pipeline, &mut descriptor, layout, key)?;
if let Some(specialize) = self.properties.specialize {
specialize(
&self.pipeline.material_pipeline,
&mut descriptor,
layout,
key,
)?;
}
Ok(descriptor)
}
@ -426,6 +390,7 @@ impl PrepassPipelineInternal {
mesh_key: MeshPipelineKey,
shader_defs: Vec<ShaderDefVal>,
layout: &MeshVertexBufferLayoutRef,
material_properties: &MaterialProperties,
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
let mut shader_defs = shader_defs;
let mut bind_group_layouts = vec![
@ -445,7 +410,13 @@ impl PrepassPipelineInternal {
// NOTE: Eventually, it would be nice to only add this when the shaders are overloaded by the Material.
// The main limitation right now is that bind group order is hardcoded in shaders.
bind_group_layouts.push(self.material_layout.clone());
bind_group_layouts.push(
material_properties
.material_layout
.as_ref()
.unwrap()
.clone(),
);
#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
shader_defs.push("WEBGL2".into());
shader_defs.push("VERTEX_OUTPUT_INSTANCE_INDEX".into());
@ -581,17 +552,28 @@ impl PrepassPipelineInternal {
let fragment_required = !targets.is_empty()
|| emulate_unclipped_depth
|| (mesh_key.contains(MeshPipelineKey::MAY_DISCARD)
&& self.prepass_material_fragment_shader.is_some());
&& material_properties
.shaders
.get(&PrepassFragmentShader.intern())
.is_some());
let fragment = fragment_required.then(|| {
// Use the fragment shader from the material
let frag_shader_handle = if mesh_key.contains(MeshPipelineKey::DEFERRED_PREPASS) {
match self.deferred_material_fragment_shader.clone() {
match material_properties
.shaders
.get(&DeferredFragmentShader.intern())
.cloned()
{
Some(frag_shader_handle) => frag_shader_handle,
None => self.default_prepass_shader.clone(),
}
} else {
match self.prepass_material_fragment_shader.clone() {
match material_properties
.shaders
.get(&PrepassFragmentShader.intern())
.cloned()
{
Some(frag_shader_handle) => frag_shader_handle,
None => self.default_prepass_shader.clone(),
}
@ -607,13 +589,13 @@ impl PrepassPipelineInternal {
// Use the vertex shader from the material if present
let vert_shader_handle = if mesh_key.contains(MeshPipelineKey::DEFERRED_PREPASS) {
if let Some(handle) = &self.deferred_material_vertex_shader {
handle.clone()
if let Some(handle) = material_properties.get_shader(DeferredVertexShader) {
handle
} else {
self.default_prepass_shader.clone()
}
} else if let Some(handle) = &self.prepass_material_vertex_shader {
handle.clone()
} else if let Some(handle) = material_properties.get_shader(PrepassVertexShader) {
handle
} else {
self.default_prepass_shader.clone()
};
@ -736,7 +718,7 @@ pub struct PrepassViewBindGroup {
impl FromWorld for PrepassViewBindGroup {
fn from_world(world: &mut World) -> Self {
let pipeline = world.resource::<PrepassPipeline<StandardMaterial>>();
let pipeline = world.resource::<PrepassPipeline>();
let render_device = world.resource::<RenderDevice>();
let empty_bind_group = render_device.create_bind_group(
@ -752,9 +734,9 @@ impl FromWorld for PrepassViewBindGroup {
}
}
pub fn prepare_prepass_view_bind_group<M: Material>(
pub fn prepare_prepass_view_bind_group(
render_device: Res<RenderDevice>,
prepass_pipeline: Res<PrepassPipeline<M>>,
prepass_pipeline: Res<PrepassPipeline>,
view_uniforms: Res<ViewUniforms>,
globals_buffer: Res<GlobalsBuffer>,
previous_view_uniforms: Res<PreviousViewUniforms>,
@ -792,40 +774,20 @@ pub fn prepare_prepass_view_bind_group<M: Material>(
}
/// Stores the [`SpecializedPrepassMaterialViewPipelineCache`] for each view.
#[derive(Resource, Deref, DerefMut)]
pub struct SpecializedPrepassMaterialPipelineCache<M> {
#[derive(Resource, Deref, DerefMut, Default)]
pub struct SpecializedPrepassMaterialPipelineCache {
// view_entity -> view pipeline cache
#[deref]
map: HashMap<RetainedViewEntity, SpecializedPrepassMaterialViewPipelineCache<M>>,
marker: PhantomData<M>,
map: HashMap<RetainedViewEntity, SpecializedPrepassMaterialViewPipelineCache>,
}
/// Stores the cached render pipeline ID for each entity in a single view, as
/// well as the last time it was changed.
#[derive(Deref, DerefMut)]
pub struct SpecializedPrepassMaterialViewPipelineCache<M> {
#[derive(Deref, DerefMut, Default)]
pub struct SpecializedPrepassMaterialViewPipelineCache {
// material entity -> (tick, pipeline_id)
#[deref]
map: MainEntityHashMap<(Tick, CachedRenderPipelineId)>,
marker: PhantomData<M>,
}
impl<M> Default for SpecializedPrepassMaterialPipelineCache<M> {
fn default() -> Self {
Self {
map: HashMap::default(),
marker: PhantomData,
}
}
}
impl<M> Default for SpecializedPrepassMaterialViewPipelineCache<M> {
fn default() -> Self {
Self {
map: HashMap::default(),
marker: PhantomData,
}
}
}
#[derive(Resource, Deref, DerefMut, Default, Clone)]
@ -870,14 +832,13 @@ pub fn check_prepass_views_need_specialization(
}
}
pub fn specialize_prepass_material_meshes<M>(
pub fn specialize_prepass_material_meshes(
render_meshes: Res<RenderAssets<RenderMesh>>,
render_materials: Res<RenderAssets<PreparedMaterial<M>>>,
render_materials: Res<ErasedRenderAssets<PreparedMaterial>>,
render_mesh_instances: Res<RenderMeshInstances>,
render_material_instances: Res<RenderMaterialInstances>,
render_lightmaps: Res<RenderLightmaps>,
render_visibility_ranges: Res<RenderVisibilityRanges>,
material_bind_group_allocator: Res<MaterialBindGroupAllocator<M>>,
view_key_cache: Res<ViewKeyPrepassCache>,
views: Query<(
&ExtractedView,
@ -906,18 +867,15 @@ pub fn specialize_prepass_material_meshes<M>(
view_specialization_ticks,
entity_specialization_ticks,
): (
ResMut<SpecializedPrepassMaterialPipelineCache<M>>,
ResMut<SpecializedPrepassMaterialPipelineCache>,
SystemChangeTick,
Res<PrepassPipeline<M>>,
ResMut<SpecializedMeshPipelines<PrepassPipeline<M>>>,
Res<PrepassPipeline>,
ResMut<SpecializedMeshPipelines<PrepassPipelineSpecializer>>,
Res<PipelineCache>,
Res<ViewPrepassSpecializationTicks>,
Res<EntitySpecializationTicks<M>>,
Res<EntitySpecializationTicks>,
),
) where
M: Material,
M::Data: PartialEq + Eq + Hash + Clone,
{
) {
for (extracted_view, visible_entities, msaa, motion_vector_prepass, deferred_prepass) in &views
{
if !opaque_deferred_render_phases.contains_key(&extracted_view.retained_view_entity)
@ -944,9 +902,6 @@ pub fn specialize_prepass_material_meshes<M>(
else {
continue;
};
let Ok(material_asset_id) = material_instance.asset_id.try_typed::<M>() else {
continue;
};
let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*visible_entity)
else {
continue;
@ -962,13 +917,7 @@ pub fn specialize_prepass_material_meshes<M>(
if !needs_specialization {
continue;
}
let Some(material) = render_materials.get(material_asset_id) else {
continue;
};
let Some(material_bind_group) =
material_bind_group_allocator.get(material.binding.group)
else {
warn!("Couldn't get bind group for material");
let Some(material) = render_materials.get(material_instance.asset_id) else {
continue;
};
let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else {
@ -1045,15 +994,19 @@ pub fn specialize_prepass_material_meshes<M>(
}
}
let erased_key = ErasedMaterialPipelineKey {
mesh_key,
material_key: material.properties.material_key.clone(),
type_id: material_instance.asset_id.type_id(),
};
let prepass_pipeline_specializer = PrepassPipelineSpecializer {
pipeline: prepass_pipeline.clone(),
properties: material.properties.clone(),
};
let pipeline_id = pipelines.specialize(
&pipeline_cache,
&prepass_pipeline,
MaterialPipelineKey {
mesh_key,
bind_group_data: material_bind_group
.get_extra_data(material.binding.slot)
.clone(),
},
&prepass_pipeline_specializer,
erased_key,
&mesh.layout,
);
let pipeline_id = match pipeline_id {
@ -1070,9 +1023,9 @@ pub fn specialize_prepass_material_meshes<M>(
}
}
pub fn queue_prepass_material_meshes<M: Material>(
pub fn queue_prepass_material_meshes(
render_mesh_instances: Res<RenderMeshInstances>,
render_materials: Res<RenderAssets<PreparedMaterial<M>>>,
render_materials: Res<ErasedRenderAssets<PreparedMaterial>>,
render_material_instances: Res<RenderMaterialInstances>,
mesh_allocator: Res<MeshAllocator>,
gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
@ -1081,10 +1034,8 @@ pub fn queue_prepass_material_meshes<M: Material>(
mut opaque_deferred_render_phases: ResMut<ViewBinnedRenderPhases<Opaque3dDeferred>>,
mut alpha_mask_deferred_render_phases: ResMut<ViewBinnedRenderPhases<AlphaMask3dDeferred>>,
views: Query<(&ExtractedView, &RenderVisibleEntities)>,
specialized_material_pipeline_cache: Res<SpecializedPrepassMaterialPipelineCache<M>>,
) where
M::Data: PartialEq + Eq + Hash + Clone,
{
specialized_material_pipeline_cache: Res<SpecializedPrepassMaterialPipelineCache>,
) {
for (extracted_view, visible_entities) in &views {
let (
mut opaque_phase,
@ -1137,14 +1088,11 @@ pub fn queue_prepass_material_meshes<M: Material>(
else {
continue;
};
let Ok(material_asset_id) = material_instance.asset_id.try_typed::<M>() else {
continue;
};
let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*visible_entity)
else {
continue;
};
let Some(material) = render_materials.get(material_asset_id) else {
let Some(material) = render_materials.get(material_instance.asset_id) else {
continue;
};
let (vertex_slab, index_slab) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id);
@ -1162,7 +1110,7 @@ pub fn queue_prepass_material_meshes<M: Material>(
OpaqueNoLightmap3dBatchSetKey {
draw_function: material
.properties
.deferred_draw_function_id
.get_draw_function(DeferredDrawFunction)
.unwrap(),
pipeline: *pipeline_id,
material_bind_group_index: Some(material.binding.group.0),
@ -1187,7 +1135,7 @@ pub fn queue_prepass_material_meshes<M: Material>(
OpaqueNoLightmap3dBatchSetKey {
draw_function: material
.properties
.prepass_draw_function_id
.get_draw_function(PrepassDrawFunction)
.unwrap(),
pipeline: *pipeline_id,
material_bind_group_index: Some(material.binding.group.0),
@ -1212,7 +1160,10 @@ pub fn queue_prepass_material_meshes<M: Material>(
let (vertex_slab, index_slab) =
mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id);
let batch_set_key = OpaqueNoLightmap3dBatchSetKey {
draw_function: material.properties.deferred_draw_function_id.unwrap(),
draw_function: material
.properties
.get_draw_function(DeferredDrawFunction)
.unwrap(),
pipeline: *pipeline_id,
material_bind_group_index: Some(material.binding.group.0),
vertex_slab: vertex_slab.unwrap_or_default(),
@ -1236,7 +1187,10 @@ pub fn queue_prepass_material_meshes<M: Material>(
let (vertex_slab, index_slab) =
mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id);
let batch_set_key = OpaqueNoLightmap3dBatchSetKey {
draw_function: material.properties.prepass_draw_function_id.unwrap(),
draw_function: material
.properties
.get_draw_function(PrepassDrawFunction)
.unwrap(),
pipeline: *pipeline_id,
material_bind_group_index: Some(material.binding.group.0),
vertex_slab: vertex_slab.unwrap_or_default(),
@ -1331,11 +1285,11 @@ impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetPrepassViewEmptyBindG
}
}
pub type DrawPrepass<M> = (
pub type DrawPrepass = (
SetItemPipeline,
SetPrepassViewBindGroup<0>,
SetPrepassViewEmptyBindGroup<1>,
SetMeshBindGroup<2>,
SetMaterialBindGroup<M, 3>,
SetMaterialBindGroup<3>,
DrawMesh,
);

View File

@ -1,5 +1,4 @@
use self::assign::ClusterableObjectType;
use crate::material_bind_groups::MaterialBindGroupAllocator;
use crate::*;
use bevy_asset::UntypedAssetId;
use bevy_color::ColorToComponents;
@ -15,6 +14,7 @@ use bevy_ecs::{
use bevy_math::{ops, Mat4, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles};
use bevy_platform::collections::{HashMap, HashSet};
use bevy_platform::hash::FixedHasher;
use bevy_render::erased_render_asset::ErasedRenderAssets;
use bevy_render::experimental::occlusion_culling::{
OcclusionCulling, OcclusionCullingSubview, OcclusionCullingSubviewEntities,
};
@ -44,7 +44,7 @@ use bevy_render::{
};
use bevy_transform::{components::GlobalTransform, prelude::Transform};
use bevy_utils::default;
use core::{hash::Hash, marker::PhantomData, ops::Range};
use core::{hash::Hash, ops::Range};
#[cfg(feature = "trace")]
use tracing::info_span;
use tracing::{error, warn};
@ -1717,37 +1717,17 @@ pub struct LightKeyCache(HashMap<RetainedViewEntity, MeshPipelineKey>);
#[derive(Resource, Deref, DerefMut, Default, Debug, Clone)]
pub struct LightSpecializationTicks(HashMap<RetainedViewEntity, Tick>);
#[derive(Resource, Deref, DerefMut)]
pub struct SpecializedShadowMaterialPipelineCache<M> {
#[derive(Resource, Deref, DerefMut, Default)]
pub struct SpecializedShadowMaterialPipelineCache {
// view light entity -> view pipeline cache
#[deref]
map: HashMap<RetainedViewEntity, SpecializedShadowMaterialViewPipelineCache<M>>,
marker: PhantomData<M>,
map: HashMap<RetainedViewEntity, SpecializedShadowMaterialViewPipelineCache>,
}
#[derive(Deref, DerefMut)]
pub struct SpecializedShadowMaterialViewPipelineCache<M> {
#[derive(Deref, DerefMut, Default)]
pub struct SpecializedShadowMaterialViewPipelineCache {
#[deref]
map: MainEntityHashMap<(Tick, CachedRenderPipelineId)>,
marker: PhantomData<M>,
}
impl<M> Default for SpecializedShadowMaterialPipelineCache<M> {
fn default() -> Self {
Self {
map: HashMap::default(),
marker: PhantomData,
}
}
}
impl<M> Default for SpecializedShadowMaterialViewPipelineCache<M> {
fn default() -> Self {
Self {
map: MainEntityHashMap::default(),
marker: PhantomData,
}
}
}
pub fn check_views_lights_need_specialization(
@ -1789,23 +1769,16 @@ pub fn check_views_lights_need_specialization(
}
}
pub fn specialize_shadows<M: Material>(
prepass_pipeline: Res<PrepassPipeline<M>>,
(
render_meshes,
render_mesh_instances,
render_materials,
render_material_instances,
material_bind_group_allocator,
): (
pub fn specialize_shadows(
prepass_pipeline: Res<PrepassPipeline>,
(render_meshes, render_mesh_instances, render_materials, render_material_instances): (
Res<RenderAssets<RenderMesh>>,
Res<RenderMeshInstances>,
Res<RenderAssets<PreparedMaterial<M>>>,
Res<ErasedRenderAssets<PreparedMaterial>>,
Res<RenderMaterialInstances>,
Res<MaterialBindGroupAllocator<M>>,
),
shadow_render_phases: Res<ViewBinnedRenderPhases<Shadow>>,
mut pipelines: ResMut<SpecializedMeshPipelines<PrepassPipeline<M>>>,
mut pipelines: ResMut<SpecializedMeshPipelines<PrepassPipelineSpecializer>>,
pipeline_cache: Res<PipelineCache>,
render_lightmaps: Res<RenderLightmaps>,
view_lights: Query<(Entity, &ViewLightEntities), With<ExtractedView>>,
@ -1817,13 +1790,11 @@ pub fn specialize_shadows<M: Material>(
>,
spot_light_entities: Query<&RenderVisibleMeshEntities, With<ExtractedPointLight>>,
light_key_cache: Res<LightKeyCache>,
mut specialized_material_pipeline_cache: ResMut<SpecializedShadowMaterialPipelineCache<M>>,
mut specialized_material_pipeline_cache: ResMut<SpecializedShadowMaterialPipelineCache>,
light_specialization_ticks: Res<LightSpecializationTicks>,
entity_specialization_ticks: Res<EntitySpecializationTicks<M>>,
entity_specialization_ticks: Res<EntitySpecializationTicks>,
ticks: SystemChangeTick,
) where
M::Data: PartialEq + Eq + Hash + Clone,
{
) {
// Record the retained IDs of all shadow views so that we can expire old
// pipeline IDs.
let mut all_shadow_views: HashSet<RetainedViewEntity, FixedHasher> = HashSet::default();
@ -1881,14 +1852,12 @@ pub fn specialize_shadows<M: Material>(
.or_default();
for (_, visible_entity) in visible_entities.iter().copied() {
let Some(material_instances) =
let Some(material_instance) =
render_material_instances.instances.get(&visible_entity)
else {
continue;
};
let Ok(material_asset_id) = material_instances.asset_id.try_typed::<M>() else {
continue;
};
let Some(mesh_instance) =
render_mesh_instances.render_mesh_queue_data(visible_entity)
else {
@ -1905,7 +1874,7 @@ pub fn specialize_shadows<M: Material>(
if !needs_specialization {
continue;
}
let Some(material) = render_materials.get(material_asset_id) else {
let Some(material) = render_materials.get(material_instance.asset_id) else {
continue;
};
if !mesh_instance
@ -1914,11 +1883,6 @@ pub fn specialize_shadows<M: Material>(
{
continue;
}
let Some(material_bind_group) =
material_bind_group_allocator.get(material.binding.group)
else {
continue;
};
let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else {
continue;
};
@ -1946,18 +1910,21 @@ pub fn specialize_shadows<M: Material>(
| AlphaMode::AlphaToCoverage => MeshPipelineKey::MAY_DISCARD,
_ => MeshPipelineKey::NONE,
};
let erased_key = ErasedMaterialPipelineKey {
mesh_key,
material_key: material.properties.material_key.clone(),
type_id: material_instance.asset_id.type_id(),
};
let material_pipeline_specializer = PrepassPipelineSpecializer {
pipeline: prepass_pipeline.clone(),
properties: material.properties.clone(),
};
let pipeline_id = pipelines.specialize(
&pipeline_cache,
&prepass_pipeline,
MaterialPipelineKey {
mesh_key,
bind_group_data: material_bind_group
.get_extra_data(material.binding.slot)
.clone(),
},
&material_pipeline_specializer,
erased_key,
&mesh.layout,
);
let pipeline_id = match pipeline_id {
Ok(id) => id,
Err(err) => {
@ -1979,10 +1946,9 @@ pub fn specialize_shadows<M: Material>(
/// For each shadow cascade, iterates over all the meshes "visible" from it and
/// adds them to [`BinnedRenderPhase`]s or [`SortedRenderPhase`]s as
/// appropriate.
pub fn queue_shadows<M: Material>(
shadow_draw_functions: Res<DrawFunctions<Shadow>>,
pub fn queue_shadows(
render_mesh_instances: Res<RenderMeshInstances>,
render_materials: Res<RenderAssets<PreparedMaterial<M>>>,
render_materials: Res<ErasedRenderAssets<PreparedMaterial>>,
render_material_instances: Res<RenderMaterialInstances>,
mut shadow_render_phases: ResMut<ViewBinnedRenderPhases<Shadow>>,
gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
@ -1995,11 +1961,8 @@ pub fn queue_shadows<M: Material>(
With<ExtractedDirectionalLight>,
>,
spot_light_entities: Query<&RenderVisibleMeshEntities, With<ExtractedPointLight>>,
specialized_material_pipeline_cache: Res<SpecializedShadowMaterialPipelineCache<M>>,
) where
M::Data: PartialEq + Eq + Hash + Clone,
{
let draw_shadow_mesh = shadow_draw_functions.read().id::<DrawPrepass<M>>();
specialized_material_pipeline_cache: Res<SpecializedShadowMaterialPipelineCache>,
) {
for (entity, view_lights) in &view_lights {
for view_light_entity in view_lights.lights.iter().copied() {
let Ok((light_entity, extracted_view_light)) =
@ -2070,10 +2033,12 @@ pub fn queue_shadows<M: Material>(
else {
continue;
};
let Ok(material_asset_id) = material_instance.asset_id.try_typed::<M>() else {
let Some(material) = render_materials.get(material_instance.asset_id) else {
continue;
};
let Some(material) = render_materials.get(material_asset_id) else {
let Some(draw_function) =
material.properties.get_draw_function(ShadowsDrawFunction)
else {
continue;
};
@ -2082,7 +2047,7 @@ pub fn queue_shadows<M: Material>(
let batch_set_key = ShadowBatchSetKey {
pipeline: *pipeline_id,
draw_function: draw_shadow_mesh,
draw_function,
material_bind_group_index: Some(material.binding.group.0),
vertex_slab: vertex_slab.unwrap_or_default(),
index_slab,

View File

@ -2039,7 +2039,7 @@ impl GetFullBatchData for MeshPipeline {
}
bitflags::bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[repr(transparent)]
// NOTE: Apparently quadro drivers support up to 64x MSAA.
/// MSAA uses the highest 3 bits for the MSAA log2(sample count) to support up to 128x MSAA.

View File

@ -1061,17 +1061,21 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result<TokenStream> {
render_device: &#render_path::renderer::RenderDevice,
(images, fallback_image, storage_buffers): &mut #ecs_path::system::SystemParamItem<'_, '_, Self::Param>,
force_no_bindless: bool,
) -> Result<#render_path::render_resource::UnpreparedBindGroup<Self::Data>, #render_path::render_resource::AsBindGroupError> {
) -> Result<#render_path::render_resource::UnpreparedBindGroup, #render_path::render_resource::AsBindGroupError> {
#uniform_binding_type_declarations
let bindings = #render_path::render_resource::BindingResources(vec![#(#binding_impls,)*]);
Ok(#render_path::render_resource::UnpreparedBindGroup {
bindings,
data: #get_prepared_data,
})
}
#[allow(clippy::unused_unit)]
fn bind_group_data(&self) -> Self::Data {
#get_prepared_data
}
fn bind_group_layout_entries(
render_device: &#render_path::renderer::RenderDevice,
force_no_bindless: bool

View File

@ -101,3 +101,29 @@ pub fn derive_render_sub_graph(input: TokenStream) -> TokenStream {
.push(format_ident!("RenderSubGraph").into());
derive_label(input, "RenderSubGraph", &trait_path)
}
#[proc_macro_derive(ShaderLabel)]
pub fn derive_shader_label(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let mut trait_path = bevy_render_path();
trait_path
.segments
.push(format_ident!("render_phase").into());
trait_path
.segments
.push(format_ident!("ShaderLabel").into());
derive_label(input, "ShaderLabel", &trait_path)
}
#[proc_macro_derive(DrawFunctionLabel)]
pub fn derive_draw_function_label(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let mut trait_path = bevy_render_path();
trait_path
.segments
.push(format_ident!("render_phase").into());
trait_path
.segments
.push(format_ident!("DrawFunctionLabel").into());
derive_label(input, "DrawFunctionLabel", &trait_path)
}

View File

@ -0,0 +1,431 @@
use crate::{
render_resource::AsBindGroupError, ExtractSchedule, MainWorld, Render, RenderApp,
RenderSystems, Res,
};
use bevy_app::{App, Plugin, SubApp};
pub use bevy_asset::RenderAssetUsages;
use bevy_asset::{Asset, AssetEvent, AssetId, Assets, UntypedAssetId};
use bevy_ecs::{
prelude::{Commands, EventReader, IntoScheduleConfigs, ResMut, Resource},
schedule::{ScheduleConfigs, SystemSet},
system::{ScheduleSystem, StaticSystemParam, SystemParam, SystemParamItem, SystemState},
world::{FromWorld, Mut},
};
use bevy_platform::collections::{HashMap, HashSet};
use bevy_render::render_asset::RenderAssetBytesPerFrameLimiter;
use core::marker::PhantomData;
use thiserror::Error;
use tracing::{debug, error};
#[derive(Debug, Error)]
pub enum PrepareAssetError<E: Send + Sync + 'static> {
#[error("Failed to prepare asset")]
RetryNextUpdate(E),
#[error("Failed to build bind group: {0}")]
AsBindGroupError(AsBindGroupError),
}
/// The system set during which we extract modified assets to the render world.
#[derive(SystemSet, Clone, PartialEq, Eq, Debug, Hash)]
pub struct AssetExtractionSystems;
/// Deprecated alias for [`AssetExtractionSystems`].
#[deprecated(since = "0.17.0", note = "Renamed to `AssetExtractionSystems`.")]
pub type ExtractAssetsSet = AssetExtractionSystems;
/// Describes how an asset gets extracted and prepared for rendering.
///
/// In the [`ExtractSchedule`] step the [`ErasedRenderAsset::SourceAsset`] is transferred
/// from the "main world" into the "render world".
///
/// After that in the [`RenderSystems::PrepareAssets`] step the extracted asset
/// is transformed into its GPU-representation of type [`ErasedRenderAsset`].
pub trait ErasedRenderAsset: Send + Sync + 'static {
/// The representation of the asset in the "main world".
type SourceAsset: Asset + Clone;
/// The target representation of the asset in the "render world".
type ErasedAsset: Send + Sync + 'static + Sized;
/// Specifies all ECS data required by [`ErasedRenderAsset::prepare_asset`].
///
/// For convenience use the [`lifetimeless`](bevy_ecs::system::lifetimeless) [`SystemParam`].
type Param: SystemParam;
/// Whether or not to unload the asset after extracting it to the render world.
#[inline]
fn asset_usage(_source_asset: &Self::SourceAsset) -> RenderAssetUsages {
RenderAssetUsages::default()
}
/// Size of the data the asset will upload to the gpu. Specifying a return value
/// will allow the asset to be throttled via [`RenderAssetBytesPerFrameLimiter`].
#[inline]
#[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."
)]
fn byte_len(erased_asset: &Self::SourceAsset) -> Option<usize> {
None
}
/// Prepares the [`ErasedRenderAsset::SourceAsset`] for the GPU by transforming it into a [`ErasedRenderAsset`].
///
/// ECS data may be accessed via `param`.
fn prepare_asset(
source_asset: Self::SourceAsset,
asset_id: AssetId<Self::SourceAsset>,
param: &mut SystemParamItem<Self::Param>,
) -> Result<Self::ErasedAsset, PrepareAssetError<Self::SourceAsset>>;
/// Called whenever the [`ErasedRenderAsset::SourceAsset`] has been removed.
///
/// You can implement this method if you need to access ECS data (via
/// `_param`) in order to perform cleanup tasks when the asset is removed.
///
/// The default implementation does nothing.
fn unload_asset(
_source_asset: AssetId<Self::SourceAsset>,
_param: &mut SystemParamItem<Self::Param>,
) {
}
}
/// This plugin extracts the changed assets from the "app world" into the "render world"
/// and prepares them for the GPU. They can then be accessed from the [`ErasedRenderAssets`] resource.
///
/// Therefore it sets up the [`ExtractSchedule`] and
/// [`RenderSystems::PrepareAssets`] steps for the specified [`ErasedRenderAsset`].
///
/// The `AFTER` generic parameter can be used to specify that `A::prepare_asset` should not be run until
/// `prepare_assets::<AFTER>` has completed. This allows the `prepare_asset` function to depend on another
/// prepared [`ErasedRenderAsset`], for example `Mesh::prepare_asset` relies on `ErasedRenderAssets::<GpuImage>` for morph
/// targets, so the plugin is created as `ErasedRenderAssetPlugin::<RenderMesh, GpuImage>::default()`.
pub struct ErasedRenderAssetPlugin<
A: ErasedRenderAsset,
AFTER: ErasedRenderAssetDependency + 'static = (),
> {
phantom: PhantomData<fn() -> (A, AFTER)>,
}
impl<A: ErasedRenderAsset, AFTER: ErasedRenderAssetDependency + 'static> Default
for ErasedRenderAssetPlugin<A, AFTER>
{
fn default() -> Self {
Self {
phantom: Default::default(),
}
}
}
impl<A: ErasedRenderAsset, AFTER: ErasedRenderAssetDependency + 'static> Plugin
for ErasedRenderAssetPlugin<A, AFTER>
{
fn build(&self, app: &mut App) {
app.init_resource::<CachedExtractErasedRenderAssetSystemState<A>>();
}
fn finish(&self, app: &mut App) {
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
render_app
.init_resource::<ExtractedAssets<A>>()
.init_resource::<ErasedRenderAssets<A::ErasedAsset>>()
.init_resource::<PrepareNextFrameAssets<A>>()
.add_systems(
ExtractSchedule,
extract_erased_render_asset::<A>.in_set(AssetExtractionSystems),
);
AFTER::register_system(
render_app,
prepare_erased_assets::<A>.in_set(RenderSystems::PrepareAssets),
);
}
}
}
// helper to allow specifying dependencies between render assets
pub trait ErasedRenderAssetDependency {
fn register_system(render_app: &mut SubApp, system: ScheduleConfigs<ScheduleSystem>);
}
impl ErasedRenderAssetDependency for () {
fn register_system(render_app: &mut SubApp, system: ScheduleConfigs<ScheduleSystem>) {
render_app.add_systems(Render, system);
}
}
impl<A: ErasedRenderAsset> ErasedRenderAssetDependency for A {
fn register_system(render_app: &mut SubApp, system: ScheduleConfigs<ScheduleSystem>) {
render_app.add_systems(Render, system.after(prepare_erased_assets::<A>));
}
}
/// Temporarily stores the extracted and removed assets of the current frame.
#[derive(Resource)]
pub struct ExtractedAssets<A: ErasedRenderAsset> {
/// The assets extracted this frame.
///
/// These are assets that were either added or modified this frame.
pub extracted: Vec<(AssetId<A::SourceAsset>, A::SourceAsset)>,
/// IDs of the assets that were removed this frame.
///
/// These assets will not be present in [`ExtractedAssets::extracted`].
pub removed: HashSet<AssetId<A::SourceAsset>>,
/// IDs of the assets that were modified this frame.
pub modified: HashSet<AssetId<A::SourceAsset>>,
/// IDs of the assets that were added this frame.
pub added: HashSet<AssetId<A::SourceAsset>>,
}
impl<A: ErasedRenderAsset> Default for ExtractedAssets<A> {
fn default() -> Self {
Self {
extracted: Default::default(),
removed: Default::default(),
modified: Default::default(),
added: Default::default(),
}
}
}
/// Stores all GPU representations ([`ErasedRenderAsset`])
/// of [`ErasedRenderAsset::SourceAsset`] as long as they exist.
#[derive(Resource)]
pub struct ErasedRenderAssets<ERA>(HashMap<UntypedAssetId, ERA>);
impl<ERA> Default for ErasedRenderAssets<ERA> {
fn default() -> Self {
Self(Default::default())
}
}
impl<ERA> ErasedRenderAssets<ERA> {
pub fn get(&self, id: impl Into<UntypedAssetId>) -> Option<&ERA> {
self.0.get(&id.into())
}
pub fn get_mut(&mut self, id: impl Into<UntypedAssetId>) -> Option<&mut ERA> {
self.0.get_mut(&id.into())
}
pub fn insert(&mut self, id: impl Into<UntypedAssetId>, value: ERA) -> Option<ERA> {
self.0.insert(id.into(), value)
}
pub fn remove(&mut self, id: impl Into<UntypedAssetId>) -> Option<ERA> {
self.0.remove(&id.into())
}
pub fn iter(&self) -> impl Iterator<Item = (UntypedAssetId, &ERA)> {
self.0.iter().map(|(k, v)| (*k, v))
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = (UntypedAssetId, &mut ERA)> {
self.0.iter_mut().map(|(k, v)| (*k, v))
}
}
#[derive(Resource)]
struct CachedExtractErasedRenderAssetSystemState<A: ErasedRenderAsset> {
state: SystemState<(
EventReader<'static, 'static, AssetEvent<A::SourceAsset>>,
ResMut<'static, Assets<A::SourceAsset>>,
)>,
}
impl<A: ErasedRenderAsset> FromWorld for CachedExtractErasedRenderAssetSystemState<A> {
fn from_world(world: &mut bevy_ecs::world::World) -> Self {
Self {
state: SystemState::new(world),
}
}
}
/// This system extracts all created or modified assets of the corresponding [`ErasedRenderAsset::SourceAsset`] type
/// into the "render world".
pub(crate) fn extract_erased_render_asset<A: ErasedRenderAsset>(
mut commands: Commands,
mut main_world: ResMut<MainWorld>,
) {
main_world.resource_scope(
|world, mut cached_state: Mut<CachedExtractErasedRenderAssetSystemState<A>>| {
let (mut events, mut assets) = cached_state.state.get_mut(world);
let mut needs_extracting = <HashSet<_>>::default();
let mut removed = <HashSet<_>>::default();
let mut modified = <HashSet<_>>::default();
for event in events.read() {
#[expect(
clippy::match_same_arms,
reason = "LoadedWithDependencies is marked as a TODO, so it's likely this will no longer lint soon."
)]
match event {
AssetEvent::Added { id } => {
needs_extracting.insert(*id);
}
AssetEvent::Modified { id } => {
needs_extracting.insert(*id);
modified.insert(*id);
}
AssetEvent::Removed { .. } => {
// We don't care that the asset was removed from Assets<T> in the main world.
// An asset is only removed from ErasedRenderAssets<T> when its last handle is dropped (AssetEvent::Unused).
}
AssetEvent::Unused { id } => {
needs_extracting.remove(id);
modified.remove(id);
removed.insert(*id);
}
AssetEvent::LoadedWithDependencies { .. } => {
// TODO: handle this
}
}
}
let mut extracted_assets = Vec::new();
let mut added = <HashSet<_>>::default();
for id in needs_extracting.drain() {
if let Some(asset) = assets.get(id) {
let asset_usage = A::asset_usage(asset);
if asset_usage.contains(RenderAssetUsages::RENDER_WORLD) {
if asset_usage == RenderAssetUsages::RENDER_WORLD {
if let Some(asset) = assets.remove(id) {
extracted_assets.push((id, asset));
added.insert(id);
}
} else {
extracted_assets.push((id, asset.clone()));
added.insert(id);
}
}
}
}
commands.insert_resource(ExtractedAssets::<A> {
extracted: extracted_assets,
removed,
modified,
added,
});
cached_state.state.apply(world);
},
);
}
// TODO: consider storing inside system?
/// All assets that should be prepared next frame.
#[derive(Resource)]
pub struct PrepareNextFrameAssets<A: ErasedRenderAsset> {
assets: Vec<(AssetId<A::SourceAsset>, A::SourceAsset)>,
}
impl<A: ErasedRenderAsset> Default for PrepareNextFrameAssets<A> {
fn default() -> Self {
Self {
assets: Default::default(),
}
}
}
/// This system prepares all assets of the corresponding [`ErasedRenderAsset::SourceAsset`] type
/// which where extracted this frame for the GPU.
pub fn prepare_erased_assets<A: ErasedRenderAsset>(
mut extracted_assets: ResMut<ExtractedAssets<A>>,
mut render_assets: ResMut<ErasedRenderAssets<A::ErasedAsset>>,
mut prepare_next_frame: ResMut<PrepareNextFrameAssets<A>>,
param: StaticSystemParam<<A as ErasedRenderAsset>::Param>,
bpf: Res<RenderAssetBytesPerFrameLimiter>,
) {
let mut wrote_asset_count = 0;
let mut param = param.into_inner();
let queued_assets = core::mem::take(&mut prepare_next_frame.assets);
for (id, extracted_asset) in queued_assets {
if extracted_assets.removed.contains(&id) || extracted_assets.added.contains(&id) {
// skip previous frame's assets that have been removed or updated
continue;
}
let write_bytes = if let Some(size) = A::byte_len(&extracted_asset) {
// we could check if available bytes > byte_len here, but we want to make some
// forward progress even if the asset is larger than the max bytes per frame.
// this way we always write at least one (sized) asset per frame.
// in future we could also consider partial asset uploads.
if bpf.exhausted() {
prepare_next_frame.assets.push((id, extracted_asset));
continue;
}
size
} else {
0
};
match A::prepare_asset(extracted_asset, id, &mut param) {
Ok(prepared_asset) => {
render_assets.insert(id, prepared_asset);
bpf.write_bytes(write_bytes);
wrote_asset_count += 1;
}
Err(PrepareAssetError::RetryNextUpdate(extracted_asset)) => {
prepare_next_frame.assets.push((id, extracted_asset));
}
Err(PrepareAssetError::AsBindGroupError(e)) => {
error!(
"{} Bind group construction failed: {e}",
core::any::type_name::<A>()
);
}
}
}
for removed in extracted_assets.removed.drain() {
render_assets.remove(removed);
A::unload_asset(removed, &mut param);
}
for (id, extracted_asset) in extracted_assets.extracted.drain(..) {
// we remove previous here to ensure that if we are updating the asset then
// any users will not see the old asset after a new asset is extracted,
// even if the new asset is not yet ready or we are out of bytes to write.
render_assets.remove(id);
let write_bytes = if let Some(size) = A::byte_len(&extracted_asset) {
if bpf.exhausted() {
prepare_next_frame.assets.push((id, extracted_asset));
continue;
}
size
} else {
0
};
match A::prepare_asset(extracted_asset, id, &mut param) {
Ok(prepared_asset) => {
render_assets.insert(id, prepared_asset);
bpf.write_bytes(write_bytes);
wrote_asset_count += 1;
}
Err(PrepareAssetError::RetryNextUpdate(extracted_asset)) => {
prepare_next_frame.assets.push((id, extracted_asset));
}
Err(PrepareAssetError::AsBindGroupError(e)) => {
error!(
"{} Bind group construction failed: {e}",
core::any::type_name::<A>()
);
}
}
}
if bpf.exhausted() && !prepare_next_frame.assets.is_empty() {
debug!(
"{} write budget exhausted with {} assets remaining (wrote {})",
core::any::type_name::<A>(),
prepare_next_frame.assets.len(),
wrote_asset_count
);
}
}

View File

@ -26,6 +26,7 @@ pub mod alpha;
pub mod batching;
pub mod camera;
pub mod diagnostic;
pub mod erased_render_asset;
pub mod experimental;
pub mod extract_component;
pub mod extract_instances;

View File

@ -499,14 +499,14 @@ impl RenderAssetBytesPerFrameLimiter {
}
/// Decreases the available bytes for the current frame.
fn write_bytes(&self, bytes: usize) {
pub(crate) fn write_bytes(&self, bytes: usize) {
if self.max_bytes.is_some() && bytes > 0 {
self.bytes_written.fetch_add(bytes, Ordering::Relaxed);
}
}
/// Returns `true` if there are no remaining bytes available for writing this frame.
fn exhausted(&self) -> bool {
pub(crate) fn exhausted(&self) -> bool {
if let Some(max_bytes) = self.max_bytes {
let bytes_written = self.bytes_written.load(Ordering::Relaxed);
bytes_written >= max_bytes

View File

@ -61,7 +61,9 @@ use crate::{
render_resource::{CachedRenderPipelineId, GpuArrayBufferIndex, PipelineCache},
Render, RenderApp, RenderSystems,
};
use bevy_ecs::intern::Interned;
use bevy_ecs::{
define_label,
prelude::*,
system::{lifetimeless::SRes, SystemParamItem},
};
@ -69,6 +71,33 @@ use core::{fmt::Debug, hash::Hash, iter, marker::PhantomData, ops::Range, slice:
use smallvec::SmallVec;
use tracing::warn;
pub use bevy_render_macros::ShaderLabel;
define_label!(
#[diagnostic::on_unimplemented(
note = "consider annotating `{Self}` with `#[derive(ShaderLabel)]`"
)]
/// Labels used to uniquely identify types of material shaders
ShaderLabel,
SHADER_LABEL_INTERNER
);
/// A shorthand for `Interned<dyn RenderSubGraph>`.
pub type InternedShaderLabel = Interned<dyn ShaderLabel>;
pub use bevy_render_macros::DrawFunctionLabel;
define_label!(
#[diagnostic::on_unimplemented(
note = "consider annotating `{Self}` with `#[derive(DrawFunctionLabel)]`"
)]
/// Labels used to uniquely identify types of material shaders
DrawFunctionLabel,
DRAW_FUNCTION_LABEL_INTERNER
);
pub type InternedDrawFunctionLabel = Interned<dyn DrawFunctionLabel>;
/// Stores the rendering instructions for a single phase that uses bins in all
/// views.
///

View File

@ -481,22 +481,27 @@ impl Deref for BindGroup {
/// is_shaded: bool,
/// }
///
/// #[derive(Copy, Clone, Hash, Eq, PartialEq)]
/// // Materials keys are intended to be small, cheap to hash, and
/// // uniquely identify a specific material permutation, which
/// // is why they are required to be `bytemuck::Pod` and `bytemuck::Zeroable`
/// // when using the `AsBindGroup` derive macro.
/// #[repr(C)]
/// #[derive(Copy, Clone, Hash, Eq, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
/// struct CoolMaterialKey {
/// is_shaded: bool,
/// is_shaded: u32,
/// }
///
/// impl From<&CoolMaterial> for CoolMaterialKey {
/// fn from(material: &CoolMaterial) -> CoolMaterialKey {
/// CoolMaterialKey {
/// is_shaded: material.is_shaded,
/// is_shaded: material.is_shaded as u32,
/// }
/// }
/// }
/// ```
pub trait AsBindGroup {
/// Data that will be stored alongside the "prepared" bind group.
type Data: Send + Sync;
type Data: bytemuck::Pod + bytemuck::Zeroable + Send + Sync;
type Param: SystemParam + 'static;
@ -531,8 +536,8 @@ pub trait AsBindGroup {
layout: &BindGroupLayout,
render_device: &RenderDevice,
param: &mut SystemParamItem<'_, '_, Self::Param>,
) -> Result<PreparedBindGroup<Self::Data>, AsBindGroupError> {
let UnpreparedBindGroup { bindings, data } =
) -> Result<PreparedBindGroup, AsBindGroupError> {
let UnpreparedBindGroup { bindings } =
Self::unprepared_bind_group(self, layout, render_device, param, false)?;
let entries = bindings
@ -548,10 +553,11 @@ pub trait AsBindGroup {
Ok(PreparedBindGroup {
bindings,
bind_group,
data,
})
}
fn bind_group_data(&self) -> Self::Data;
/// Returns a vec of (binding index, `OwnedBindingResource`).
///
/// In cases where `OwnedBindingResource` is not available (as for bindless
@ -569,7 +575,7 @@ pub trait AsBindGroup {
render_device: &RenderDevice,
param: &mut SystemParamItem<'_, '_, Self::Param>,
force_no_bindless: bool,
) -> Result<UnpreparedBindGroup<Self::Data>, AsBindGroupError>;
) -> Result<UnpreparedBindGroup, AsBindGroupError>;
/// Creates the bind group layout matching all bind groups returned by
/// [`AsBindGroup::as_bind_group`]
@ -613,16 +619,14 @@ pub enum AsBindGroupError {
}
/// A prepared bind group returned as a result of [`AsBindGroup::as_bind_group`].
pub struct PreparedBindGroup<T> {
pub struct PreparedBindGroup {
pub bindings: BindingResources,
pub bind_group: BindGroup,
pub data: T,
}
/// a map containing `OwnedBindingResource`s, keyed by the target binding index
pub struct UnpreparedBindGroup<T> {
pub struct UnpreparedBindGroup {
pub bindings: BindingResources,
pub data: T,
}
/// A pair of binding index and binding resource, used as part of

View File

@ -410,7 +410,7 @@ where
fn clone(&self) -> Self {
Self {
mesh_key: self.mesh_key,
bind_group_data: self.bind_group_data.clone(),
bind_group_data: self.bind_group_data,
}
}
}
@ -753,7 +753,7 @@ pub fn specialize_material2d_meshes<M: Material2d>(
&material2d_pipeline,
Material2dKey {
mesh_key,
bind_group_data: material_2d.key.clone(),
bind_group_data: material_2d.key,
},
&mesh.layout,
);
@ -969,6 +969,7 @@ impl<M: Material2d> RenderAsset for PreparedMaterial2d<M> {
): &mut SystemParamItem<Self::Param>,
_: Option<&Self>,
) -> Result<Self, PrepareAssetError<Self::SourceAsset>> {
let bind_group_data = material.bind_group_data();
match material.as_bind_group(&pipeline.material2d_layout, render_device, material_param) {
Ok(prepared) => {
let mut mesh_pipeline_key_bits = Mesh2dPipelineKey::empty();
@ -987,7 +988,7 @@ impl<M: Material2d> RenderAsset for PreparedMaterial2d<M> {
Ok(PreparedMaterial2d {
bindings: prepared.bindings,
bind_group: prepared.bind_group,
key: prepared.data,
key: bind_group_data,
properties: Material2dProperties {
depth_bias: material.depth_bias(),
alpha_mode: material.alpha_mode(),

View File

@ -583,11 +583,12 @@ impl<M: UiMaterial> RenderAsset for PreparedUiMaterial<M> {
(render_device, pipeline, material_param): &mut SystemParamItem<Self::Param>,
_: Option<&Self>,
) -> Result<Self, PrepareAssetError<Self::SourceAsset>> {
let bind_group_data = material.bind_group_data();
match material.as_bind_group(&pipeline.ui_layout, render_device, material_param) {
Ok(prepared) => Ok(PreparedUiMaterial {
bindings: prepared.bindings,
bind_group: prepared.bind_group,
key: prepared.data,
key: bind_group_data,
}),
Err(AsBindGroupError::RetryNextUpdate) => {
Err(PrepareAssetError::RetryNextUpdate(material))
@ -637,7 +638,7 @@ pub fn queue_ui_material_nodes<M: UiMaterial>(
&ui_material_pipeline,
UiMaterialKey {
hdr: view.hdr,
bind_group_data: material.key.clone(),
bind_group_data: material.key,
},
);
if transparent_phase.items.capacity() < extracted_uinodes.uinodes.len() {

View File

@ -141,7 +141,7 @@ where
fn clone(&self) -> Self {
Self {
hdr: self.hdr,
bind_group_data: self.bind_group_data.clone(),
bind_group_data: self.bind_group_data,
}
}
}

View File

@ -77,7 +77,7 @@ impl Material for LineMaterial {
}
fn specialize(
_pipeline: &MaterialPipeline<Self>,
_pipeline: &MaterialPipeline,
descriptor: &mut RenderPipelineDescriptor,
_layout: &MeshVertexBufferLayoutRef,
_key: MaterialPipelineKey<Self>,

View File

@ -0,0 +1,315 @@
//! A simple 3D scene with light shining over a cube sitting on a plane.
use bevy::{
asset::{AsAssetId, AssetEventSystems},
core_pipeline::core_3d::Opaque3d,
ecs::system::{
lifetimeless::{SRes, SResMut},
SystemChangeTick, SystemParamItem,
},
pbr::{
DrawMaterial, EntitiesNeedingSpecialization, EntitySpecializationTicks,
MaterialBindGroupAllocator, MaterialBindGroupAllocators, MaterialDrawFunction,
MaterialFragmentShader, MaterialProperties, PreparedMaterial, RenderMaterialBindings,
RenderMaterialInstance, RenderMaterialInstances, SpecializedMaterialPipelineCache,
},
platform::collections::hash_map::Entry,
prelude::*,
render::{
erased_render_asset::{ErasedRenderAsset, ErasedRenderAssetPlugin, PrepareAssetError},
render_asset::RenderAssets,
render_phase::DrawFunctions,
render_resource::{
binding_types::{sampler, texture_2d},
AsBindGroup, BindGroupLayout, BindGroupLayoutEntries, BindingResources,
OwnedBindingResource, Sampler, SamplerBindingType, SamplerDescriptor, ShaderStages,
TextureSampleType, TextureViewDimension, UnpreparedBindGroup,
},
renderer::RenderDevice,
sync_world::MainEntity,
texture::GpuImage,
view::ExtractedView,
Extract, RenderApp,
},
utils::Parallel,
};
use std::{any::TypeId, sync::Arc};
const SHADER_ASSET_PATH: &str = "shaders/manual_material.wgsl";
fn main() {
App::new()
.add_plugins((DefaultPlugins, ImageMaterialPlugin))
.add_systems(Startup, setup)
.run();
}
struct ImageMaterialPlugin;
impl Plugin for ImageMaterialPlugin {
fn build(&self, app: &mut App) {
app.init_asset::<ImageMaterial>()
.add_plugins(ErasedRenderAssetPlugin::<ImageMaterial>::default())
.add_systems(
PostUpdate,
check_entities_needing_specialization.after(AssetEventSystems),
)
.init_resource::<EntitiesNeedingSpecialization<ImageMaterial>>();
}
fn finish(&self, app: &mut App) {
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};
render_app.add_systems(
ExtractSchedule,
(
extract_image_materials,
extract_image_materials_needing_specialization,
),
);
render_app.world_mut().resource_scope(
|world: &mut World, mut bind_group_allocators: Mut<MaterialBindGroupAllocators>| {
world.resource_scope(|world: &mut World, render_device: Mut<RenderDevice>| {
let bind_group_layout = render_device.create_bind_group_layout(
"image_material_layout",
&BindGroupLayoutEntries::sequential(
ShaderStages::FRAGMENT,
(
texture_2d(TextureSampleType::Float { filterable: false }),
sampler(SamplerBindingType::NonFiltering),
),
),
);
let sampler = render_device.create_sampler(&SamplerDescriptor::default());
world.insert_resource(ImageMaterialBindGroupLayout(bind_group_layout.clone()));
world.insert_resource(ImageMaterialBindGroupSampler(sampler));
bind_group_allocators.insert(
TypeId::of::<ImageMaterial>(),
MaterialBindGroupAllocator::new(
&render_device,
None,
None,
bind_group_layout,
None,
),
);
});
},
);
}
}
#[derive(Resource)]
struct ImageMaterialBindGroupLayout(BindGroupLayout);
#[derive(Resource)]
struct ImageMaterialBindGroupSampler(Sampler);
#[derive(Component)]
struct ImageMaterial3d(Handle<ImageMaterial>);
impl AsAssetId for ImageMaterial3d {
type Asset = ImageMaterial;
fn as_asset_id(&self) -> AssetId<Self::Asset> {
self.0.id()
}
}
#[derive(Asset, TypePath, AsBindGroup, Debug, Clone)]
struct ImageMaterial {
image: Handle<Image>,
}
impl ErasedRenderAsset for ImageMaterial {
type SourceAsset = ImageMaterial;
type ErasedAsset = PreparedMaterial;
type Param = (
SRes<DrawFunctions<Opaque3d>>,
SRes<ImageMaterialBindGroupLayout>,
SRes<AssetServer>,
SResMut<MaterialBindGroupAllocators>,
SResMut<RenderMaterialBindings>,
SRes<RenderAssets<GpuImage>>,
SRes<ImageMaterialBindGroupSampler>,
);
fn prepare_asset(
source_asset: Self::SourceAsset,
asset_id: AssetId<Self::SourceAsset>,
(
opaque_draw_functions,
material_layout,
asset_server,
bind_group_allocators,
render_material_bindings,
gpu_images,
image_material_sampler,
): &mut SystemParamItem<Self::Param>,
) -> std::result::Result<Self::ErasedAsset, PrepareAssetError<Self::SourceAsset>> {
let material_layout = material_layout.0.clone();
let draw_function_id = opaque_draw_functions.read().id::<DrawMaterial>();
let bind_group_allocator = bind_group_allocators
.get_mut(&TypeId::of::<ImageMaterial>())
.unwrap();
let Some(image) = gpu_images.get(&source_asset.image) else {
return Err(PrepareAssetError::RetryNextUpdate(source_asset));
};
let unprepared = UnpreparedBindGroup {
bindings: BindingResources(vec![
(
0,
OwnedBindingResource::TextureView(
TextureViewDimension::D2,
image.texture_view.clone(),
),
),
(
1,
OwnedBindingResource::Sampler(
SamplerBindingType::NonFiltering,
image_material_sampler.0.clone(),
),
),
]),
};
let binding = match render_material_bindings.entry(asset_id.into()) {
Entry::Occupied(mut occupied_entry) => {
bind_group_allocator.free(*occupied_entry.get());
let new_binding =
bind_group_allocator.allocate_unprepared(unprepared, &material_layout);
*occupied_entry.get_mut() = new_binding;
new_binding
}
Entry::Vacant(vacant_entry) => *vacant_entry
.insert(bind_group_allocator.allocate_unprepared(unprepared, &material_layout)),
};
let mut properties = MaterialProperties {
material_layout: Some(material_layout),
..Default::default()
};
properties.add_draw_function(MaterialDrawFunction, draw_function_id);
properties.add_shader(MaterialFragmentShader, asset_server.load(SHADER_ASSET_PATH));
Ok(PreparedMaterial {
binding,
properties: Arc::new(properties),
})
}
}
/// set up a simple 3D scene
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ImageMaterial>>,
asset_server: Res<AssetServer>,
) {
// cube
commands.spawn((
Mesh3d(meshes.add(Cuboid::new(2.0, 2.0, 2.0))),
ImageMaterial3d(materials.add(ImageMaterial {
image: asset_server.load("branding/icon.png"),
})),
Transform::from_xyz(0.0, 0.5, 0.0),
));
// light
commands.spawn((
PointLight {
shadows_enabled: true,
..default()
},
Transform::from_xyz(4.0, 8.0, 4.0),
));
// camera
commands.spawn((
Camera3d::default(),
Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y),
));
}
fn extract_image_materials(
mut material_instances: ResMut<RenderMaterialInstances>,
changed_meshes_query: Extract<
Query<
(Entity, &ViewVisibility, &ImageMaterial3d),
Or<(Changed<ViewVisibility>, Changed<ImageMaterial3d>)>,
>,
>,
) {
let last_change_tick = material_instances.current_change_tick;
for (entity, view_visibility, material) in &changed_meshes_query {
if view_visibility.get() {
material_instances.instances.insert(
entity.into(),
RenderMaterialInstance {
asset_id: material.0.id().untyped(),
last_change_tick,
},
);
} else {
material_instances
.instances
.remove(&MainEntity::from(entity));
}
}
}
fn check_entities_needing_specialization(
needs_specialization: Query<
Entity,
(
Or<(
Changed<Mesh3d>,
AssetChanged<Mesh3d>,
Changed<ImageMaterial3d>,
AssetChanged<ImageMaterial3d>,
)>,
With<ImageMaterial3d>,
),
>,
mut par_local: Local<Parallel<Vec<Entity>>>,
mut entities_needing_specialization: ResMut<EntitiesNeedingSpecialization<ImageMaterial>>,
) {
entities_needing_specialization.clear();
needs_specialization
.par_iter()
.for_each(|entity| par_local.borrow_local_mut().push(entity));
par_local.drain_into(&mut entities_needing_specialization);
}
fn extract_image_materials_needing_specialization(
entities_needing_specialization: Extract<Res<EntitiesNeedingSpecialization<ImageMaterial>>>,
mut entity_specialization_ticks: ResMut<EntitySpecializationTicks>,
mut removed_mesh_material_components: Extract<RemovedComponents<ImageMaterial3d>>,
mut specialized_material_pipeline_cache: ResMut<SpecializedMaterialPipelineCache>,
views: Query<&ExtractedView>,
ticks: SystemChangeTick,
) {
// Clean up any despawned entities, we do this first in case the removed material was re-added
// the same frame, thus will appear both in the removed components list and have been added to
// the `EntitiesNeedingSpecialization` collection by triggering the `Changed` filter
for entity in removed_mesh_material_components.read() {
entity_specialization_ticks.remove(&MainEntity::from(entity));
for view in views {
if let Some(cache) =
specialized_material_pipeline_cache.get_mut(&view.retained_view_entity)
{
cache.remove(&MainEntity::from(entity));
}
}
}
for entity in entities_needing_specialization.iter() {
// Update the entity's specialization tick with this run's tick
entity_specialization_ticks.insert((*entity).into(), ticks.this_run());
}
}

View File

@ -165,6 +165,7 @@ Example | Description
[Lines](../examples/3d/lines.rs) | Create a custom material to draw 3d lines
[Load glTF](../examples/3d/load_gltf.rs) | Loads and renders a glTF file as a scene
[Load glTF extras](../examples/3d/load_gltf_extras.rs) | Loads and renders a glTF file as a scene, including the gltf extras
[Manual Material Implementation](../examples/3d/manual_material.rs) | Demonstrates how to implement a material manually using the mid-level render APIs
[Mesh Ray Cast](../examples/3d/mesh_ray_cast.rs) | Demonstrates ray casting with the `MeshRayCast` system parameter
[Meshlet](../examples/3d/meshlet.rs) | Meshlet rendering for dense high-poly scenes (experimental)
[Mixed lighting](../examples/3d/mixed_lighting.rs) | Demonstrates how to combine baked and dynamic lighting

View File

@ -74,7 +74,7 @@ impl Material for CustomMaterial {
}
fn specialize(
_pipeline: &MaterialPipeline<Self>,
_pipeline: &MaterialPipeline,
descriptor: &mut RenderPipelineDescriptor,
layout: &MeshVertexBufferLayoutRef,
_key: MaterialPipelineKey<Self>,

View File

@ -61,12 +61,12 @@ impl Material for CustomMaterial {
}
fn specialize(
_pipeline: &MaterialPipeline<Self>,
_pipeline: &MaterialPipeline,
descriptor: &mut RenderPipelineDescriptor,
_layout: &MeshVertexBufferLayoutRef,
key: MaterialPipelineKey<Self>,
) -> Result<(), SpecializedMeshPipelineError> {
if key.bind_group_data.is_red {
if key.bind_group_data.is_red == 1 {
let fragment = descriptor.fragment.as_mut().unwrap();
fragment.shader_defs.push("IS_RED".into());
}
@ -86,16 +86,19 @@ struct CustomMaterial {
// This key is used to identify a specific permutation of this material pipeline.
// In this case, we specialize on whether or not to configure the "IS_RED" shader def.
// Specialization keys should be kept as small / cheap to hash as possible,
// as they will be used to look up the pipeline for each drawn entity with this material type.
#[derive(Eq, PartialEq, Hash, Clone)]
// as they will be used to look up the pipeline for each drawn entity with this material type,
// Which is why they are required to be `bytemuck::Pod` and `bytemuck::Zeroable` for materials
// that use the `AsBindGroup` derive macro.
#[repr(C)]
#[derive(Eq, PartialEq, Hash, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct CustomMaterialKey {
is_red: bool,
is_red: u32,
}
impl From<&CustomMaterial> for CustomMaterialKey {
fn from(material: &CustomMaterial) -> Self {
Self {
is_red: material.is_red,
is_red: material.is_red as u32,
}
}
}

View File

@ -79,7 +79,7 @@ impl Material for CustomMaterial {
// and fragment shaders use the "fragment" entry point (for WGSL shaders).
// GLSL uses "main" as the entry point, so we must override the defaults here
fn specialize(
_pipeline: &MaterialPipeline<Self>,
_pipeline: &MaterialPipeline,
descriptor: &mut RenderPipelineDescriptor,
_layout: &MeshVertexBufferLayoutRef,
_key: MaterialPipelineKey<Self>,

View File

@ -101,15 +101,16 @@ struct CustomMaterial {
party_mode: bool,
}
#[derive(Eq, PartialEq, Hash, Clone)]
#[repr(C)]
#[derive(Eq, PartialEq, Hash, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct CustomMaterialKey {
party_mode: bool,
party_mode: u32,
}
impl From<&CustomMaterial> for CustomMaterialKey {
fn from(material: &CustomMaterial) -> Self {
Self {
party_mode: material.party_mode,
party_mode: material.party_mode as u32,
}
}
}
@ -120,7 +121,7 @@ impl Material for CustomMaterial {
}
fn specialize(
_pipeline: &MaterialPipeline<Self>,
_pipeline: &MaterialPipeline,
descriptor: &mut RenderPipelineDescriptor,
_layout: &MeshVertexBufferLayoutRef,
key: MaterialPipelineKey<Self>,
@ -128,7 +129,7 @@ impl Material for CustomMaterial {
let fragment = descriptor.fragment.as_mut().unwrap();
fragment.shader_defs.push(ShaderDefVal::Bool(
"PARTY_MODE".to_string(),
key.bind_group_data.party_mode,
key.bind_group_data.party_mode == 1,
));
Ok(())
}

View File

@ -104,7 +104,7 @@ impl AsBindGroup for BindlessMaterial {
layout: &BindGroupLayout,
render_device: &RenderDevice,
(image_assets, fallback_image): &mut SystemParamItem<'_, '_, Self::Param>,
) -> Result<PreparedBindGroup<Self::Data>, AsBindGroupError> {
) -> Result<PreparedBindGroup, AsBindGroupError> {
// retrieve the render resources from handles
let mut images = vec![];
for handle in self.textures.iter().take(MAX_TEXTURE_COUNT) {
@ -135,17 +135,18 @@ impl AsBindGroup for BindlessMaterial {
Ok(PreparedBindGroup {
bindings: BindingResources(vec![]),
bind_group,
data: (),
})
}
fn bind_group_data(&self) -> Self::Data {}
fn unprepared_bind_group(
&self,
_layout: &BindGroupLayout,
_render_device: &RenderDevice,
_param: &mut SystemParamItem<'_, '_, Self::Param>,
_force_no_bindless: bool,
) -> Result<UnpreparedBindGroup<Self::Data>, AsBindGroupError> {
) -> Result<UnpreparedBindGroup, AsBindGroupError> {
// We implement `as_bind_group`` directly because bindless texture
// arrays can't be owned.
// Or rather, they can be owned, but then you can't make a `&'a [&'a