forked from bevyengine/bevy
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Request WGPU Capabilities for Non-uniform Indexing (bevyengine#6995)
# 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
Showing
5 changed files
with
203 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} | ||
} |