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.  ## Changelog - Added new capabilities for shader validation. - Added example `shaders/texture_binding_array`.
This commit is contained in:
parent
c3a46822e1
commit
6b38863313
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"
|
category = "Shaders"
|
||||||
wasm = true
|
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
|
# Stress tests
|
||||||
[[package.metadata.category]]
|
[[package.metadata.category]]
|
||||||
name = "Stress Tests"
|
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,
|
Features::SHADER_PRIMITIVE_INDEX,
|
||||||
Capabilities::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();
|
let mut capabilities = Capabilities::empty();
|
||||||
for (feature, capability) in CAPABILITIES {
|
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
|
[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
|
[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)
|
[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
|
## 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 New Issue
Block a user