
# Objective
- Introduce a stable alternative to
[`std::any::type_name`](https://doc.rust-lang.org/std/any/fn.type_name.html).
- Rewrite of #5805 with heavy inspiration in design.
- On the path to #5830.
- Part of solving #3327.
## Solution
- Add a `TypePath` trait for static stable type path/name information.
- Add a `TypePath` derive macro.
- Add a `impl_type_path` macro for implementing internal and foreign
types in `bevy_reflect`.
---
## Changelog
- Added `TypePath` trait.
- Added `DynamicTypePath` trait and `get_type_path` method to `Reflect`.
- Added a `TypePath` derive macro.
- Added a `bevy_reflect::impl_type_path` for implementing `TypePath` on
internal and foreign types in `bevy_reflect`.
- Changed `bevy_reflect::utility::(Non)GenericTypeInfoCell` to
`(Non)GenericTypedCell<T>` which allows us to be generic over both
`TypeInfo` and `TypePath`.
- `TypePath` is now a supertrait of `Asset`, `Material` and
`Material2d`.
- `impl_reflect_struct` needs a `#[type_path = "..."]` attribute to be
specified.
- `impl_reflect_value` needs to either specify path starting with a
double colon (`::core::option::Option`) or an `in my_crate::foo`
declaration.
- Added `bevy_reflect_derive::ReflectTypePath`.
- Most uses of `Ident` in `bevy_reflect_derive` changed to use
`ReflectTypePath`.
## Migration Guide
- Implementors of `Asset`, `Material` and `Material2d` now also need to
derive `TypePath`.
- Manual implementors of `Reflect` will need to implement the new
`get_type_path` method.
## Open Questions
- [x] ~This PR currently does not migrate any usages of
`std::any::type_name` to use `bevy_reflect::TypePath` to ease the review
process. Should it?~ Migration will be left to a follow-up PR.
- [ ] This PR adds a lot of `#[derive(TypePath)]` and `T: TypePath` to
satisfy new bounds, mostly when deriving `TypeUuid`. Should we make
`TypePath` a supertrait of `TypeUuid`? [Should we remove `TypeUuid` in
favour of
`TypePath`?](2afbd85532 (r961067892)
)
183 lines
6.0 KiB
Rust
183 lines
6.0 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::{TypePath, TypeUuid},
|
|
render::{
|
|
render_asset::RenderAssets,
|
|
render_resource::{AsBindGroupError, PreparedBindGroup, *},
|
|
renderer::RenderDevice,
|
|
texture::FallbackImage,
|
|
RenderApp,
|
|
},
|
|
};
|
|
use std::{num::NonZeroU32, process::exit};
|
|
|
|
fn main() {
|
|
let mut app = App::new();
|
|
app.add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest()));
|
|
|
|
app.add_plugin(GpuFeatureSupportChecker)
|
|
.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,
|
|
];
|
|
|
|
struct GpuFeatureSupportChecker;
|
|
|
|
impl Plugin for GpuFeatureSupportChecker {
|
|
fn build(&self, _app: &mut App) {}
|
|
|
|
fn finish(&self, app: &mut App) {
|
|
let Ok(render_app) = app.get_sub_app_mut(RenderApp) else {
|
|
return
|
|
};
|
|
|
|
let render_device = render_app.world.resource::<RenderDevice>();
|
|
|
|
// Check if the device support the required feature. If not, exit the example.
|
|
// In a real application, you should setup a fallback for the missing 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"
|
|
);
|
|
exit(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
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, TypePath, 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()
|
|
}
|
|
}
|