mirror of
https://github.com/bevyengine/bevy
synced 2024-11-21 20:23:28 +00:00
Request WGPU Capabilities for Non-uniform Indexing (#6995)
# Objective Fixes #6952 ## Solution - Request WGPU capabilities `SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING`, `SAMPLER_NON_UNIFORM_INDEXING` and `UNIFORM_BUFFER_AND_STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING` when corresponding features are enabled. - Add an example (`shaders/texture_binding_array`) illustrating (and testing) the use of non-uniform indexed textures and samplers. ![image](https://user-images.githubusercontent.com/16053640/209448310-defa4eae-6bcb-460d-9b3d-a3d2fad4316c.png) ## Changelog - Added new capabilities for shader validation. - Added example `shaders/texture_binding_array`.
This commit is contained in:
parent
c3a46822e1
commit
6b38863313
5 changed files with 203 additions and 0 deletions
10
Cargo.toml
10
Cargo.toml
|
@ -1301,6 +1301,16 @@ description = "A shader that shows how to reuse the core bevy PBR shading functi
|
|||
category = "Shaders"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "texture_binding_array"
|
||||
path = "examples/shader/texture_binding_array.rs"
|
||||
|
||||
[package.metadata.example.texture_binding_array]
|
||||
name = "Texture Binding Array (Bindless Textures)"
|
||||
description = "A shader that shows how to bind and sample multiple textures as a binding array (a.k.a. bindless textures)."
|
||||
category = "Shaders"
|
||||
wasm = false
|
||||
|
||||
# Stress tests
|
||||
[[package.metadata.category]]
|
||||
name = "Stress Tests"
|
||||
|
|
15
assets/shaders/texture_binding_array.wgsl
Normal file
15
assets/shaders/texture_binding_array.wgsl
Normal file
|
@ -0,0 +1,15 @@
|
|||
@group(1) @binding(0)
|
||||
var textures: binding_array<texture_2d<f32>>;
|
||||
@group(1) @binding(1)
|
||||
var samplers: binding_array<sampler>;
|
||||
|
||||
@fragment
|
||||
fn fragment(
|
||||
#import bevy_pbr::mesh_vertex_output
|
||||
) -> @location(0) vec4<f32> {
|
||||
// Select the texture to sample from using non-uniform uv coordinates
|
||||
let coords = clamp(vec2<u32>(uv * 4.0), vec2<u32>(0u), vec2<u32>(3u));
|
||||
let index = coords.y * 4u + coords.x;
|
||||
let inner_uv = fract(uv * 4.0);
|
||||
return textureSample(textures[index], samplers[index], inner_uv);
|
||||
}
|
|
@ -141,6 +141,18 @@ impl ProcessedShader {
|
|||
Features::SHADER_PRIMITIVE_INDEX,
|
||||
Capabilities::PRIMITIVE_INDEX,
|
||||
),
|
||||
(
|
||||
Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING,
|
||||
Capabilities::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING,
|
||||
),
|
||||
(
|
||||
Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING,
|
||||
Capabilities::SAMPLER_NON_UNIFORM_INDEXING,
|
||||
),
|
||||
(
|
||||
Features::UNIFORM_BUFFER_AND_STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING,
|
||||
Capabilities::UNIFORM_BUFFER_AND_STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING,
|
||||
),
|
||||
];
|
||||
let mut capabilities = Capabilities::empty();
|
||||
for (feature, capability) in CAPABILITIES {
|
||||
|
|
|
@ -271,6 +271,7 @@ Example | Description
|
|||
[Material Prepass](../examples/shader/shader_prepass.rs) | A shader that uses the depth texture generated in a prepass
|
||||
[Post Processing](../examples/shader/post_processing.rs) | A custom post processing effect, using two cameras, with one reusing the render texture of the first one
|
||||
[Shader Defs](../examples/shader/shader_defs.rs) | A shader that uses "shaders defs" (a bevy tool to selectively toggle parts of a shader)
|
||||
[Texture Binding Array (Bindless Textures)](../examples/shader/texture_binding_array.rs) | A shader that shows how to bind and sample multiple textures as a binding array (a.k.a. bindless textures).
|
||||
|
||||
## Stress Tests
|
||||
|
||||
|
|
165
examples/shader/texture_binding_array.rs
Normal file
165
examples/shader/texture_binding_array.rs
Normal file
|
@ -0,0 +1,165 @@
|
|||
//! 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()
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue