mirror of
https://github.com/bevyengine/bevy
synced 2024-11-26 06:30:19 +00:00
166 lines
5.4 KiB
Rust
166 lines
5.4 KiB
Rust
|
//! A shader that binds several textures onto one
|
||
|
//! `binding_array<texture<f32>>` shader binding slot and sample non-uniformly.
|
||
|
|
||
|
use bevy::{
|
||
|
prelude::*,
|
||
|
reflect::TypeUuid,
|
||
|
render::{
|
||
|
render_asset::RenderAssets,
|
||
|
render_resource::{AsBindGroupError, PreparedBindGroup, *},
|
||
|
renderer::RenderDevice,
|
||
|
texture::FallbackImage,
|
||
|
},
|
||
|
};
|
||
|
use std::num::NonZeroU32;
|
||
|
|
||
|
fn main() {
|
||
|
App::new()
|
||
|
.add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest()))
|
||
|
.add_plugin(MaterialPlugin::<BindlessMaterial>::default())
|
||
|
.add_startup_system(setup)
|
||
|
.run();
|
||
|
}
|
||
|
|
||
|
const MAX_TEXTURE_COUNT: usize = 16;
|
||
|
const TILE_ID: [usize; 16] = [
|
||
|
19, 23, 4, 33, 12, 69, 30, 48, 10, 65, 40, 47, 57, 41, 44, 46,
|
||
|
];
|
||
|
|
||
|
fn setup(
|
||
|
mut commands: Commands,
|
||
|
mut meshes: ResMut<Assets<Mesh>>,
|
||
|
mut materials: ResMut<Assets<BindlessMaterial>>,
|
||
|
asset_server: Res<AssetServer>,
|
||
|
render_device: Res<RenderDevice>,
|
||
|
) {
|
||
|
// check if the device support the required feature
|
||
|
if !render_device
|
||
|
.features()
|
||
|
.contains(WgpuFeatures::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING)
|
||
|
{
|
||
|
error!(
|
||
|
"Render device doesn't support feature \
|
||
|
SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING, \
|
||
|
which is required for texture binding arrays"
|
||
|
);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
commands.spawn(Camera3dBundle {
|
||
|
transform: Transform::from_xyz(2.0, 2.0, 2.0).looking_at(Vec3::new(0.0, 0.0, 0.0), Vec3::Y),
|
||
|
..Default::default()
|
||
|
});
|
||
|
|
||
|
// load 16 textures
|
||
|
let textures: Vec<_> = TILE_ID
|
||
|
.iter()
|
||
|
.map(|id| {
|
||
|
let path = format!("textures/rpg/tiles/generic-rpg-tile{:0>2}.png", id);
|
||
|
asset_server.load(path)
|
||
|
})
|
||
|
.collect();
|
||
|
|
||
|
// a cube with multiple textures
|
||
|
commands.spawn(MaterialMeshBundle {
|
||
|
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
|
||
|
material: materials.add(BindlessMaterial { textures }),
|
||
|
..Default::default()
|
||
|
});
|
||
|
}
|
||
|
|
||
|
#[derive(Debug, Clone, TypeUuid)]
|
||
|
#[uuid = "8dd2b424-45a2-4a53-ac29-7ce356b2d5fe"]
|
||
|
struct BindlessMaterial {
|
||
|
textures: Vec<Handle<Image>>,
|
||
|
}
|
||
|
|
||
|
impl AsBindGroup for BindlessMaterial {
|
||
|
type Data = ();
|
||
|
|
||
|
fn as_bind_group(
|
||
|
&self,
|
||
|
layout: &BindGroupLayout,
|
||
|
render_device: &RenderDevice,
|
||
|
image_assets: &RenderAssets<Image>,
|
||
|
fallback_image: &FallbackImage,
|
||
|
) -> Result<PreparedBindGroup<Self::Data>, AsBindGroupError> {
|
||
|
// retrieve the render resources from handles
|
||
|
let mut images = vec![];
|
||
|
for handle in self.textures.iter().take(MAX_TEXTURE_COUNT) {
|
||
|
match image_assets.get(handle) {
|
||
|
Some(image) => images.push(image),
|
||
|
None => return Err(AsBindGroupError::RetryNextUpdate),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
let textures = vec![&fallback_image.texture_view; MAX_TEXTURE_COUNT];
|
||
|
let samplers = vec![&fallback_image.sampler; MAX_TEXTURE_COUNT];
|
||
|
|
||
|
// convert bevy's resource types to WGPU's references
|
||
|
let mut textures: Vec<_> = textures.into_iter().map(|texture| &**texture).collect();
|
||
|
let mut samplers: Vec<_> = samplers.into_iter().map(|sampler| &**sampler).collect();
|
||
|
|
||
|
// fill in up to the first `MAX_TEXTURE_COUNT` textures and samplers to the arrays
|
||
|
for (id, image) in images.into_iter().enumerate() {
|
||
|
textures[id] = &*image.texture_view;
|
||
|
samplers[id] = &*image.sampler;
|
||
|
}
|
||
|
|
||
|
let bind_group = render_device.create_bind_group(&BindGroupDescriptor {
|
||
|
label: "bindless_material_bind_group".into(),
|
||
|
layout,
|
||
|
entries: &[
|
||
|
BindGroupEntry {
|
||
|
binding: 0,
|
||
|
resource: BindingResource::TextureViewArray(&textures[..]),
|
||
|
},
|
||
|
BindGroupEntry {
|
||
|
binding: 1,
|
||
|
resource: BindingResource::SamplerArray(&samplers[..]),
|
||
|
},
|
||
|
],
|
||
|
});
|
||
|
|
||
|
Ok(PreparedBindGroup {
|
||
|
bindings: vec![],
|
||
|
bind_group,
|
||
|
data: (),
|
||
|
})
|
||
|
}
|
||
|
|
||
|
fn bind_group_layout(render_device: &RenderDevice) -> BindGroupLayout
|
||
|
where
|
||
|
Self: Sized,
|
||
|
{
|
||
|
render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
||
|
label: "bindless_material_layout".into(),
|
||
|
entries: &[
|
||
|
// @group(1) @binding(0) var textures: binding_array<texture_2d<f32>>;
|
||
|
BindGroupLayoutEntry {
|
||
|
binding: 0,
|
||
|
visibility: ShaderStages::FRAGMENT,
|
||
|
ty: BindingType::Texture {
|
||
|
sample_type: TextureSampleType::Float { filterable: true },
|
||
|
view_dimension: TextureViewDimension::D2,
|
||
|
multisampled: false,
|
||
|
},
|
||
|
count: NonZeroU32::new(MAX_TEXTURE_COUNT as u32),
|
||
|
},
|
||
|
// @group(1) @binding(1) var samplers: binding_array<sampler>;
|
||
|
BindGroupLayoutEntry {
|
||
|
binding: 1,
|
||
|
visibility: ShaderStages::FRAGMENT,
|
||
|
ty: BindingType::Sampler(SamplerBindingType::Filtering),
|
||
|
count: NonZeroU32::new(MAX_TEXTURE_COUNT as u32),
|
||
|
},
|
||
|
],
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl Material for BindlessMaterial {
|
||
|
fn fragment_shader() -> ShaderRef {
|
||
|
"shaders/texture_binding_array.wgsl".into()
|
||
|
}
|
||
|
}
|