mirror of
https://github.com/bevyengine/bevy
synced 2024-11-23 05:03:47 +00:00
aefe1f0739
Co-authored-by: Mike <mike.hsu@gmail.com>
167 lines
5.5 KiB
Rust
167 lines
5.5 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() {
|
|
let mut app = App::new();
|
|
app.add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest()));
|
|
|
|
let render_device = app.world.resource::<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;
|
|
}
|
|
|
|
app.add_plugin(MaterialPlugin::<BindlessMaterial>::default())
|
|
.add_systems(Startup, 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>,
|
|
) {
|
|
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{id:0>2}.png");
|
|
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];
|
|
|
|
// convert bevy's resource types to WGPU's references
|
|
let mut textures: Vec<_> = textures.into_iter().map(|texture| &**texture).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;
|
|
}
|
|
|
|
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::Sampler(&fallback_image.sampler),
|
|
},
|
|
],
|
|
});
|
|
|
|
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 nearest_sampler: sampler;
|
|
BindGroupLayoutEntry {
|
|
binding: 1,
|
|
visibility: ShaderStages::FRAGMENT,
|
|
ty: BindingType::Sampler(SamplerBindingType::Filtering),
|
|
count: None,
|
|
// Note: as textures, multiple samplers can also be bound onto one binding slot.
|
|
// One may need to pay attention to the limit of sampler binding amount on some platforms.
|
|
// count: NonZeroU32::new(MAX_TEXTURE_COUNT as u32),
|
|
},
|
|
],
|
|
})
|
|
}
|
|
}
|
|
|
|
impl Material for BindlessMaterial {
|
|
fn fragment_shader() -> ShaderRef {
|
|
"shaders/texture_binding_array.wgsl".into()
|
|
}
|
|
}
|