Skip to content

Commit

Permalink
Request WGPU Capabilities for Non-uniform Indexing (bevyengine#6995)
Browse files Browse the repository at this point in the history
# Objective

Fixes bevyengine#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`.
  • Loading branch information
cryscan authored and ItsDoot committed Feb 1, 2023
1 parent 0d98763 commit a8252f4
Show file tree
Hide file tree
Showing 5 changed files with 203 additions and 0 deletions.
10 changes: 10 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
15 changes: 15 additions & 0 deletions assets/shaders/texture_binding_array.wgsl
Original file line number Diff line number Diff line change
@@ -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);
}
12 changes: 12 additions & 0 deletions crates/bevy_render/src/render_resource/shader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
165 changes: 165 additions & 0 deletions examples/shader/texture_binding_array.rs
Original file line number Diff line number Diff line change
@@ -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()
}
}

0 comments on commit a8252f4

Please sign in to comment.