Skip to content

Commit dc7c8f2

Browse files
authored
Add bindless support back to ExtendedMaterial. (#18025)
PR #17898 disabled bindless support for `ExtendedMaterial`. This commit adds it back. It also adds a new example, `extended_material_bindless`, showing how to use it.
1 parent 714b4a4 commit dc7c8f2

File tree

9 files changed

+659
-113
lines changed

9 files changed

+659
-113
lines changed

Cargo.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4317,3 +4317,14 @@ name = "`no_std` Compatible Library"
43174317
description = "Example library compatible with `std` and `no_std` targets"
43184318
category = "Embedded"
43194319
wasm = true
4320+
4321+
[[example]]
4322+
name = "extended_material_bindless"
4323+
path = "examples/shader/extended_material_bindless.rs"
4324+
doc-scrape-examples = true
4325+
4326+
[package.metadata.example.extended_material_bindless]
4327+
name = "Extended Bindless Material"
4328+
description = "Demonstrates bindless `ExtendedMaterial`"
4329+
category = "Shaders"
4330+
wasm = false
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// The shader that goes with `extended_material_bindless.rs`.
2+
//
3+
// This code demonstrates how to write shaders that are compatible with both
4+
// bindless and non-bindless mode. See the `#ifdef BINDLESS` blocks.
5+
6+
#import bevy_pbr::{
7+
forward_io::{FragmentOutput, VertexOutput},
8+
mesh_bindings::mesh,
9+
pbr_fragment::pbr_input_from_standard_material,
10+
pbr_functions::{apply_pbr_lighting, main_pass_post_lighting_processing},
11+
}
12+
#import bevy_render::bindless::{bindless_samplers_filtering, bindless_textures_2d}
13+
14+
#ifdef BINDLESS
15+
#import bevy_pbr::pbr_bindings::{material_array, material_indices}
16+
#else // BINDLESS
17+
#import bevy_pbr::pbr_bindings::material
18+
#endif // BINDLESS
19+
20+
// Stores the indices of the bindless resources in the bindless resource arrays,
21+
// for the `ExampleBindlessExtension` fields.
22+
struct ExampleBindlessExtendedMaterialIndices {
23+
// The index of the `ExampleBindlessExtendedMaterial` data in
24+
// `example_extended_material`.
25+
material: u32,
26+
// The index of the texture we're going to modulate the base color with in
27+
// the `bindless_textures_2d` array.
28+
modulate_texture: u32,
29+
// The index of the sampler we're going to sample the modulated texture with
30+
// in the `bindless_samplers_filtering` array.
31+
modulate_texture_sampler: u32,
32+
}
33+
34+
// Plain data associated with this example material.
35+
struct ExampleBindlessExtendedMaterial {
36+
// The color that we multiply the base color, base color texture, and
37+
// modulated texture with.
38+
modulate_color: vec4<f32>,
39+
}
40+
41+
#ifdef BINDLESS
42+
43+
// The indices of the bindless resources in the bindless resource arrays, for
44+
// the `ExampleBindlessExtension` fields.
45+
@group(2) @binding(100) var<storage> example_extended_material_indices:
46+
array<ExampleBindlessExtendedMaterialIndices>;
47+
// An array that holds the `ExampleBindlessExtendedMaterial` plain old data,
48+
// indexed by `ExampleBindlessExtendedMaterialIndices.material`.
49+
@group(2) @binding(101) var<storage> example_extended_material:
50+
array<ExampleBindlessExtendedMaterial>;
51+
52+
#else // BINDLESS
53+
54+
// In non-bindless mode, we simply use a uniform for the plain old data.
55+
@group(2) @binding(50) var<uniform> example_extended_material: ExampleBindlessExtendedMaterial;
56+
@group(2) @binding(51) var modulate_texture: texture_2d<f32>;
57+
@group(2) @binding(52) var modulate_sampler: sampler;
58+
59+
#endif // BINDLESS
60+
61+
@fragment
62+
fn fragment(
63+
in: VertexOutput,
64+
@builtin(front_facing) is_front: bool,
65+
) -> FragmentOutput {
66+
#ifdef BINDLESS
67+
// Fetch the material slot. We'll use this in turn to fetch the bindless
68+
// indices from `example_extended_material_indices`.
69+
let slot = mesh[in.instance_index].material_and_lightmap_bind_group_slot & 0xffffu;
70+
#endif // BINDLESS
71+
72+
// Generate a `PbrInput` struct from the `StandardMaterial` bindings.
73+
var pbr_input = pbr_input_from_standard_material(in, is_front);
74+
75+
// Calculate the UV for the texture we're about to sample.
76+
#ifdef BINDLESS
77+
let uv_transform = material_array[material_indices[slot].material].uv_transform;
78+
#else // BINDLESS
79+
let uv_transform = material.uv_transform;
80+
#endif // BINDLESS
81+
let uv = (uv_transform * vec3(in.uv, 1.0)).xy;
82+
83+
// Multiply the base color by the `modulate_texture` and `modulate_color`.
84+
#ifdef BINDLESS
85+
// Notice how we fetch the texture, sampler, and plain extended material
86+
// data from the appropriate arrays.
87+
pbr_input.material.base_color *= textureSample(
88+
bindless_textures_2d[example_extended_material_indices[slot].modulate_texture],
89+
bindless_samplers_filtering[
90+
example_extended_material_indices[slot].modulate_texture_sampler
91+
],
92+
uv
93+
) * example_extended_material[example_extended_material_indices[slot].material].modulate_color;
94+
#else // BINDLESS
95+
pbr_input.material.base_color *= textureSample(modulate_texture, modulate_sampler, uv) *
96+
example_extended_material.modulate_color;
97+
#endif // BINDLESS
98+
99+
var out: FragmentOutput;
100+
// Apply lighting.
101+
out.color = apply_pbr_lighting(pbr_input);
102+
// Apply in-shader post processing (fog, alpha-premultiply, and also
103+
// tonemapping, debanding if the camera is non-HDR). Note this does not
104+
// include fullscreen postprocessing effects like bloom.
105+
out.color = main_pass_post_lighting_processing(pbr_input, out.color);
106+
return out;
107+
}

crates/bevy_pbr/src/extended_material.rs

Lines changed: 97 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1+
use alloc::borrow::Cow;
2+
13
use bevy_asset::{Asset, Handle};
24
use bevy_ecs::system::SystemParamItem;
5+
use bevy_platform_support::{collections::HashSet, hash::FixedHasher};
36
use bevy_reflect::{impl_type_path, Reflect};
47
use bevy_render::{
58
alpha::AlphaMode,
69
mesh::MeshVertexBufferLayoutRef,
710
render_resource::{
8-
AsBindGroup, AsBindGroupError, BindGroupLayout, BindlessDescriptor,
9-
BindlessSlabResourceLimit, RenderPipelineDescriptor, Shader, ShaderRef,
10-
SpecializedMeshPipelineError, UnpreparedBindGroup,
11+
AsBindGroup, AsBindGroupError, BindGroupLayout, BindGroupLayoutEntry, BindlessDescriptor,
12+
BindlessResourceType, BindlessSlabResourceLimit, RenderPipelineDescriptor, Shader,
13+
ShaderRef, SpecializedMeshPipelineError, UnpreparedBindGroup,
1114
},
1215
renderer::RenderDevice,
1316
};
@@ -156,27 +159,53 @@ impl<B: Material, E: MaterialExtension> AsBindGroup for ExtendedMaterial<B, E> {
156159
type Param = (<B as AsBindGroup>::Param, <E as AsBindGroup>::Param);
157160

158161
fn bindless_slot_count() -> Option<BindlessSlabResourceLimit> {
159-
// For now, disable bindless in `ExtendedMaterial`.
160-
if B::bindless_slot_count().is_some() && E::bindless_slot_count().is_some() {
161-
panic!("Bindless extended materials are currently unsupported")
162+
// We only enable bindless if both the base material and its extension
163+
// are bindless. If we do enable bindless, we choose the smaller of the
164+
// two slab size limits.
165+
match (B::bindless_slot_count()?, E::bindless_slot_count()?) {
166+
(BindlessSlabResourceLimit::Auto, BindlessSlabResourceLimit::Auto) => {
167+
Some(BindlessSlabResourceLimit::Auto)
168+
}
169+
(BindlessSlabResourceLimit::Auto, BindlessSlabResourceLimit::Custom(limit))
170+
| (BindlessSlabResourceLimit::Custom(limit), BindlessSlabResourceLimit::Auto) => {
171+
Some(BindlessSlabResourceLimit::Custom(limit))
172+
}
173+
(
174+
BindlessSlabResourceLimit::Custom(base_limit),
175+
BindlessSlabResourceLimit::Custom(extended_limit),
176+
) => Some(BindlessSlabResourceLimit::Custom(
177+
base_limit.min(extended_limit),
178+
)),
162179
}
163-
None
164180
}
165181

166182
fn unprepared_bind_group(
167183
&self,
168184
layout: &BindGroupLayout,
169185
render_device: &RenderDevice,
170186
(base_param, extended_param): &mut SystemParamItem<'_, '_, Self::Param>,
171-
_: bool,
187+
mut force_non_bindless: bool,
172188
) -> Result<UnpreparedBindGroup<Self::Data>, AsBindGroupError> {
189+
force_non_bindless = force_non_bindless || Self::bindless_slot_count().is_none();
190+
173191
// add together the bindings of the base material and the user material
174192
let UnpreparedBindGroup {
175193
mut bindings,
176194
data: base_data,
177-
} = B::unprepared_bind_group(&self.base, layout, render_device, base_param, true)?;
178-
let extended_bindgroup =
179-
E::unprepared_bind_group(&self.extension, layout, render_device, extended_param, true)?;
195+
} = B::unprepared_bind_group(
196+
&self.base,
197+
layout,
198+
render_device,
199+
base_param,
200+
force_non_bindless,
201+
)?;
202+
let extended_bindgroup = E::unprepared_bind_group(
203+
&self.extension,
204+
layout,
205+
render_device,
206+
extended_param,
207+
force_non_bindless,
208+
)?;
180209

181210
bindings.extend(extended_bindgroup.bindings.0);
182211

@@ -188,23 +217,72 @@ impl<B: Material, E: MaterialExtension> AsBindGroup for ExtendedMaterial<B, E> {
188217

189218
fn bind_group_layout_entries(
190219
render_device: &RenderDevice,
191-
_: bool,
192-
) -> Vec<bevy_render::render_resource::BindGroupLayoutEntry>
220+
mut force_non_bindless: bool,
221+
) -> Vec<BindGroupLayoutEntry>
193222
where
194223
Self: Sized,
195224
{
196-
// add together the bindings of the standard material and the user material
197-
let mut entries = B::bind_group_layout_entries(render_device, true);
198-
entries.extend(E::bind_group_layout_entries(render_device, true));
225+
force_non_bindless = force_non_bindless || Self::bindless_slot_count().is_none();
226+
227+
// Add together the bindings of the standard material and the user
228+
// material, skipping duplicate bindings. Duplicate bindings will occur
229+
// when bindless mode is on, because of the common bindless resource
230+
// arrays, and we need to eliminate the duplicates or `wgpu` will
231+
// complain.
232+
let mut entries = vec![];
233+
let mut seen_bindings = HashSet::<_>::with_hasher(FixedHasher);
234+
for entry in B::bind_group_layout_entries(render_device, force_non_bindless)
235+
.into_iter()
236+
.chain(E::bind_group_layout_entries(render_device, force_non_bindless).into_iter())
237+
{
238+
if seen_bindings.insert(entry.binding) {
239+
entries.push(entry);
240+
}
241+
}
199242
entries
200243
}
201244

202245
fn bindless_descriptor() -> Option<BindlessDescriptor> {
203-
if B::bindless_descriptor().is_some() && E::bindless_descriptor().is_some() {
204-
panic!("Bindless extended materials are currently unsupported")
246+
// We're going to combine the two bindless descriptors.
247+
let base_bindless_descriptor = B::bindless_descriptor()?;
248+
let extended_bindless_descriptor = E::bindless_descriptor()?;
249+
250+
// Combining the buffers and index tables is straightforward.
251+
252+
let mut buffers = base_bindless_descriptor.buffers.to_vec();
253+
let mut index_tables = base_bindless_descriptor.index_tables.to_vec();
254+
255+
buffers.extend(extended_bindless_descriptor.buffers.iter().cloned());
256+
index_tables.extend(extended_bindless_descriptor.index_tables.iter().cloned());
257+
258+
// Combining the resources is a little trickier because the resource
259+
// array is indexed by bindless index, so we have to merge the two
260+
// arrays, not just concatenate them.
261+
let max_bindless_index = base_bindless_descriptor
262+
.resources
263+
.len()
264+
.max(extended_bindless_descriptor.resources.len());
265+
let mut resources = Vec::with_capacity(max_bindless_index);
266+
for bindless_index in 0..max_bindless_index {
267+
// In the event of a conflicting bindless index, we choose the
268+
// base's binding.
269+
match base_bindless_descriptor.resources.get(bindless_index) {
270+
None | Some(&BindlessResourceType::None) => resources.push(
271+
extended_bindless_descriptor
272+
.resources
273+
.get(bindless_index)
274+
.copied()
275+
.unwrap_or(BindlessResourceType::None),
276+
),
277+
Some(&resource_type) => resources.push(resource_type),
278+
}
205279
}
206280

207-
None
281+
Some(BindlessDescriptor {
282+
resources: Cow::Owned(resources),
283+
buffers: Cow::Owned(buffers),
284+
index_tables: Cow::Owned(index_tables),
285+
})
208286
}
209287
}
210288

0 commit comments

Comments
 (0)