Skip to content

Conversation

@tychedelia
Copy link
Member

@tychedelia tychedelia commented Jun 16, 2025

Objective

Closes #18075

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

This opens the possibility for:

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

Solution

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

Benchmarks

  • many_cubes:
    cargo run --example many_cubes --release --features=bevy/trace_tracy -- --vary-material-data-per-instance:
    Screenshot 2025-06-26 235426

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

  • @DGriffin91's Caldera with 20 unique material types (i.e. MaterialPlugin<M>) and random materials per mesh
    cargo run --release --features=bevy/trace_tracy -- --random-materials
    Screenshot 2025-06-27 000425

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 for a custom material implemented without the use of the Material trait and thus AsBindGroup.

image

@tychedelia tychedelia changed the title Erase material types from the render world Remove Material trait bound from all render world systems and resources Jun 16, 2025
@tychedelia tychedelia added A-Rendering Drawing game state to the screen M-Migration-Guide A breaking change to Bevy's public API that needs to be noted in a migration guide D-Complex Quite challenging from either a design or technical perspective. Ask for help! X-Controversial There is active debate or serious implications around merging this PR M-Release-Note Work that should be called out in the blog due to impact S-Needs-Review Needs reviewer attention (from anyone!) to move forward S-Needs-SME Decision or review from an SME is required labels Jun 16, 2025
@github-actions
Copy link
Contributor

Your PR caused a change in the graphical output of an example or rendering test. This might be intentional, but it could also mean that something broke!
You can review it at https://pixel-eagle.com/project/B04F67C0-C054-4A6F-92EC-F599FEC2FD1D?filter=PR-19667

If it's expected, please add the M-Deliberate-Rendering-Change label.

If this change seems unrelated to your PR, you can consider updating your PR to target the latest main branch, either by rebasing or merging main into it.

@github-actions
Copy link
Contributor

Your PR caused a change in the graphical output of an example or rendering test. This might be intentional, but it could also mean that something broke!
You can review it at https://pixel-eagle.com/project/B04F67C0-C054-4A6F-92EC-F599FEC2FD1D?filter=PR-19667

If it's expected, please add the M-Deliberate-Rendering-Change label.

If this change seems unrelated to your PR, you can consider updating your PR to target the latest main branch, either by rebasing or merging main into it.

@ecoskey ecoskey self-requested a review June 16, 2025 01:46
@github-actions
Copy link
Contributor

Your PR caused a change in the graphical output of an example or rendering test. This might be intentional, but it could also mean that something broke!
You can review it at https://pixel-eagle.com/project/B04F67C0-C054-4A6F-92EC-F599FEC2FD1D?filter=PR-19667

If it's expected, please add the M-Deliberate-Rendering-Change label.

If this change seems unrelated to your PR, you can consider updating your PR to target the latest main branch, either by rebasing or merging main into it.

@tychedelia tychedelia changed the title Remove Material trait bound from all render world systems and resources Type erased materials Jun 16, 2025
@IceSentry IceSentry self-requested a review June 16, 2025 19:32
flate2 = "1.0"
serde = { version = "1", features = ["derive"] }
serde_json = "1.0.140"
bytemuck = "1.7"
Copy link
Contributor

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?

Copy link
Member Author

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 MaterialProperties where it's stored as a SmallVec), 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.

Copy link
Contributor

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 😅

Copy link
Member Author

@tychedelia tychedelia Jun 16, 2025

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.

(
check_views_lights_need_specialization.in_set(RenderSystems::PrepareAssets),
// specialize_shadows also needs to run after prepare_assets::<PreparedMaterial>,
// which is fine since ManageViews is after PrepareAssets
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will ManageViews always be after PrepareAssets or might something change it down the line? i think a redundant edge for defensiveness doesnt have overhead, right?

Copy link
Contributor

@atlv24 atlv24 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so cool. so good

})
}

fn bind_group_data(&self) -> Self::Data {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very minor thing, but if it's valid to not return anything this should probably just the default behaviour of the trait

Copy link
Contributor

@IceSentry IceSentry left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's a couple of things that could still be simplified, but this is a good starting point and we can iterate on everything else. I'm very excited for what kind of features this will unlock for us!

LGTM to me

@alice-i-cecile alice-i-cecile added S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it X-Blessed Has a large architectural impact or tradeoffs, but the design has been endorsed by decision makers and removed S-Needs-Review Needs reviewer attention (from anyone!) to move forward S-Needs-SME Decision or review from an SME is required X-Controversial There is active debate or serious implications around merging this PR labels Jun 27, 2025
@alice-i-cecile alice-i-cecile added this pull request to the merge queue Jun 27, 2025
Merged via the queue into bevyengine:main with commit e6ba9a6 Jun 27, 2025
40 checks passed
github-merge-queue bot pushed a commit that referenced this pull request Jul 1, 2025
# Objective

- PrepassPipelineInternal used to exist to optimize compile time and
binary size when PrepassPipeline was generic over the material.
- After #19667, PrepassPipeline is no longer generic!

## Solution

- Flatten all the fields of `PrepassPipelineInternal` into
`PrepassPipeline`.
greeble-dev added a commit to greeble-dev/bevy that referenced this pull request Jul 15, 2025
…refactoring as it depended on the generic parameter which has been erased in bevyengine#19667.
github-merge-queue bot pushed a commit that referenced this pull request Aug 17, 2025
# Objective

#19667 added a `bytemuck::Pod` type constraint on `AsBindGroup::Data`
which, while technically a reasonable constraint to expect from material
keys, imposed a breaking change on our users which may be confusing /
difficult for them to work around. It's not technically invalid to store
arbitrary types in a material key, just probably bad practice.

Additionally, the performance concerns here were probably overstated: 1.
cold specialization makes re-specialization less likely. 2. we can
mitigate going through the vtable unnecessarily by pre-computing our
hash which should still make the boxed storage fast in almost all cases
where we don't get a collision.

## Solution

Use `Box<dyn Any>` for erasure.
github-merge-queue bot pushed a commit that referenced this pull request Nov 4, 2025
# Objective

#19667 introduced a type-erased material system that effectively puts
all material instances through a single cache: during asset preparation
(`ErasedRenderAssetPlugin`), materials are processed into a set of
`MaterialProperties` that contains all the data needed to render them,
and these are all cached and deduplicated as needed.

This allows for maximal flexibility (every single material instance
could have a different ""type"") but complicates the logic and makes
cache keys really big. So, one goal for the material revamp I have (and
I think @tychedelia is on board) is to cache materials at two levels:
material "types" and material "instances", where material types roughly
map to the rust types that currently implement `Material`. Without
getting into implementation details, storing mostly static data separate
from instance data would let us simplify a lot of the logic, while only
requiring a little more work for fully-dynamic use cases.

This PR is a first step in that direction, which stores *all* the
available draw functions in `MaterialProperties`, and pushes the
decision for "what draw function should I use for this material?" to
queue time, where before it was split between there and asset
preparation. This makes the list of available draw functions
instance-independent, and will later allow us to store it with other
"static" material data.

## Solution

- Make draw function labels 1:1 with render phases, and include all of
them in the list in `MaterialProperties`

## Testing

- Ran `3d_scene`
- Ran `manual_material`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Rendering Drawing game state to the screen D-Complex Quite challenging from either a design or technical perspective. Ask for help! M-Migration-Guide A breaking change to Bevy's public API that needs to be noted in a migration guide M-Release-Note Work that should be called out in the blog due to impact S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it X-Blessed Has a large architectural impact or tradeoffs, but the design has been endorsed by decision makers

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Type erased / dynamic runtime materials

6 participants