Skip to content

Enable partial bindless on Metal and reduce bind group overhead (#18149)#23436

Merged
alice-i-cecile merged 8 commits intobevyengine:mainfrom
holg:fix/metal-partial-blindless
Mar 28, 2026
Merged

Enable partial bindless on Metal and reduce bind group overhead (#18149)#23436
alice-i-cecile merged 8 commits intobevyengine:mainfrom
holg:fix/metal-partial-blindless

Conversation

@holg
Copy link
Copy Markdown
Contributor

@holg holg commented Mar 20, 2026

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_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.

…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.
Copilot AI review requested due to automatic review settings March 20, 2026 12:38
@github-actions
Copy link
Copy Markdown
Contributor

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 ✨

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

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_ARRAY a 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 of max_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.

Comment on lines +223 to 230
/// `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> {
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
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.

-1, we don't enforce API stability in rendering guts

Comment on lines 220 to +229
/// 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],
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
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.

-1, the comments were out of date

@kfc35 kfc35 added the A-Rendering Drawing game state to the screen label Mar 20, 2026
@github-project-automation github-project-automation Bot moved this to Needs SME Triage in Rendering Mar 20, 2026
@kfc35 kfc35 added C-Feature A new feature, making something new possible S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Mar 20, 2026
Copy link
Copy Markdown
Contributor

@pcwalton pcwalton left a comment

Choose a reason for hiding this comment

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

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.
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 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.

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.

my PR is under review for adding support for buffer binding arrays in Metal: gfx-rs/wgpu#9081

@alice-i-cecile alice-i-cecile added the M-Release-Note Work that should be called out in the blog due to impact label Mar 21, 2026
@github-actions
Copy link
Copy Markdown
Contributor

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.

@holg holg mentioned this pull request Mar 22, 2026
1 task
@kfc35 kfc35 added S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged and removed S-Needs-Review Needs reviewer attention (from anyone!) to move forward S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it labels Mar 23, 2026
@kfc35
Copy link
Copy Markdown
Contributor

kfc35 commented Mar 23, 2026

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)
Here are the corrected links:
directions: https://github.com/bevyengine/bevy/blob/main/_release-content/release_notes.md

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 😄

@holg
Copy link
Copy Markdown
Contributor Author

holg commented Mar 24, 2026

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.

Copy link
Copy Markdown
Contributor

@mate-h mate-h left a comment

Choose a reason for hiding this comment

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

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] = &[
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.

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.

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.

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)
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.

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),
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.

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

@@ -0,0 +1,23 @@
---
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.

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

@kfc35 kfc35 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-Waiting-on-Author The author needs to make changes or address concerns before this can be merged labels Mar 24, 2026
@kfc35

This comment has been minimized.

@alice-i-cecile alice-i-cecile added this pull request to the merge queue Mar 28, 2026
Merged via the queue into bevyengine:main with commit 3cf98a6 Mar 28, 2026
38 checks passed
@github-project-automation github-project-automation Bot moved this from Needs SME Triage to Done in Rendering Mar 28, 2026
splo pushed a commit to splo/bevy that referenced this pull request Mar 31, 2026
…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.
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-Feature A new feature, making something new possible 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

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

enable partial bindless support on metal

7 participants