Path: blob/main/examples/shader_advanced/texture_binding_array.rs
9341 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: &BindGroupLayoutDescriptor,105render_device: &RenderDevice,106pipeline_cache: &PipelineCache,107(image_assets, fallback_image): &mut SystemParamItem<'_, '_, Self::Param>,108) -> Result<PreparedBindGroup, AsBindGroupError> {109// retrieve the render resources from handles110let mut images = vec![];111for handle in self.textures.iter().take(MAX_TEXTURE_COUNT) {112match image_assets.get(handle) {113Some(image) => images.push(image),114None => return Err(AsBindGroupError::RetryNextUpdate),115}116}117118let fallback_image = &fallback_image.d2;119120let textures = vec![&fallback_image.texture_view; MAX_TEXTURE_COUNT];121122// convert bevy's resource types to WGPU's references123let mut textures: Vec<_> = textures.into_iter().map(|texture| &**texture).collect();124125// fill in up to the first `MAX_TEXTURE_COUNT` textures and samplers to the arrays126for (id, image) in images.into_iter().enumerate() {127textures[id] = &*image.texture_view;128}129130let bind_group = render_device.create_bind_group(131Self::label(),132&pipeline_cache.get_bind_group_layout(layout),133&BindGroupEntries::sequential((&textures[..], &fallback_image.sampler)),134);135136Ok(PreparedBindGroup {137bindings: BindingResources(vec![]),138bind_group,139})140}141142fn bind_group_data(&self) -> Self::Data {}143144fn 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 texture152// arrays can't be owned.153// Or rather, they can be owned, but then you can't make a `&'a [&'a154// TextureView]` from a vec of them in `get_binding()`.155Err(AsBindGroupError::CreateBindGroupDirectly)156}157158fn bind_group_layout_entries(_: &RenderDevice, _: bool) -> Vec<BindGroupLayoutEntry>159where160Self: Sized,161{162BindGroupLayoutEntries::with_indices(163// The layout entries will only be visible in the fragment stage164ShaderStages::FRAGMENT,165(166// Screen texture167//168// @group(#{MATERIAL_BIND_GROUP}) @binding(0) var textures: binding_array<texture_2d<f32>>;169(1700,171texture_2d(TextureSampleType::Float { filterable: true })172.count(NonZero::<u32>::new(MAX_TEXTURE_COUNT as u32).unwrap()),173),174// Sampler175//176// @group(#{MATERIAL_BIND_GROUP}) @binding(1) var nearest_sampler: sampler;177//178// Note: as with textures, multiple samplers can also be bound179// 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 binding187// amount on some platforms.188(1, sampler(SamplerBindingType::Filtering)),189),190)191.to_vec()192}193194fn label() -> &'static str {195"bindless_material_bind_group"196}197}198199impl Material for BindlessMaterial {200fn fragment_shader() -> ShaderRef {201SHADER_ASSET_PATH.into()202}203}204205206