Skip to content

Commit 6b38863

Browse files
committed
Request WGPU Capabilities for Non-uniform Indexing (#6995)
# Objective Fixes #6952 ## Solution - Request WGPU capabilities `SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING`, `SAMPLER_NON_UNIFORM_INDEXING` and `UNIFORM_BUFFER_AND_STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING` when corresponding features are enabled. - Add an example (`shaders/texture_binding_array`) illustrating (and testing) the use of non-uniform indexed textures and samplers. ![image](https://user-images.githubusercontent.com/16053640/209448310-defa4eae-6bcb-460d-9b3d-a3d2fad4316c.png) ## Changelog - Added new capabilities for shader validation. - Added example `shaders/texture_binding_array`.
1 parent c3a4682 commit 6b38863

File tree

5 files changed

+203
-0
lines changed

5 files changed

+203
-0
lines changed

Cargo.toml

+10
Original file line numberDiff line numberDiff line change
@@ -1301,6 +1301,16 @@ description = "A shader that shows how to reuse the core bevy PBR shading functi
13011301
category = "Shaders"
13021302
wasm = true
13031303

1304+
[[example]]
1305+
name = "texture_binding_array"
1306+
path = "examples/shader/texture_binding_array.rs"
1307+
1308+
[package.metadata.example.texture_binding_array]
1309+
name = "Texture Binding Array (Bindless Textures)"
1310+
description = "A shader that shows how to bind and sample multiple textures as a binding array (a.k.a. bindless textures)."
1311+
category = "Shaders"
1312+
wasm = false
1313+
13041314
# Stress tests
13051315
[[package.metadata.category]]
13061316
name = "Stress Tests"
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
@group(1) @binding(0)
2+
var textures: binding_array<texture_2d<f32>>;
3+
@group(1) @binding(1)
4+
var samplers: binding_array<sampler>;
5+
6+
@fragment
7+
fn fragment(
8+
#import bevy_pbr::mesh_vertex_output
9+
) -> @location(0) vec4<f32> {
10+
// Select the texture to sample from using non-uniform uv coordinates
11+
let coords = clamp(vec2<u32>(uv * 4.0), vec2<u32>(0u), vec2<u32>(3u));
12+
let index = coords.y * 4u + coords.x;
13+
let inner_uv = fract(uv * 4.0);
14+
return textureSample(textures[index], samplers[index], inner_uv);
15+
}

crates/bevy_render/src/render_resource/shader.rs

+12
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,18 @@ impl ProcessedShader {
141141
Features::SHADER_PRIMITIVE_INDEX,
142142
Capabilities::PRIMITIVE_INDEX,
143143
),
144+
(
145+
Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING,
146+
Capabilities::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING,
147+
),
148+
(
149+
Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING,
150+
Capabilities::SAMPLER_NON_UNIFORM_INDEXING,
151+
),
152+
(
153+
Features::UNIFORM_BUFFER_AND_STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING,
154+
Capabilities::UNIFORM_BUFFER_AND_STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING,
155+
),
144156
];
145157
let mut capabilities = Capabilities::empty();
146158
for (feature, capability) in CAPABILITIES {

examples/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@ Example | Description
271271
[Material Prepass](../examples/shader/shader_prepass.rs) | A shader that uses the depth texture generated in a prepass
272272
[Post Processing](../examples/shader/post_processing.rs) | A custom post processing effect, using two cameras, with one reusing the render texture of the first one
273273
[Shader Defs](../examples/shader/shader_defs.rs) | A shader that uses "shaders defs" (a bevy tool to selectively toggle parts of a shader)
274+
[Texture Binding Array (Bindless Textures)](../examples/shader/texture_binding_array.rs) | A shader that shows how to bind and sample multiple textures as a binding array (a.k.a. bindless textures).
274275

275276
## Stress Tests
276277

+165
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
//! A shader that binds several textures onto one
2+
//! `binding_array<texture<f32>>` shader binding slot and sample non-uniformly.
3+
4+
use bevy::{
5+
prelude::*,
6+
reflect::TypeUuid,
7+
render::{
8+
render_asset::RenderAssets,
9+
render_resource::{AsBindGroupError, PreparedBindGroup, *},
10+
renderer::RenderDevice,
11+
texture::FallbackImage,
12+
},
13+
};
14+
use std::num::NonZeroU32;
15+
16+
fn main() {
17+
App::new()
18+
.add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest()))
19+
.add_plugin(MaterialPlugin::<BindlessMaterial>::default())
20+
.add_startup_system(setup)
21+
.run();
22+
}
23+
24+
const MAX_TEXTURE_COUNT: usize = 16;
25+
const TILE_ID: [usize; 16] = [
26+
19, 23, 4, 33, 12, 69, 30, 48, 10, 65, 40, 47, 57, 41, 44, 46,
27+
];
28+
29+
fn setup(
30+
mut commands: Commands,
31+
mut meshes: ResMut<Assets<Mesh>>,
32+
mut materials: ResMut<Assets<BindlessMaterial>>,
33+
asset_server: Res<AssetServer>,
34+
render_device: Res<RenderDevice>,
35+
) {
36+
// check if the device support the required feature
37+
if !render_device
38+
.features()
39+
.contains(WgpuFeatures::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING)
40+
{
41+
error!(
42+
"Render device doesn't support feature \
43+
SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING, \
44+
which is required for texture binding arrays"
45+
);
46+
return;
47+
}
48+
49+
commands.spawn(Camera3dBundle {
50+
transform: Transform::from_xyz(2.0, 2.0, 2.0).looking_at(Vec3::new(0.0, 0.0, 0.0), Vec3::Y),
51+
..Default::default()
52+
});
53+
54+
// load 16 textures
55+
let textures: Vec<_> = TILE_ID
56+
.iter()
57+
.map(|id| {
58+
let path = format!("textures/rpg/tiles/generic-rpg-tile{:0>2}.png", id);
59+
asset_server.load(path)
60+
})
61+
.collect();
62+
63+
// a cube with multiple textures
64+
commands.spawn(MaterialMeshBundle {
65+
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
66+
material: materials.add(BindlessMaterial { textures }),
67+
..Default::default()
68+
});
69+
}
70+
71+
#[derive(Debug, Clone, TypeUuid)]
72+
#[uuid = "8dd2b424-45a2-4a53-ac29-7ce356b2d5fe"]
73+
struct BindlessMaterial {
74+
textures: Vec<Handle<Image>>,
75+
}
76+
77+
impl AsBindGroup for BindlessMaterial {
78+
type Data = ();
79+
80+
fn as_bind_group(
81+
&self,
82+
layout: &BindGroupLayout,
83+
render_device: &RenderDevice,
84+
image_assets: &RenderAssets<Image>,
85+
fallback_image: &FallbackImage,
86+
) -> Result<PreparedBindGroup<Self::Data>, AsBindGroupError> {
87+
// retrieve the render resources from handles
88+
let mut images = vec![];
89+
for handle in self.textures.iter().take(MAX_TEXTURE_COUNT) {
90+
match image_assets.get(handle) {
91+
Some(image) => images.push(image),
92+
None => return Err(AsBindGroupError::RetryNextUpdate),
93+
}
94+
}
95+
96+
let textures = vec![&fallback_image.texture_view; MAX_TEXTURE_COUNT];
97+
let samplers = vec![&fallback_image.sampler; MAX_TEXTURE_COUNT];
98+
99+
// convert bevy's resource types to WGPU's references
100+
let mut textures: Vec<_> = textures.into_iter().map(|texture| &**texture).collect();
101+
let mut samplers: Vec<_> = samplers.into_iter().map(|sampler| &**sampler).collect();
102+
103+
// fill in up to the first `MAX_TEXTURE_COUNT` textures and samplers to the arrays
104+
for (id, image) in images.into_iter().enumerate() {
105+
textures[id] = &*image.texture_view;
106+
samplers[id] = &*image.sampler;
107+
}
108+
109+
let bind_group = render_device.create_bind_group(&BindGroupDescriptor {
110+
label: "bindless_material_bind_group".into(),
111+
layout,
112+
entries: &[
113+
BindGroupEntry {
114+
binding: 0,
115+
resource: BindingResource::TextureViewArray(&textures[..]),
116+
},
117+
BindGroupEntry {
118+
binding: 1,
119+
resource: BindingResource::SamplerArray(&samplers[..]),
120+
},
121+
],
122+
});
123+
124+
Ok(PreparedBindGroup {
125+
bindings: vec![],
126+
bind_group,
127+
data: (),
128+
})
129+
}
130+
131+
fn bind_group_layout(render_device: &RenderDevice) -> BindGroupLayout
132+
where
133+
Self: Sized,
134+
{
135+
render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
136+
label: "bindless_material_layout".into(),
137+
entries: &[
138+
// @group(1) @binding(0) var textures: binding_array<texture_2d<f32>>;
139+
BindGroupLayoutEntry {
140+
binding: 0,
141+
visibility: ShaderStages::FRAGMENT,
142+
ty: BindingType::Texture {
143+
sample_type: TextureSampleType::Float { filterable: true },
144+
view_dimension: TextureViewDimension::D2,
145+
multisampled: false,
146+
},
147+
count: NonZeroU32::new(MAX_TEXTURE_COUNT as u32),
148+
},
149+
// @group(1) @binding(1) var samplers: binding_array<sampler>;
150+
BindGroupLayoutEntry {
151+
binding: 1,
152+
visibility: ShaderStages::FRAGMENT,
153+
ty: BindingType::Sampler(SamplerBindingType::Filtering),
154+
count: NonZeroU32::new(MAX_TEXTURE_COUNT as u32),
155+
},
156+
],
157+
})
158+
}
159+
}
160+
161+
impl Material for BindlessMaterial {
162+
fn fragment_shader() -> ShaderRef {
163+
"shaders/texture_binding_array.wgsl".into()
164+
}
165+
}

0 commit comments

Comments
 (0)