Path: blob/main/examples/shader_advanced/texture_binding_array.rs
6849 views
//! A shader that binds several textures onto one1//! `binding_array<texture<f32>>` shader binding slot and sample non-uniformly.23use bevy::{4ecs::system::{lifetimeless::SRes, SystemParamItem},5prelude::*,6reflect::TypePath,7render::{8render_asset::RenderAssets,9render_resource::{10binding_types::{sampler, texture_2d},11*,12},13renderer::RenderDevice,14texture::{FallbackImage, GpuImage},15RenderApp, RenderStartup,16},17shader::ShaderRef,18};19use std::{num::NonZero, process::exit};2021/// This example uses a shader source file from the assets subdirectory22const SHADER_ASSET_PATH: &str = "shaders/texture_binding_array.wgsl";2324fn main() {25let mut app = App::new();26app.add_plugins((27DefaultPlugins.set(ImagePlugin::default_nearest()),28GpuFeatureSupportChecker,29MaterialPlugin::<BindlessMaterial>::default(),30))31.add_systems(Startup, setup)32.run();33}3435const MAX_TEXTURE_COUNT: usize = 16;36const TILE_ID: [usize; 16] = [3719, 23, 4, 33, 12, 69, 30, 48, 10, 65, 40, 47, 57, 41, 44, 46,38];3940struct GpuFeatureSupportChecker;4142impl Plugin for GpuFeatureSupportChecker {43fn build(&self, app: &mut App) {44let Some(render_app) = app.get_sub_app_mut(RenderApp) else {45return;46};4748render_app.add_systems(RenderStartup, verify_required_features);49}50}5152fn setup(53mut commands: Commands,54mut meshes: ResMut<Assets<Mesh>>,55mut materials: ResMut<Assets<BindlessMaterial>>,56asset_server: Res<AssetServer>,57) {58commands.spawn((59Camera3d::default(),60Transform::from_xyz(2.0, 2.0, 2.0).looking_at(Vec3::new(0.0, 0.0, 0.0), Vec3::Y),61));6263// load 16 textures64let textures: Vec<_> = TILE_ID65.iter()66.map(|id| asset_server.load(format!("textures/rpg/tiles/generic-rpg-tile{id:0>2}.png")))67.collect();6869// a cube with multiple textures70commands.spawn((71Mesh3d(meshes.add(Cuboid::default())),72MeshMaterial3d(materials.add(BindlessMaterial { textures })),73));74}7576fn verify_required_features(render_device: Res<RenderDevice>) {77// Check if the device support the required feature. If not, exit the example. In a real78// application, you should setup a fallback for the missing feature79if !render_device80.features()81.contains(WgpuFeatures::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING)82{83error!(84"Render device doesn't support feature \85SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING, \86which is required for texture binding arrays"87);88exit(1);89}90}9192#[derive(Asset, TypePath, Debug, Clone)]93struct BindlessMaterial {94textures: Vec<Handle<Image>>,95}9697impl AsBindGroup for BindlessMaterial {98type Data = ();99100type Param = (SRes<RenderAssets<GpuImage>>, SRes<FallbackImage>);101102fn as_bind_group(103&self,104layout: &BindGroupLayout,105render_device: &RenderDevice,106(image_assets, fallback_image): &mut SystemParamItem<'_, '_, Self::Param>,107) -> Result<PreparedBindGroup, AsBindGroupError> {108// retrieve the render resources from handles109let mut images = vec![];110for handle in self.textures.iter().take(MAX_TEXTURE_COUNT) {111match image_assets.get(handle) {112Some(image) => images.push(image),113None => return Err(AsBindGroupError::RetryNextUpdate),114}115}116117let fallback_image = &fallback_image.d2;118119let textures = vec![&fallback_image.texture_view; MAX_TEXTURE_COUNT];120121// convert bevy's resource types to WGPU's references122let mut textures: Vec<_> = textures.into_iter().map(|texture| &**texture).collect();123124// fill in up to the first `MAX_TEXTURE_COUNT` textures and samplers to the arrays125for (id, image) in images.into_iter().enumerate() {126textures[id] = &*image.texture_view;127}128129let bind_group = render_device.create_bind_group(130"bindless_material_bind_group",131layout,132&BindGroupEntries::sequential((&textures[..], &fallback_image.sampler)),133);134135Ok(PreparedBindGroup {136bindings: BindingResources(vec![]),137bind_group,138})139}140141fn bind_group_data(&self) -> Self::Data {}142143fn unprepared_bind_group(144&self,145_layout: &BindGroupLayout,146_render_device: &RenderDevice,147_param: &mut SystemParamItem<'_, '_, Self::Param>,148_force_no_bindless: bool,149) -> Result<UnpreparedBindGroup, AsBindGroupError> {150// We implement `as_bind_group`` directly because bindless texture151// arrays can't be owned.152// Or rather, they can be owned, but then you can't make a `&'a [&'a153// TextureView]` from a vec of them in `get_binding()`.154Err(AsBindGroupError::CreateBindGroupDirectly)155}156157fn bind_group_layout_entries(_: &RenderDevice, _: bool) -> Vec<BindGroupLayoutEntry>158where159Self: Sized,160{161BindGroupLayoutEntries::with_indices(162// The layout entries will only be visible in the fragment stage163ShaderStages::FRAGMENT,164(165// Screen texture166//167// @group(#{MATERIAL_BIND_GROUP}) @binding(0) var textures: binding_array<texture_2d<f32>>;168(1690,170texture_2d(TextureSampleType::Float { filterable: true })171.count(NonZero::<u32>::new(MAX_TEXTURE_COUNT as u32).unwrap()),172),173// Sampler174//175// @group(#{MATERIAL_BIND_GROUP}) @binding(1) var nearest_sampler: sampler;176//177// Note: as with textures, multiple samplers can also be bound178// onto one binding slot:179//180// ```181// sampler(SamplerBindingType::Filtering)182// .count(NonZero::<u32>::new(MAX_TEXTURE_COUNT as u32).unwrap()),183// ```184//185// One may need to pay attention to the limit of sampler binding186// amount on some platforms.187(1, sampler(SamplerBindingType::Filtering)),188),189)190.to_vec()191}192}193194impl Material for BindlessMaterial {195fn fragment_shader() -> ShaderRef {196SHADER_ASSET_PATH.into()197}198}199200201