1use 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,
17 },
18};
19use std::{num::NonZero, process::exit};
20
21const SHADER_ASSET_PATH: &str = "shaders/texture_binding_array.wgsl";
23
24fn main() {
25 let mut app = App::new();
26 app.add_plugins((
27 DefaultPlugins.set(ImagePlugin::default_nearest()),
28 GpuFeatureSupportChecker,
29 MaterialPlugin::<BindlessMaterial>::default(),
30 ))
31 .add_systems(Startup, setup)
32 .run();
33}
34
35const MAX_TEXTURE_COUNT: usize = 16;
36const TILE_ID: [usize; 16] = [
37 19, 23, 4, 33, 12, 69, 30, 48, 10, 65, 40, 47, 57, 41, 44, 46,
38];
39
40struct GpuFeatureSupportChecker;
41
42impl Plugin for GpuFeatureSupportChecker {
43 fn build(&self, _app: &mut App) {}
44
45 fn finish(&self, app: &mut App) {
46 let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
47 return;
48 };
49
50 let render_device = render_app.world().resource::<RenderDevice>();
51
52 if !render_device
55 .features()
56 .contains(WgpuFeatures::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING)
57 {
58 error!(
59 "Render device doesn't support feature \
60SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING, \
61which is required for texture binding arrays"
62 );
63 exit(1);
64 }
65 }
66}
67
68fn setup(
69 mut commands: Commands,
70 mut meshes: ResMut<Assets<Mesh>>,
71 mut materials: ResMut<Assets<BindlessMaterial>>,
72 asset_server: Res<AssetServer>,
73) {
74 commands.spawn((
75 Camera3d::default(),
76 Transform::from_xyz(2.0, 2.0, 2.0).looking_at(Vec3::new(0.0, 0.0, 0.0), Vec3::Y),
77 ));
78
79 let textures: Vec<_> = TILE_ID
81 .iter()
82 .map(|id| asset_server.load(format!("textures/rpg/tiles/generic-rpg-tile{id:0>2}.png")))
83 .collect();
84
85 commands.spawn((
87 Mesh3d(meshes.add(Cuboid::default())),
88 MeshMaterial3d(materials.add(BindlessMaterial { textures })),
89 ));
90}
91
92#[derive(Asset, TypePath, Debug, Clone)]
93struct BindlessMaterial {
94 textures: Vec<Handle<Image>>,
95}
96
97impl AsBindGroup for BindlessMaterial {
98 type Data = ();
99
100 type Param = (SRes<RenderAssets<GpuImage>>, SRes<FallbackImage>);
101
102 fn as_bind_group(
103 &self,
104 layout: &BindGroupLayout,
105 render_device: &RenderDevice,
106 (image_assets, fallback_image): &mut SystemParamItem<'_, '_, Self::Param>,
107 ) -> Result<PreparedBindGroup<Self::Data>, AsBindGroupError> {
108 let mut images = vec![];
110 for handle in self.textures.iter().take(MAX_TEXTURE_COUNT) {
111 match image_assets.get(handle) {
112 Some(image) => images.push(image),
113 None => return Err(AsBindGroupError::RetryNextUpdate),
114 }
115 }
116
117 let fallback_image = &fallback_image.d2;
118
119 let textures = vec![&fallback_image.texture_view; MAX_TEXTURE_COUNT];
120
121 let mut textures: Vec<_> = textures.into_iter().map(|texture| &**texture).collect();
123
124 for (id, image) in images.into_iter().enumerate() {
126 textures[id] = &*image.texture_view;
127 }
128
129 let bind_group = render_device.create_bind_group(
130 "bindless_material_bind_group",
131 layout,
132 &BindGroupEntries::sequential((&textures[..], &fallback_image.sampler)),
133 );
134
135 Ok(PreparedBindGroup {
136 bindings: BindingResources(vec![]),
137 bind_group,
138 data: (),
139 })
140 }
141
142 fn unprepared_bind_group(
143 &self,
144 _layout: &BindGroupLayout,
145 _render_device: &RenderDevice,
146 _param: &mut SystemParamItem<'_, '_, Self::Param>,
147 _force_no_bindless: bool,
148 ) -> Result<UnpreparedBindGroup<Self::Data>, AsBindGroupError> {
149 Err(AsBindGroupError::CreateBindGroupDirectly)
154 }
155
156 fn bind_group_layout_entries(_: &RenderDevice, _: bool) -> Vec<BindGroupLayoutEntry>
157 where
158 Self: Sized,
159 {
160 BindGroupLayoutEntries::with_indices(
161 ShaderStages::FRAGMENT,
163 (
164 (
168 0,
169 texture_2d(TextureSampleType::Float { filterable: true })
170 .count(NonZero::<u32>::new(MAX_TEXTURE_COUNT as u32).unwrap()),
171 ),
172 (1, sampler(SamplerBindingType::Filtering)),
187 ),
188 )
189 .to_vec()
190 }
191}
192
193impl Material for BindlessMaterial {
194 fn fragment_shader() -> ShaderRef {
195 SHADER_ASSET_PATH.into()
196 }
197}