Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/shader_advanced/texture_binding_array.rs
6849 views
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
ecs::system::{lifetimeless::SRes, SystemParamItem},
6
prelude::*,
7
reflect::TypePath,
8
render::{
9
render_asset::RenderAssets,
10
render_resource::{
11
binding_types::{sampler, texture_2d},
12
*,
13
},
14
renderer::RenderDevice,
15
texture::{FallbackImage, GpuImage},
16
RenderApp, RenderStartup,
17
},
18
shader::ShaderRef,
19
};
20
use std::{num::NonZero, process::exit};
21
22
/// This example uses a shader source file from the assets subdirectory
23
const SHADER_ASSET_PATH: &str = "shaders/texture_binding_array.wgsl";
24
25
fn main() {
26
let mut app = App::new();
27
app.add_plugins((
28
DefaultPlugins.set(ImagePlugin::default_nearest()),
29
GpuFeatureSupportChecker,
30
MaterialPlugin::<BindlessMaterial>::default(),
31
))
32
.add_systems(Startup, setup)
33
.run();
34
}
35
36
const MAX_TEXTURE_COUNT: usize = 16;
37
const TILE_ID: [usize; 16] = [
38
19, 23, 4, 33, 12, 69, 30, 48, 10, 65, 40, 47, 57, 41, 44, 46,
39
];
40
41
struct GpuFeatureSupportChecker;
42
43
impl Plugin for GpuFeatureSupportChecker {
44
fn build(&self, app: &mut App) {
45
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
46
return;
47
};
48
49
render_app.add_systems(RenderStartup, verify_required_features);
50
}
51
}
52
53
fn setup(
54
mut commands: Commands,
55
mut meshes: ResMut<Assets<Mesh>>,
56
mut materials: ResMut<Assets<BindlessMaterial>>,
57
asset_server: Res<AssetServer>,
58
) {
59
commands.spawn((
60
Camera3d::default(),
61
Transform::from_xyz(2.0, 2.0, 2.0).looking_at(Vec3::new(0.0, 0.0, 0.0), Vec3::Y),
62
));
63
64
// load 16 textures
65
let textures: Vec<_> = TILE_ID
66
.iter()
67
.map(|id| asset_server.load(format!("textures/rpg/tiles/generic-rpg-tile{id:0>2}.png")))
68
.collect();
69
70
// a cube with multiple textures
71
commands.spawn((
72
Mesh3d(meshes.add(Cuboid::default())),
73
MeshMaterial3d(materials.add(BindlessMaterial { textures })),
74
));
75
}
76
77
fn verify_required_features(render_device: Res<RenderDevice>) {
78
// Check if the device support the required feature. If not, exit the example. In a real
79
// application, you should setup a fallback for the missing feature
80
if !render_device
81
.features()
82
.contains(WgpuFeatures::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING)
83
{
84
error!(
85
"Render device doesn't support feature \
86
SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING, \
87
which is required for texture binding arrays"
88
);
89
exit(1);
90
}
91
}
92
93
#[derive(Asset, TypePath, Debug, Clone)]
94
struct BindlessMaterial {
95
textures: Vec<Handle<Image>>,
96
}
97
98
impl AsBindGroup for BindlessMaterial {
99
type Data = ();
100
101
type Param = (SRes<RenderAssets<GpuImage>>, SRes<FallbackImage>);
102
103
fn as_bind_group(
104
&self,
105
layout: &BindGroupLayout,
106
render_device: &RenderDevice,
107
(image_assets, fallback_image): &mut SystemParamItem<'_, '_, Self::Param>,
108
) -> Result<PreparedBindGroup, AsBindGroupError> {
109
// retrieve the render resources from handles
110
let mut images = vec![];
111
for handle in self.textures.iter().take(MAX_TEXTURE_COUNT) {
112
match image_assets.get(handle) {
113
Some(image) => images.push(image),
114
None => return Err(AsBindGroupError::RetryNextUpdate),
115
}
116
}
117
118
let fallback_image = &fallback_image.d2;
119
120
let textures = vec![&fallback_image.texture_view; MAX_TEXTURE_COUNT];
121
122
// convert bevy's resource types to WGPU's references
123
let mut textures: Vec<_> = textures.into_iter().map(|texture| &**texture).collect();
124
125
// fill in up to the first `MAX_TEXTURE_COUNT` textures and samplers to the arrays
126
for (id, image) in images.into_iter().enumerate() {
127
textures[id] = &*image.texture_view;
128
}
129
130
let bind_group = render_device.create_bind_group(
131
"bindless_material_bind_group",
132
layout,
133
&BindGroupEntries::sequential((&textures[..], &fallback_image.sampler)),
134
);
135
136
Ok(PreparedBindGroup {
137
bindings: BindingResources(vec![]),
138
bind_group,
139
})
140
}
141
142
fn bind_group_data(&self) -> Self::Data {}
143
144
fn unprepared_bind_group(
145
&self,
146
_layout: &BindGroupLayout,
147
_render_device: &RenderDevice,
148
_param: &mut SystemParamItem<'_, '_, Self::Param>,
149
_force_no_bindless: bool,
150
) -> Result<UnpreparedBindGroup, AsBindGroupError> {
151
// We implement `as_bind_group`` directly because bindless texture
152
// arrays can't be owned.
153
// Or rather, they can be owned, but then you can't make a `&'a [&'a
154
// TextureView]` from a vec of them in `get_binding()`.
155
Err(AsBindGroupError::CreateBindGroupDirectly)
156
}
157
158
fn bind_group_layout_entries(_: &RenderDevice, _: bool) -> Vec<BindGroupLayoutEntry>
159
where
160
Self: Sized,
161
{
162
BindGroupLayoutEntries::with_indices(
163
// The layout entries will only be visible in the fragment stage
164
ShaderStages::FRAGMENT,
165
(
166
// Screen texture
167
//
168
// @group(#{MATERIAL_BIND_GROUP}) @binding(0) var textures: binding_array<texture_2d<f32>>;
169
(
170
0,
171
texture_2d(TextureSampleType::Float { filterable: true })
172
.count(NonZero::<u32>::new(MAX_TEXTURE_COUNT as u32).unwrap()),
173
),
174
// Sampler
175
//
176
// @group(#{MATERIAL_BIND_GROUP}) @binding(1) var nearest_sampler: sampler;
177
//
178
// Note: as with textures, multiple samplers can also be bound
179
// onto one binding slot:
180
//
181
// ```
182
// sampler(SamplerBindingType::Filtering)
183
// .count(NonZero::<u32>::new(MAX_TEXTURE_COUNT as u32).unwrap()),
184
// ```
185
//
186
// One may need to pay attention to the limit of sampler binding
187
// amount on some platforms.
188
(1, sampler(SamplerBindingType::Filtering)),
189
),
190
)
191
.to_vec()
192
}
193
}
194
195
impl Material for BindlessMaterial {
196
fn fragment_shader() -> ShaderRef {
197
SHADER_ASSET_PATH.into()
198
}
199
}
200
201