Enable partial bindless on Metal and reduce bind group overhead (#18149)#23436
Conversation
…engine#18149) Make BUFFER_BINDING_ARRAY conditional on whether the material uses buffer binding arrays. Fix sampler limit to check array element count instead of binding slot count. Only create binding arrays for resource types the material actually uses, reducing overhead on all platforms.
|
Welcome, new contributor! Please make sure you've read our contributing guide, as well as our policy regarding AI usage, and we look forward to reviewing your pull request shortly ✨ |
There was a problem hiding this comment.
Pull request overview
This PR enables partial bindless support on Metal by making BUFFER_BINDING_ARRAY conditional, fixes the sampler limit check for bindless binding arrays, and reduces bind group overhead by only creating bindless binding arrays for resource types actually used by a material.
Changes:
- Make
BUFFER_BINDING_ARRAYa conditional requirement based on whether the material uses buffer binding arrays. - Fix bindless sampler limit validation to use
max_binding_array_sampler_elements_per_shader_stage(element count) instead ofmax_samplers_per_shader_stage(binding slots). - Reduce layout + fallback resource overhead by skipping creation of unused bindless sampler/texture binding arrays.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
crates/bevy_render/src/render_resource/bindless.rs |
Creates bindless bind group layout entries only for the resource types actually used by the material. |
crates/bevy_render/macros/src/as_bind_group.rs |
Updates bindless feature gating and sampler limit checks; passes used bindless resource types into layout entry creation. |
crates/bevy_pbr/src/material_bind_groups.rs |
Skips creating unused sampler/texture binding resource arrays based on the material’s bindless descriptor. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| /// `used_resource_types` limits which binding arrays are created, | ||
| /// reducing argument buffer slot usage on constrained platforms. | ||
| pub fn create_bindless_bind_group_layout_entries( | ||
| bindless_index_table_length: u32, | ||
| bindless_slab_resource_limit: u32, | ||
| bindless_index_table_binding_number: BindingNumber, | ||
| used_resource_types: &[BindlessResourceType], | ||
| ) -> Vec<BindGroupLayoutEntry> { |
There was a problem hiding this comment.
create_bindless_bind_group_layout_entries is a pub function and this signature change adds a required used_resource_types parameter, which is a breaking change for downstream crates that may call it directly. Consider keeping the old signature as a wrapper (e.g. defaulting to all entries in BINDING_NUMBERS), or making the new parameter optional so existing callers continue to compile while still allowing the optimization for generated code.
There was a problem hiding this comment.
-1, we don't enforce API stability in rendering guts
| /// Creates the bind group layout entries common to all shaders that use | ||
| /// bindless bind groups. | ||
| /// | ||
| /// `bindless_resource_count` specifies the total number of bindless resources. | ||
| /// `bindless_slab_resource_limit` specifies the resolved | ||
| /// [`BindlessSlabResourceLimit`] value. | ||
| /// `used_resource_types` limits which binding arrays are created, | ||
| /// reducing argument buffer slot usage on constrained platforms. | ||
| pub fn create_bindless_bind_group_layout_entries( | ||
| bindless_index_table_length: u32, | ||
| bindless_slab_resource_limit: u32, | ||
| bindless_index_table_binding_number: BindingNumber, | ||
| used_resource_types: &[BindlessResourceType], |
There was a problem hiding this comment.
The rustdoc for create_bindless_bind_group_layout_entries no longer documents bindless_index_table_length, bindless_slab_resource_limit, or bindless_index_table_binding_number, even though they remain public parameters. Please restore brief parameter docs (or an overall explanation) so external callers can understand how to supply correct values.
There was a problem hiding this comment.
-1, the comments were out of date
pcwalton
left a comment
There was a problem hiding this comment.
Looks good, had one nit. This will help macOS/iOS performance a lot, thank you!
| let mut bindless_resource_types = Vec::new(); | ||
| let mut bindless_buffer_descriptors = Vec::new(); | ||
| // Whether this material needs buffer binding arrays (BUFFER_BINDING_ARRAY feature). | ||
| // False when only #[data(...)], textures, and samplers are used. |
There was a problem hiding this comment.
I might mention in the comment for future readers that if and when wgpu adds support for buffer binding arrays in Metal, we can remove this.
There was a problem hiding this comment.
my PR is under review for adding support for buffer binding arrays in Metal: gfx-rs/wgpu#9081
|
It looks like your PR has been selected for a highlight in the next release blog post, but you didn't provide a release note. Please review the instructions for writing release notes, then expand or revise the content in the release notes directory to showcase your changes. |
This bot is pointing to the wrong links (this has already been fixed) directory: https://github.com/bevyengine/bevy/tree/main/_release-content/release-notes Once you’ve made one, I’ll do a quick look over and place this into ready for final review 😄 |
|
Added release note in _release-content/release-notes/partial_bindless_metal.md. Also addressed pcwalton's nit about the future Metal buffer binding array support comment (referencing wgpu#9081) in the previous commit. |
mate-h
left a comment
There was a problem hiding this comment.
Looks good to me overall and is a good improvement! Just a few small comments
| match #actual_bindless_slot_count { | ||
| Some(bindless_slot_count) => { | ||
| let bindless_index_table_range = #bindless_index_table_range; | ||
| let used_resource_types: &[#render_path::render_resource::BindlessResourceType] = &[ |
There was a problem hiding this comment.
Code style nit pick: does the type need to be explicitly declared here or can it be inferred? Also if it does need to be declared then shorten it by adding an import for that type.
There was a problem hiding this comment.
Fair point, it can be inferred from the context..., will change...
| ]; | ||
|
|
||
| // binding arrays only for types that this material uses | ||
| // Specifically Metal where each binding array uses the buffer slot (limited to 31) |
There was a problem hiding this comment.
Specify where this limit comes from ie the metal feature set tables or reference a wgpu constant
| let entry = match resource_type { | ||
| BindlessResourceType::SamplerFiltering => sampler(SamplerBindingType::Filtering) | ||
| .count(bindless_slab_resource_limit) | ||
| .build(**binding_number, stages), |
There was a problem hiding this comment.
These .count and .build lines are repeated frequently. Maybe reorganize the code so that first you return Some type from the match and then count and build. For the ones with a continue map to None
And skip none after for counting
…ding array creation to reduce code repetition
| @@ -0,0 +1,23 @@ | |||
| --- | |||
There was a problem hiding this comment.
Hm, this file still ended up in release-content and not _release-content 😅 maybe main needs to be merged into your branch if the directory hasn’t been updated
This comment has been minimized.
This comment has been minimized.
…engine#18149) (bevyengine#23436) Make BUFFER_BINDING_ARRAY conditional on whether the material uses buffer binding arrays. Fix sampler limit to check array element count instead of binding slot count. Only create binding arrays for resource types the material actually uses, reducing overhead on all platforms. ## Objective Fixes bevyengine#18149 Metal supports `TEXTURE_BINDING_ARRAY` but not `BUFFER_BINDING_ARRAY`. Bindless was disabled entirely on Metal because `bindless_supported()` required both unconditionally. ## Solution 1. **Conditional feature check**: Only require `BUFFER_BINDING_ARRAY` when the material actually uses buffer binding arrays. 2. Materials using `#[data(...)]`, textures, and samplers (like `StandardMaterial`) only need `TEXTURE_BINDING_ARRAY`. **Fix sampler limit check**: Use `max_binding_array_sampler_elements_per_shader_stage` (array element count) instead of `max_samplers_per_shader_stage` (binding slot count). 3. **Only create needed binding arrays**: `create_bindless_bind_group_layout_entries` now skips resource types the material doesn't use. This stays within Metal's 31 argument buffer slot limit and reduces wasted fallback resources on all platforms. ## Testing Bistro Exterior (698 materials), 5-minute runs: | GPU | Avg FPS (before → after) | Min FPS (before → after) | RAM/VRAM | |-----|--------------------------|--------------------------|----------| | Apple M2 Max (Metal) | 115 → 136 **(+18%)** | 60 → 106 **(+77%)** | -57 MB RAM | | NVIDIA 5060 Ti | 118 → 217 **(+84%)** | 60 → 165 **(+174%)** | Same | | Intel i360P | 25 → 29 **(+15%)** | Same | Same | | AMD Vega 8 / Ryzen 4800U | 25 → 25 | Same | **-88 MB VRAM** | | Intel Iris XE | ~22 → ~22 | Same | No regression | Also tested: `3d_scene`, `pbr`, `lighting`, `transmission`, `deferred_rendering`: all pass with zero errors. Materials using `#[uniform(..., binding_array(...))]` correctly fall back to non-bindless on Metal.
Make BUFFER_BINDING_ARRAY conditional on whether the material uses buffer binding arrays. Fix sampler limit to check array element count instead of binding slot count. Only create binding arrays for resource types the material actually uses, reducing overhead on all platforms.
Objective
Fixes #18149
Metal supports
TEXTURE_BINDING_ARRAYbut notBUFFER_BINDING_ARRAY.Bindless was disabled entirely on Metal because
bindless_supported()required both unconditionally.Solution
BUFFER_BINDING_ARRAYwhen the material actually uses buffer binding arrays.#[data(...)], textures, and samplers (likeStandardMaterial) only needTEXTURE_BINDING_ARRAY. Fix sampler limit check: Usemax_binding_array_sampler_elements_per_shader_stage(array element count) instead ofmax_samplers_per_shader_stage(binding slot count).create_bindless_bind_group_layout_entriesnow skips resource types the material doesn't use. This stays within Metal's 31 argument buffer slot limit and reduces wasted fallback resources on all platforms.Testing
Bistro Exterior (698 materials), 5-minute runs:
Also tested:
3d_scene,pbr,lighting,transmission,deferred_rendering: all pass with zero errors.Materials using
#[uniform(..., binding_array(...))]correctly fall back to non-bindless on Metal.