Skip to content

Add BindGroupLayout caching via descriptors#21205

Merged
alice-i-cecile merged 56 commits into
bevyengine:mainfrom
Zeophlite:ad/bgld
Oct 17, 2025
Merged

Add BindGroupLayout caching via descriptors#21205
alice-i-cecile merged 56 commits into
bevyengine:mainfrom
Zeophlite:ad/bgld

Conversation

@Zeophlite

@Zeophlite Zeophlite commented Sep 25, 2025

Copy link
Copy Markdown
Contributor

Objective

Solution

  • Reviewers, look at first commit for mechanism, and following for usage

Testing

  • CI

Comment thread crates/bevy_anti_alias/src/contrast_adaptive_sharpening/node.rs Outdated
Comment thread crates/bevy_pbr/src/material.rs
Comment thread crates/bevy_render/src/render_resource/bind_group.rs Outdated
@alice-i-cecile alice-i-cecile added C-Bug An unexpected or incorrect behavior A-Rendering Drawing game state to the screen S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Sep 26, 2025
@alice-i-cecile

Copy link
Copy Markdown
Member

Can you add more motivation to the PR description please? I'm having trouble following why this is a good idea.

@Zeophlite Zeophlite added this to the 0.18 milestone Sep 26, 2025
@Zeophlite Zeophlite removed the C-Bug An unexpected or incorrect behavior label Sep 26, 2025
@Zeophlite Zeophlite marked this pull request as ready for review October 14, 2025 23:22

@tychedelia tychedelia left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Awesome work. Thanks for doing all this. I'd like to see some benchmarking as I'm a little worried about performance. However, my intuition is that we are extremely read heavy and so it feels likely that we can work around any performance issues if they come up. I can help with benchmarking if necessary. But overall the code looks great!

#[derive(Clone, Debug, PartialEq, Eq, Hash, Default)]
pub struct BindGroupLayoutDescriptor {
/// Debug label of the bind group layout descriptor. This will show up in graphics debuggers for easy identification.
pub label: Option<Cow<'static, str>>,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

as an aside, I kinda feel like we should make labels non-optional in all our code but that's for another PR.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yeah this would be a good follow-up.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should be using the same DebugName strategy that ECS uses for system names.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I can help with benchmarking if necessary

@tychedelia I would definitely appreciate that (especially if you can explain how)

#[derive(Resource)]
pub struct PipelineCache {
layout_cache: Arc<Mutex<LayoutCache>>,
bindgroup_layout_cache: Arc<Mutex<BindGroupLayoutCache>>,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'd like to see benchmarking before any changes but this might be a rare scenario RwLock could win. Or maybe even something like dashmap. Anyway, just curious how much contention there actually is on this.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We might get wins doing this on the layout_cache too. I thinkt he shader_cache probably wouldnt see any significant wins from it but might as well be consistent if we switch to RwLock

@atlv24 atlv24 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I have made a migration guide Zeophlite#4

Comment on lines +601 to +602
#[expect(clippy::option_map_or_none, reason = "TODO")]
label: Self::label().map_or(None, |f| Some(f.into())),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
#[expect(clippy::option_map_or_none, reason = "TODO")]
label: Self::label().map_or(None, |f| Some(f.into())),
label: Self::label().map(|f| f.into()),

@jasmine-nominal jasmine-nominal left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Lgtm once the clippy lint is addressed.

This will be very helpful for an upcoming PR I'm working on to simplify render boilerplate. I'd also like if we could extend this to cache the actual pipelines.

Comment on lines +225 to +226
#[expect(clippy::redundant_closure_for_method_calls, reason = "TODO")]
descriptor.label.as_ref().map(|s| s.as_ref()),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
#[expect(clippy::redundant_closure_for_method_calls, reason = "TODO")]
descriptor.label.as_ref().map(|s| s.as_ref()),
descriptor.label.as_ref().map(Cow::as_ref),

@atlv24 atlv24 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for putting in the work for this!

Approved, contingent on my two suggestions and the migration guide being merged.

I will make a follow-up PR for AsBindGroup cleanups to remove the RenderDevice dependency from it.

@atlv24 atlv24 added S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it and removed S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Oct 16, 2025
@alice-i-cecile alice-i-cecile added the C-Code-Quality A section of code that is hard to understand or change label Oct 17, 2025
@alice-i-cecile alice-i-cecile added this pull request to the merge queue Oct 17, 2025
Merged via the queue into bevyengine:main with commit adf8211 Oct 17, 2025
38 checks passed
mate-h pushed a commit to mate-h/bevy that referenced this pull request Oct 22, 2025
# Objective

- Defer creating `BindGroupLayout` by using a
`BindGroupLayoutDescriptor` and cache the results
- Unblocks `bevy_material` (render-less material definitions)
- Blocked by bevyengine#21533

## Solution

- Reviewers, look at first commit for mechanism, and following for usage

## Testing

- CI

---------

Co-authored-by: atlas <email@atlasdostal.com>
github-merge-queue Bot pushed a commit that referenced this pull request Nov 2, 2025
* Fix compile error when compiling with DLSS enabled after
#21205
* Use permutation sampling for ReSTIR DI temporal reuse to fix artifacts
under DLSS-RR
* For both DI and GI, removed the spatial raytrace, and moved it to the
final reservoir before shading.
* Reduced DI initial samples 32 -> 8 for better performance at the cost
of quality
* Various specular GI improvements and bugfixes (still kinda terrible
overall, I need to do some research on how people usually do this kind
of thing)
* Made the world cache adapt faster / be less stable
* Switched spatial hashing collisions from to linear probing

---------

Co-authored-by: Jasmine Schweitzer <jasmine.schweitzer@nominal.io>
@djeedai

djeedai commented Jan 31, 2026

Copy link
Copy Markdown
Contributor

Has anyone looked at perf in the end on that PR? Or memory consumption? @alice-i-cecile or @tychedelia maybe?

I'm upgrading Hanabi, and where I had before a bunch of trivially cloneable BindGroupLayout handles in various caches (hash maps) and other data structures, now everywhere is a .clone() of an entire BindGroupLayoutDescriptor including cloning its Vec<> of entries (so at least 2 allocations per .clone()). Even in BindGroupLayoutCache::get(), the layout descriptor is passed by value, so needs to be cloned by the caller, even if the bind group layout already exists in the cache. Basically unless I'm missing something, since you need to query the cache each frame to rebuild the bind groups (as I understand Bevy rebuilds them all the time, as last time I asked I was told to do the same because creating bind groups was cheap), this means we .clone() every single BindGroupLayoutDescriptor every frame, likely multiple times.

@mockersf

Copy link
Copy Markdown
Member

It had no impact on the performance of Bevy's stress tests

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 C-Code-Quality A section of code that is hard to understand or change S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it

Projects

No open projects
Status: Done

Development

Successfully merging this pull request may close these issues.

7 participants