-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Type erased materials #19667
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
alice-i-cecile
merged 34 commits into
bevyengine:main
from
tychedelia:18075-type-erased-materials
Jun 27, 2025
Merged
Type erased materials #19667
Changes from 13 commits
Commits
Show all changes
34 commits
Select commit
Hold shift + click to select a range
99be854
Erase material types from the render world.
tychedelia 1f74dfe
Custom material example.
tychedelia e449f3a
Ci.
tychedelia 8d5d1ee
Switch to SmallVec + fn ptr.
tychedelia 838c4d3
Use labels.
tychedelia 235f823
Ci.
tychedelia 0b98281
Ci.
tychedelia 21f1b95
Ci.
tychedelia 71f4a21
Ci.
tychedelia 026c91e
Fix example.
tychedelia 40348f3
Fix example.
tychedelia 1b616da
Docs.
tychedelia 0f2b1c5
Merge branch 'main' into 18075-type-erased-materials
tychedelia cf29e33
Store type id in erased key.
tychedelia 72b89d5
Change example name.
tychedelia 2999a8d
Merge branch 'main' into 18075-type-erased-materials
tychedelia 0875c04
1.88.
tychedelia 67ed7e4
Merge main.
tychedelia a2118c5
Fix rename.
tychedelia bb675be
Merge main.
tychedelia 7a1b6dd
Fixes.
tychedelia 0415540
Update crates/bevy_pbr/src/material.rs
tychedelia 493d99c
Fix bind group number.
tychedelia 1b7e4a0
Revert unrelated changes.
tychedelia f73113c
Fixes.
tychedelia 3173100
Docs.
tychedelia aee1ed2
Ci.
tychedelia ab91ef7
Remove unused trait.
tychedelia 141d233
Correct docs.
tychedelia 1e6b8ed
Remove unused import.
tychedelia 5d939a2
Couple of minor changes
IceSentry 11e9eb0
Merge pull request #1 from IceSentry/erased_materials_minor_changes
tychedelia fd6534b
Ci.
tychedelia 81c12e2
Merge branch 'main' into 18075-type-erased-materials
tychedelia File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| #import bevy_pbr::forward_io::VertexOutput | ||
|
|
||
| @group(2) @binding(0) var material_color_texture: texture_2d<f32>; | ||
| @group(2) @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); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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,30 @@ 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 MaterialPipeline { mesh_pipeline } = pipeline.clone(); | ||
| let base_pipeline = MaterialPipeline { mesh_pipeline }; | ||
| 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)?; | ||
|
|
||
| // Call the extended material's specialize function afterwards | ||
| let MaterialPipeline::<Self> { | ||
| mesh_pipeline, | ||
| material_layout, | ||
| vertex_shader, | ||
| fragment_shader, | ||
| bindless, | ||
| .. | ||
| } = pipeline.clone(); | ||
| let MaterialPipeline { mesh_pipeline, .. } = pipeline.clone(); | ||
|
|
||
| E::specialize( | ||
| &MaterialExtensionPipeline { | ||
| mesh_pipeline, | ||
| material_layout, | ||
| vertex_shader, | ||
| fragment_shader, | ||
| bindless, | ||
| }, | ||
| &MaterialExtensionPipeline { mesh_pipeline }, | ||
| descriptor, | ||
| layout, | ||
| MaterialExtensionKey { | ||
| mesh_key: key.mesh_key, | ||
| bind_group_data: key.bind_group_data.1, | ||
| bind_group_data: key.bind_group_data.extension, | ||
| }, | ||
| ) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can't apply a suggestion for some reason, but you can replace it with this. You don't need to clone the pipeline twice and it's a bit shorter. // Call the base material's specialize function
let base_key = MaterialPipelineKey::<B> {
mesh_key: key.mesh_key,
bind_group_data: key.bind_group_data.base,
};
B::specialize(&pipeline, descriptor, layout, base_key)?;
// Call the extended material's specialize function afterwards
E::specialize(
&MaterialExtensionPipeline {
mesh_pipeline: pipeline.mesh_pipeline.clone(),
},
descriptor,
layout,
MaterialExtensionKey {
mesh_key: key.mesh_key,
bind_group_data: key.bind_group_data.extension,
},
) |
||
| } | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did this need to change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So the way that we're erasing material key (aka bind group data) in this PR at the moment is by going through bytemuck (see
MaterialPropertieswhere it's stored as aSmallVec), which basically puts it in our public api.I'm somewhat uncertain about this change as it enforces new constraints on
AsBindGroup::Data, namely the bytemuck derives and repr c. On the one hand, our use of bind group data internal to the engine is always in the form of flags and so this just makes that explicit. On the other hand, if people want to have weird material keys, maybe it isn't our place to stop them.The alternative is doing
Box<dyn Any>, but I ran into problems with making that hashable. The key bit here is that while the erased key only needs to be downcast to the concrete material key in the event you are respecializing, it needs to be hashable for the internal pipeline cache check. While actual respecialization is rare, the cache check happens any time a mesh/material changes.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I understand how this relates to changing the bytemuck version 😅
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh sorry! It needs to match the version used in
bevy_pbr. If we wanted to add that constraint, we should probably re-export it.