Disable bindless on a per-material basis if the specific material uses more samplers than are available on the device. (#17155)

Some hardware and driver combos, such as Intel Iris Xe, have low limits
on the numbers of samplers per shader, causing an overflow. With
first-class bindless arrays, `wgpu` should be able to work around this
limitation eventually, but for now we need to disable bindless materials
on those platforms.

This is an alternative to PR #17107 that calculates the precise number
of samplers needed and compares to the hardware sampler limit,
transparently falling back to non-bindless if the limit is exceeded.

Fixes #16988.
This commit is contained in:
Patrick Walton 2025-01-05 12:36:39 -08:00 committed by GitHub
parent 3c829d7f68
commit 0e36abc180
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 42 additions and 15 deletions

View File

@ -19,7 +19,6 @@ use bevy_render::{
UnpreparedBindGroup, WgpuSampler, WgpuTextureView,
},
renderer::RenderDevice,
settings::WgpuFeatures,
texture::FallbackImage,
};
use bevy_utils::{default, tracing::error, HashMap};
@ -795,10 +794,7 @@ pub fn material_uses_bindless_resources<M>(render_device: &RenderDevice) -> bool
where
M: Material,
{
M::bindless_slot_count().is_some()
&& render_device
.features()
.contains(WgpuFeatures::BUFFER_BINDING_ARRAY | WgpuFeatures::TEXTURE_BINDING_ARRAY)
M::bindless_slot_count().is_some() && M::bindless_supported(render_device)
}
impl FromWorld for FallbackBindlessResources {

View File

@ -132,6 +132,11 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result<TokenStream> {
}
};
// Count the number of sampler fields needed. We might have to disable
// bindless if bindless arrays take the GPU over the maximum number of
// samplers.
let mut sampler_binding_count = 0;
// Read field-level attributes
for field in fields {
// Search ahead for texture attributes so we can use them with any
@ -341,6 +346,8 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result<TokenStream> {
)
});
sampler_binding_count += 1;
binding_layouts.push(quote! {
#render_path::render_resource::BindGroupLayoutEntry {
binding: #binding_index,
@ -417,6 +424,8 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result<TokenStream> {
)
});
sampler_binding_count += 1;
binding_layouts.push(quote!{
#render_path::render_resource::BindGroupLayoutEntry {
binding: #binding_index,
@ -440,11 +449,7 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result<TokenStream> {
Some(_) => {
quote! {
let (#uniform_binding_type, #uniform_buffer_usages) =
if render_device.features().contains(
#render_path::settings::WgpuFeatures::BUFFER_BINDING_ARRAY |
#render_path::settings::WgpuFeatures::TEXTURE_BINDING_ARRAY
) && render_device.limits().max_storage_buffers_per_shader_stage > 0 &&
!force_no_bindless {
if Self::bindless_supported(render_device) && !force_no_bindless {
(
#render_path::render_resource::BufferBindingType::Storage { read_only: true },
#render_path::render_resource::BufferUsages::STORAGE,
@ -563,6 +568,17 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result<TokenStream> {
(prepared_data.clone(), prepared_data)
};
// Calculate the number of samplers that we need, so that we don't go over
// the limit on certain platforms. See
// https://github.com/bevyengine/bevy/issues/16988.
let samplers_needed = match attr_bindless_count {
Some(Lit::Int(ref bindless_count)) => match bindless_count.base10_parse::<u32>() {
Ok(bindless_count) => sampler_binding_count * bindless_count,
Err(_) => 0,
},
_ => 0,
};
// Calculate the actual number of bindless slots, taking hardware
// limitations into account.
let (bindless_slot_count, actual_bindless_slot_count_declaration) = match attr_bindless_count {
@ -571,13 +587,19 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result<TokenStream> {
fn bindless_slot_count() -> Option<u32> {
Some(#bindless_count)
}
fn bindless_supported(render_device: &#render_path::renderer::RenderDevice) -> bool {
render_device.features().contains(
#render_path::settings::WgpuFeatures::BUFFER_BINDING_ARRAY |
#render_path::settings::WgpuFeatures::TEXTURE_BINDING_ARRAY
) &&
render_device.limits().max_storage_buffers_per_shader_stage > 0 &&
render_device.limits().max_samplers_per_shader_stage >= #samplers_needed
}
},
quote! {
let #actual_bindless_slot_count = if render_device.features().contains(
#render_path::settings::WgpuFeatures::BUFFER_BINDING_ARRAY |
#render_path::settings::WgpuFeatures::TEXTURE_BINDING_ARRAY
) && render_device.limits().max_storage_buffers_per_shader_stage > 0 &&
!force_no_bindless {
let #actual_bindless_slot_count = if Self::bindless_supported(render_device) &&
!force_no_bindless {
::core::num::NonZeroU32::new(#bindless_count)
} else {
None

View File

@ -342,6 +342,15 @@ pub trait AsBindGroup {
None
}
/// True if the hardware *actually* supports bindless textures for this
/// type, taking the device and driver capabilities into account.
///
/// If this type doesn't use bindless textures, then the return value from
/// this function is meaningless.
fn bindless_supported(_: &RenderDevice) -> bool {
true
}
/// label
fn label() -> Option<&'static str> {
None