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`:  - @DGriffin91's Caldera `cargo run --release --features=bevy/trace_tracy -- --random-materials`  - @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`  ### 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`.  --------- Co-authored-by: IceSentry <IceSentry@users.noreply.github.com> Co-authored-by: IceSentry <c.giguere42@gmail.com>
This commit is contained in:
parent
73a313200d
commit
e6ba9a6d18
13
Cargo.toml
13
Cargo.toml
@ -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"
|
||||
|
11
assets/shaders/manual_material.wgsl
Normal file
11
assets/shaders/manual_material.wgsl
Normal 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);
|
||||
}
|
@ -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,
|
||||
|
@ -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"] }
|
||||
|
@ -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,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
@ -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
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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())
|
||||
|
@ -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),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -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>,
|
||||
|
@ -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,
|
||||
);
|
||||
|
@ -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,
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
431
crates/bevy_render/src/erased_render_asset.rs
Normal file
431
crates/bevy_render/src/erased_render_asset.rs
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
///
|
||||
|
@ -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
|
||||
|
@ -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(),
|
||||
|
@ -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() {
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -77,7 +77,7 @@ impl Material for LineMaterial {
|
||||
}
|
||||
|
||||
fn specialize(
|
||||
_pipeline: &MaterialPipeline<Self>,
|
||||
_pipeline: &MaterialPipeline,
|
||||
descriptor: &mut RenderPipelineDescriptor,
|
||||
_layout: &MeshVertexBufferLayoutRef,
|
||||
_key: MaterialPipelineKey<Self>,
|
||||
|
315
examples/3d/manual_material.rs
Normal file
315
examples/3d/manual_material.rs
Normal 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());
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -74,7 +74,7 @@ impl Material for CustomMaterial {
|
||||
}
|
||||
|
||||
fn specialize(
|
||||
_pipeline: &MaterialPipeline<Self>,
|
||||
_pipeline: &MaterialPipeline,
|
||||
descriptor: &mut RenderPipelineDescriptor,
|
||||
layout: &MeshVertexBufferLayoutRef,
|
||||
_key: MaterialPipelineKey<Self>,
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>,
|
||||
|
@ -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(())
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user