Use global binding arrays for bindless resources. (#17898)
Currently, Bevy's implementation of bindless resources is rather unusual: every binding in an object that implements `AsBindGroup` (most commonly, a material) becomes its own separate binding array in the shader. This is inefficient for two reasons: 1. If multiple materials reference the same texture or other resource, the reference to that resource will be duplicated many times. This increases `wgpu` validation overhead. 2. It creates many unused binding array slots. This increases `wgpu` and driver overhead and makes it easier to hit limits on APIs that `wgpu` currently imposes tight resource limits on, like Metal. This PR fixes these issues by switching Bevy to use the standard approach in GPU-driven renderers, in which resources are de-duplicated and passed as global arrays, one for each type of resource. Along the way, this patch introduces per-platform resource limits and bumps them from 16 resources per binding array to 64 resources per bind group on Metal and 2048 resources per bind group on other platforms. (Note that the number of resources per *binding array* isn't the same as the number of resources per *bind group*; as it currently stands, if all the PBR features are turned on, Bevy could pack as many as 496 resources into a single slab.) The limits have been increased because `wgpu` now has universal support for partially-bound binding arrays, which mean that we no longer need to fill the binding arrays with fallback resources on Direct3D 12. The `#[bindless(LIMIT)]` declaration when deriving `AsBindGroup` can now simply be written `#[bindless]` in order to have Bevy choose a default limit size for the current platform. Custom limits are still available with the new `#[bindless(limit(LIMIT))]` syntax: e.g. `#[bindless(limit(8))]`. The material bind group allocator has been completely rewritten. Now there are two allocators: one for bindless materials and one for non-bindless materials. The new non-bindless material allocator simply maintains a 1:1 mapping from material to bind group. The new bindless material allocator maintains a list of slabs and allocates materials into slabs on a first-fit basis. This unfortunately makes its performance O(number of resources per object * number of slabs), but the number of slabs is likely to be low, and it's planned to become even lower in the future with `wgpu` improvements. Resources are de-duplicated with in a slab and reference counted. So, for instance, if multiple materials refer to the same texture, that texture will exist only once in the appropriate binding array. To support these new features, this patch adds the concept of a *bindless descriptor* to the `AsBindGroup` trait. The bindless descriptor allows the material bind group allocator to probe the layout of the material, now that an array of `BindGroupLayoutEntry` records is insufficient to describe the group. The `#[derive(AsBindGroup)]` has been heavily modified to support the new features. The most important user-facing change to that macro is that the struct-level `uniform` attribute, `#[uniform(BINDING_NUMBER, StandardMaterial)]`, now reads `#[uniform(BINDLESS_INDEX, MATERIAL_UNIFORM_TYPE, binding_array(BINDING_NUMBER)]`, allowing the material to specify the binding number for the binding array that holds the uniform data. To make this patch simpler, I removed support for bindless `ExtendedMaterial`s, as well as field-level bindless uniform and storage buffers. I intend to add back support for these as a follow-up. Because they aren't in any released Bevy version yet, I figured this was OK. Finally, this patch updates `StandardMaterial` for the new bindless changes. Generally, code throughout the PBR shaders that looked like `base_color_texture[slot]` now looks like `bindless_2d_textures[material_indices[slot].base_color_texture]`. This patch fixes a system hang that I experienced on the [Caldera test] when running with `caldera --random-materials --texture-count 100`. The time per frame is around 19.75 ms, down from 154.2 ms in Bevy 0.14: a 7.8× speedup. [Caldera test]: https://github.com/DGriffin91/bevy_caldera_scene
This commit is contained in:
parent
6bcb2b633b
commit
28441337bb
@ -1,14 +1,22 @@
|
||||
#import bevy_pbr::forward_io::VertexOutput
|
||||
#import bevy_pbr::mesh_bindings::mesh
|
||||
#import bevy_render::bindless::{bindless_samplers_filtering, bindless_textures_2d}
|
||||
|
||||
struct Color {
|
||||
base_color: vec4<f32>,
|
||||
}
|
||||
|
||||
// This structure is a mapping from bindless index to the index in the
|
||||
// appropriate slab
|
||||
struct MaterialBindings {
|
||||
material: u32, // 0
|
||||
color_texture: u32, // 1
|
||||
color_texture_sampler: u32, // 2
|
||||
}
|
||||
|
||||
#ifdef BINDLESS
|
||||
@group(2) @binding(0) var<storage> material_color: binding_array<Color, 4>;
|
||||
@group(2) @binding(1) var material_color_texture: binding_array<texture_2d<f32>, 4>;
|
||||
@group(2) @binding(2) var material_color_sampler: binding_array<sampler, 4>;
|
||||
@group(2) @binding(0) var<storage> materials: binding_array<MaterialBindings>;
|
||||
@group(2) @binding(10) var<storage> material_color: binding_array<Color>;
|
||||
#else // BINDLESS
|
||||
@group(2) @binding(0) var<uniform> material_color: Color;
|
||||
@group(2) @binding(1) var material_color_texture: texture_2d<f32>;
|
||||
@ -19,15 +27,15 @@ struct Color {
|
||||
fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
#ifdef BINDLESS
|
||||
let slot = mesh[in.instance_index].material_and_lightmap_bind_group_slot & 0xffffu;
|
||||
let base_color = material_color[slot].base_color;
|
||||
let base_color = material_color[materials[slot].material].base_color;
|
||||
#else // BINDLESS
|
||||
let base_color = material_color.base_color;
|
||||
#endif // BINDLESS
|
||||
|
||||
return base_color * textureSampleLevel(
|
||||
#ifdef BINDLESS
|
||||
material_color_texture[slot],
|
||||
material_color_sampler[slot],
|
||||
bindless_textures_2d[materials[slot].color_texture],
|
||||
bindless_samplers_filtering[materials[slot].color_texture_sampler],
|
||||
#else // BINDLESS
|
||||
material_color_texture,
|
||||
material_color_sampler,
|
||||
|
@ -5,8 +5,9 @@ use bevy_render::{
|
||||
alpha::AlphaMode,
|
||||
mesh::MeshVertexBufferLayoutRef,
|
||||
render_resource::{
|
||||
AsBindGroup, AsBindGroupError, BindGroupLayout, RenderPipelineDescriptor, Shader,
|
||||
ShaderRef, SpecializedMeshPipelineError, UnpreparedBindGroup,
|
||||
AsBindGroup, AsBindGroupError, BindGroupLayout, BindlessDescriptor,
|
||||
BindlessSlabResourceLimit, RenderPipelineDescriptor, Shader, ShaderRef,
|
||||
SpecializedMeshPipelineError, UnpreparedBindGroup,
|
||||
},
|
||||
renderer::RenderDevice,
|
||||
};
|
||||
@ -153,13 +154,12 @@ impl<B: Material, E: MaterialExtension> AsBindGroup for ExtendedMaterial<B, E> {
|
||||
type Data = (<B as AsBindGroup>::Data, <E as AsBindGroup>::Data);
|
||||
type Param = (<B as AsBindGroup>::Param, <E as AsBindGroup>::Param);
|
||||
|
||||
fn bindless_slot_count() -> Option<u32> {
|
||||
match (B::bindless_slot_count(), E::bindless_slot_count()) {
|
||||
(Some(base_bindless_slot_count), Some(extension_bindless_slot_count)) => {
|
||||
Some(base_bindless_slot_count.min(extension_bindless_slot_count))
|
||||
}
|
||||
_ => None,
|
||||
fn bindless_slot_count() -> Option<BindlessSlabResourceLimit> {
|
||||
// For now, disable bindless in `ExtendedMaterial`.
|
||||
if B::bindless_slot_count().is_some() && E::bindless_slot_count().is_some() {
|
||||
panic!("Bindless extended materials are currently unsupported")
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn unprepared_bind_group(
|
||||
@ -167,30 +167,15 @@ impl<B: Material, E: MaterialExtension> AsBindGroup for ExtendedMaterial<B, E> {
|
||||
layout: &BindGroupLayout,
|
||||
render_device: &RenderDevice,
|
||||
(base_param, extended_param): &mut SystemParamItem<'_, '_, Self::Param>,
|
||||
mut force_no_bindless: bool,
|
||||
_: bool,
|
||||
) -> Result<UnpreparedBindGroup<Self::Data>, AsBindGroupError> {
|
||||
// Only allow bindless mode if both the base material and the extension
|
||||
// support it.
|
||||
force_no_bindless = force_no_bindless || Self::bindless_slot_count().is_none();
|
||||
|
||||
// add together the bindings of the base material and the user material
|
||||
let UnpreparedBindGroup {
|
||||
mut bindings,
|
||||
data: base_data,
|
||||
} = B::unprepared_bind_group(
|
||||
&self.base,
|
||||
layout,
|
||||
render_device,
|
||||
base_param,
|
||||
force_no_bindless,
|
||||
)?;
|
||||
let extended_bindgroup = E::unprepared_bind_group(
|
||||
&self.extension,
|
||||
layout,
|
||||
render_device,
|
||||
extended_param,
|
||||
force_no_bindless,
|
||||
)?;
|
||||
} = B::unprepared_bind_group(&self.base, layout, render_device, base_param, true)?;
|
||||
let extended_bindgroup =
|
||||
E::unprepared_bind_group(&self.extension, layout, render_device, extended_param, true)?;
|
||||
|
||||
bindings.extend(extended_bindgroup.bindings.0);
|
||||
|
||||
@ -202,23 +187,24 @@ impl<B: Material, E: MaterialExtension> AsBindGroup for ExtendedMaterial<B, E> {
|
||||
|
||||
fn bind_group_layout_entries(
|
||||
render_device: &RenderDevice,
|
||||
mut force_no_bindless: bool,
|
||||
_: bool,
|
||||
) -> Vec<bevy_render::render_resource::BindGroupLayoutEntry>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
// Only allow bindless mode if both the base material and the extension
|
||||
// support it.
|
||||
force_no_bindless = force_no_bindless || Self::bindless_slot_count().is_none();
|
||||
|
||||
// add together the bindings of the standard material and the user material
|
||||
let mut entries = B::bind_group_layout_entries(render_device, force_no_bindless);
|
||||
entries.extend(E::bind_group_layout_entries(
|
||||
render_device,
|
||||
force_no_bindless,
|
||||
));
|
||||
let mut entries = B::bind_group_layout_entries(render_device, true);
|
||||
entries.extend(E::bind_group_layout_entries(render_device, true));
|
||||
entries
|
||||
}
|
||||
|
||||
fn bindless_descriptor() -> Option<BindlessDescriptor> {
|
||||
if B::bindless_descriptor().is_some() && E::bindless_descriptor().is_some() {
|
||||
panic!("Bindless extended materials are currently unsupported")
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: Material, E: MaterialExtension> Material for ExtendedMaterial<B, E> {
|
||||
|
@ -45,8 +45,6 @@ mod ssao;
|
||||
mod ssr;
|
||||
mod volumetric_fog;
|
||||
|
||||
use crate::material_bind_groups::FallbackBindlessResources;
|
||||
|
||||
use bevy_color::{Color, LinearRgba};
|
||||
|
||||
pub use atmosphere::*;
|
||||
@ -59,6 +57,7 @@ pub use light::*;
|
||||
pub use light_probe::*;
|
||||
pub use lightmap::*;
|
||||
pub use material::*;
|
||||
pub use material_bind_groups::*;
|
||||
pub use mesh_material::*;
|
||||
pub use parallax::*;
|
||||
pub use pbr_material::*;
|
||||
|
@ -1,4 +1,6 @@
|
||||
use crate::material_bind_groups::{MaterialBindGroupAllocator, MaterialBindingId};
|
||||
use crate::material_bind_groups::{
|
||||
FallbackBindlessResources, MaterialBindGroupAllocator, MaterialBindingId,
|
||||
};
|
||||
#[cfg(feature = "meshlet")]
|
||||
use crate::meshlet::{
|
||||
prepare_material_meshlet_meshes_main_opaque_pass, queue_material_meshlet_meshes,
|
||||
@ -32,6 +34,7 @@ use bevy_platform_support::hash::FixedHasher;
|
||||
use bevy_reflect::std_traits::ReflectDefault;
|
||||
use bevy_reflect::Reflect;
|
||||
use bevy_render::mesh::mark_3d_meshes_as_changed_if_their_assets_changed;
|
||||
use bevy_render::renderer::RenderQueue;
|
||||
use bevy_render::{
|
||||
batching::gpu_preprocessing::GpuPreprocessingSupport,
|
||||
extract_resource::ExtractResource,
|
||||
@ -328,7 +331,11 @@ where
|
||||
)
|
||||
.add_systems(
|
||||
Render,
|
||||
prepare_material_bind_groups::<M>
|
||||
(
|
||||
prepare_material_bind_groups::<M>,
|
||||
write_material_bind_group_buffers::<M>,
|
||||
)
|
||||
.chain()
|
||||
.in_set(RenderSet::PrepareBindGroups)
|
||||
.after(prepare_assets::<PreparedMaterial<M>>),
|
||||
);
|
||||
@ -509,7 +516,7 @@ impl<M: Material> FromWorld for MaterialPipeline<M> {
|
||||
ShaderRef::Handle(handle) => Some(handle),
|
||||
ShaderRef::Path(path) => Some(asset_server.load(path)),
|
||||
},
|
||||
bindless: material_bind_groups::material_uses_bindless_resources::<M>(render_device),
|
||||
bindless: material_uses_bindless_resources::<M>(render_device),
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
@ -560,7 +567,7 @@ impl<P: PhaseItem, M: Material, const I: usize> RenderCommand<P> for SetMaterial
|
||||
else {
|
||||
return RenderCommandResult::Skip;
|
||||
};
|
||||
let Some(bind_group) = material_bind_group.get_bind_group() else {
|
||||
let Some(bind_group) = material_bind_group.bind_group() else {
|
||||
return RenderCommandResult::Skip;
|
||||
};
|
||||
pass.set_bind_group(I, bind_group, &[]);
|
||||
@ -1234,11 +1241,6 @@ impl<M: Material> RenderAsset for PreparedMaterial<M> {
|
||||
ref mut material_param,
|
||||
): &mut SystemParamItem<Self::Param>,
|
||||
) -> Result<Self, PrepareAssetError<Self::SourceAsset>> {
|
||||
// Allocate a material binding ID if needed.
|
||||
let material_binding_id = *render_material_bindings
|
||||
.entry(material_id.into())
|
||||
.or_insert_with(|| bind_group_allocator.allocate());
|
||||
|
||||
let draw_opaque_pbr = opaque_draw_functions.read().id::<DrawMaterial<M>>();
|
||||
let draw_alpha_mask_pbr = alpha_mask_draw_functions.read().id::<DrawMaterial<M>>();
|
||||
let draw_transmissive_pbr = transmissive_draw_functions.read().id::<DrawMaterial<M>>();
|
||||
@ -1304,10 +1306,15 @@ impl<M: Material> RenderAsset for PreparedMaterial<M> {
|
||||
false,
|
||||
) {
|
||||
Ok(unprepared) => {
|
||||
bind_group_allocator.init(render_device, material_binding_id, unprepared);
|
||||
let binding = *render_material_bindings
|
||||
.entry(material_id.into())
|
||||
.or_insert_with(|| {
|
||||
bind_group_allocator
|
||||
.allocate_unprepared(unprepared, &pipeline.material_layout)
|
||||
});
|
||||
|
||||
Ok(PreparedMaterial {
|
||||
binding: material_binding_id,
|
||||
binding,
|
||||
properties: MaterialProperties {
|
||||
alpha_mode: material.alpha_mode(),
|
||||
depth_bias: material.depth_bias(),
|
||||
@ -1339,11 +1346,9 @@ impl<M: Material> RenderAsset for PreparedMaterial<M> {
|
||||
) {
|
||||
Ok(prepared_bind_group) => {
|
||||
// Store the resulting bind group directly in the slot.
|
||||
bind_group_allocator.init_custom(
|
||||
material_binding_id,
|
||||
prepared_bind_group.bind_group,
|
||||
prepared_bind_group.data,
|
||||
);
|
||||
let material_binding_id =
|
||||
bind_group_allocator.allocate_prepared(prepared_bind_group);
|
||||
render_material_bindings.insert(material_id.into(), material_binding_id);
|
||||
|
||||
Ok(PreparedMaterial {
|
||||
binding: material_binding_id,
|
||||
@ -1408,8 +1413,8 @@ impl From<BindGroup> for MaterialBindGroupId {
|
||||
}
|
||||
}
|
||||
|
||||
/// A system that creates and/or recreates any bind groups that contain
|
||||
/// materials that were modified this frame.
|
||||
/// Creates and/or recreates any bind groups that contain materials that were
|
||||
/// modified this frame.
|
||||
pub fn prepare_material_bind_groups<M>(
|
||||
mut allocator: ResMut<MaterialBindGroupAllocator<M>>,
|
||||
render_device: Res<RenderDevice>,
|
||||
@ -1418,5 +1423,20 @@ pub fn prepare_material_bind_groups<M>(
|
||||
) where
|
||||
M: Material,
|
||||
{
|
||||
allocator.prepare_bind_groups(&render_device, &fallback_image, &fallback_resources);
|
||||
allocator.prepare_bind_groups(&render_device, &fallback_resources, &fallback_image);
|
||||
}
|
||||
|
||||
/// Uploads the contents of all buffers that the [`MaterialBindGroupAllocator`]
|
||||
/// manages to the GPU.
|
||||
///
|
||||
/// Non-bindless allocators don't currently manage any buffers, so this method
|
||||
/// only has an effect for bindless allocators.
|
||||
pub fn write_material_bind_group_buffers<M>(
|
||||
mut allocator: ResMut<MaterialBindGroupAllocator<M>>,
|
||||
render_device: Res<RenderDevice>,
|
||||
render_queue: Res<RenderQueue>,
|
||||
) where
|
||||
M: Material,
|
||||
{
|
||||
allocator.write_buffers(&render_device, &render_queue);
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -228,7 +228,7 @@ pub fn prepare_material_meshlet_meshes_main_opaque_pass<M: Material>(
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let Some(bind_group) = material_bind_group.get_bind_group() else {
|
||||
let Some(bind_group) = material_bind_group.bind_group() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
@ -399,7 +399,7 @@ pub fn prepare_material_meshlet_meshes_prepass<M: Material>(
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let Some(bind_group) = material_bind_group.get_bind_group() else {
|
||||
let Some(bind_group) = material_bind_group.bind_group() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
|
@ -30,8 +30,8 @@ pub enum UvChannel {
|
||||
/// May be created directly from a [`Color`] or an [`Image`].
|
||||
#[derive(Asset, AsBindGroup, Reflect, Debug, Clone)]
|
||||
#[bind_group_data(StandardMaterialKey)]
|
||||
#[uniform(0, StandardMaterialUniform)]
|
||||
#[bindless(16)]
|
||||
#[uniform(0, StandardMaterialUniform, binding_array(10))]
|
||||
#[bindless]
|
||||
#[reflect(Default, Debug)]
|
||||
pub struct StandardMaterial {
|
||||
/// The color of the surface of the material before lighting.
|
||||
|
@ -46,7 +46,7 @@ use bevy_render::{
|
||||
Extract,
|
||||
};
|
||||
use bevy_transform::prelude::GlobalTransform;
|
||||
use tracing::error;
|
||||
use tracing::{error, warn};
|
||||
|
||||
#[cfg(feature = "meshlet")]
|
||||
use crate::meshlet::{
|
||||
@ -980,6 +980,7 @@ pub fn specialize_prepass_material_meshes<M>(
|
||||
let Some(material_bind_group) =
|
||||
material_bind_group_allocator.get(material.binding.group)
|
||||
else {
|
||||
warn!("Couldn't get bind group for material");
|
||||
continue;
|
||||
};
|
||||
let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else {
|
||||
|
@ -1,10 +1,16 @@
|
||||
#define_import_path bevy_pbr::parallax_mapping
|
||||
|
||||
#import bevy_render::bindless::{bindless_samplers_filtering, bindless_textures_2d}
|
||||
|
||||
#import bevy_pbr::{
|
||||
pbr_bindings::{depth_map_texture, depth_map_sampler},
|
||||
mesh_bindings::mesh
|
||||
}
|
||||
|
||||
#ifdef BINDLESS
|
||||
#import bevy_pbr::pbr_bindings::material_indices
|
||||
#endif // BINDLESS
|
||||
|
||||
fn sample_depth_map(uv: vec2<f32>, material_bind_group_slot: u32) -> f32 {
|
||||
// We use `textureSampleLevel` over `textureSample` because the wgpu DX12
|
||||
// backend (Fxc) panics when using "gradient instructions" inside a loop.
|
||||
@ -18,8 +24,8 @@ fn sample_depth_map(uv: vec2<f32>, material_bind_group_slot: u32) -> f32 {
|
||||
// See https://stackoverflow.com/questions/56581141/direct3d11-gradient-instruction-used-in-a-loop-with-varying-iteration-forcing
|
||||
return textureSampleLevel(
|
||||
#ifdef BINDLESS
|
||||
depth_map_texture[material_bind_group_slot],
|
||||
depth_map_sampler[material_bind_group_slot],
|
||||
bindless_textures_2d[material_indices[material_bind_group_slot].depth_map_texture],
|
||||
bindless_samplers_filtering[material_indices[material_bind_group_slot].depth_map_sampler],
|
||||
#else // BINDLESS
|
||||
depth_map_texture,
|
||||
depth_map_sampler,
|
||||
|
@ -3,20 +3,53 @@
|
||||
#import bevy_pbr::pbr_types::StandardMaterial
|
||||
|
||||
#ifdef BINDLESS
|
||||
@group(2) @binding(0) var<storage> material: binding_array<StandardMaterial, 16>;
|
||||
@group(2) @binding(1) var base_color_texture: binding_array<texture_2d<f32>, 16>;
|
||||
@group(2) @binding(2) var base_color_sampler: binding_array<sampler, 16>;
|
||||
@group(2) @binding(3) var emissive_texture: binding_array<texture_2d<f32>, 16>;
|
||||
@group(2) @binding(4) var emissive_sampler: binding_array<sampler, 16>;
|
||||
@group(2) @binding(5) var metallic_roughness_texture: binding_array<texture_2d<f32>, 16>;
|
||||
@group(2) @binding(6) var metallic_roughness_sampler: binding_array<sampler, 16>;
|
||||
@group(2) @binding(7) var occlusion_texture: binding_array<texture_2d<f32>, 16>;
|
||||
@group(2) @binding(8) var occlusion_sampler: binding_array<sampler, 16>;
|
||||
@group(2) @binding(9) var normal_map_texture: binding_array<texture_2d<f32>, 16>;
|
||||
@group(2) @binding(10) var normal_map_sampler: binding_array<sampler, 16>;
|
||||
@group(2) @binding(11) var depth_map_texture: binding_array<texture_2d<f32>, 16>;
|
||||
@group(2) @binding(12) var depth_map_sampler: binding_array<sampler, 16>;
|
||||
struct StandardMaterialBindings {
|
||||
material: u32, // 0
|
||||
base_color_texture: u32, // 1
|
||||
base_color_sampler: u32, // 2
|
||||
emissive_texture: u32, // 3
|
||||
emissive_sampler: u32, // 4
|
||||
metallic_roughness_texture: u32, // 5
|
||||
metallic_roughness_sampler: u32, // 6
|
||||
occlusion_texture: u32, // 7
|
||||
occlusion_sampler: u32, // 8
|
||||
normal_map_texture: u32, // 9
|
||||
normal_map_sampler: u32, // 10
|
||||
depth_map_texture: u32, // 11
|
||||
depth_map_sampler: u32, // 12
|
||||
#ifdef PBR_ANISOTROPY_TEXTURE_SUPPORTED
|
||||
anisotropy_texture: u32, // 13
|
||||
anisotropy_sampler: u32, // 14
|
||||
#endif // PBR_ANISOTROPY_TEXTURE_SUPPORTED
|
||||
#ifdef PBR_TRANSMISSION_TEXTURES_SUPPORTED
|
||||
specular_transmission_texture: u32, // 15
|
||||
specular_transmission_sampler: u32, // 16
|
||||
thickness_texture: u32, // 17
|
||||
thickness_sampler: u32, // 18
|
||||
diffuse_transmission_texture: u32, // 19
|
||||
diffuse_transmission_sampler: u32, // 20
|
||||
#endif // PBR_TRANSMISSION_TEXTURES_SUPPORTED
|
||||
#ifdef PBR_MULTI_LAYER_MATERIAL_TEXTURES_SUPPORTED
|
||||
clearcoat_texture: u32, // 21
|
||||
clearcoat_sampler: u32, // 22
|
||||
clearcoat_roughness_texture: u32, // 23
|
||||
clearcoat_roughness_sampler: u32, // 24
|
||||
clearcoat_normal_texture: u32, // 25
|
||||
clearcoat_normal_sampler: u32, // 26
|
||||
#endif // PBR_MULTI_LAYER_MATERIAL_TEXTURES_SUPPORTED
|
||||
#ifdef PBR_SPECULAR_TEXTURES_SUPPORTED
|
||||
specular_texture: u32, // 27
|
||||
specular_sampler: u32, // 28
|
||||
specular_tint_texture: u32, // 29
|
||||
specular_tint_sampler: u32, // 30
|
||||
#endif // PBR_SPECULAR_TEXTURES_SUPPORTED
|
||||
}
|
||||
|
||||
@group(2) @binding(0) var<storage> material_indices: array<StandardMaterialBindings>;
|
||||
@group(2) @binding(10) var<storage> material_array: binding_array<StandardMaterial>;
|
||||
|
||||
#else // BINDLESS
|
||||
|
||||
@group(2) @binding(0) var<uniform> material: StandardMaterial;
|
||||
@group(2) @binding(1) var base_color_texture: texture_2d<f32>;
|
||||
@group(2) @binding(2) var base_color_sampler: sampler;
|
||||
@ -30,64 +63,35 @@
|
||||
@group(2) @binding(10) var normal_map_sampler: sampler;
|
||||
@group(2) @binding(11) var depth_map_texture: texture_2d<f32>;
|
||||
@group(2) @binding(12) var depth_map_sampler: sampler;
|
||||
#endif // BINDLESS
|
||||
|
||||
#ifdef PBR_ANISOTROPY_TEXTURE_SUPPORTED
|
||||
#ifdef BINDLESS
|
||||
@group(2) @binding(13) var anisotropy_texture: binding_array<texture_2d<f32>, 16>;
|
||||
@group(2) @binding(14) var anisotropy_sampler: binding_array<sampler, 16>;
|
||||
#else // BINDLESS
|
||||
@group(2) @binding(13) var anisotropy_texture: texture_2d<f32>;
|
||||
@group(2) @binding(14) var anisotropy_sampler: sampler;
|
||||
#endif // BINDLESS
|
||||
#endif // PBR_ANISOTROPY_TEXTURE_SUPPORTED
|
||||
|
||||
#ifdef PBR_TRANSMISSION_TEXTURES_SUPPORTED
|
||||
#ifdef BINDLESS
|
||||
@group(2) @binding(15) var specular_transmission_texture: binding_array<texture_2d<f32>, 16>;
|
||||
@group(2) @binding(16) var specular_transmission_sampler: binding_array<sampler, 16>;
|
||||
@group(2) @binding(17) var thickness_texture: binding_array<texture_2d<f32>, 16>;
|
||||
@group(2) @binding(18) var thickness_sampler: binding_array<sampler, 16>;
|
||||
@group(2) @binding(19) var diffuse_transmission_texture: binding_array<texture_2d<f32>, 16>;
|
||||
@group(2) @binding(20) var diffuse_transmission_sampler: binding_array<sampler, 16>;
|
||||
#else // BINDLESS
|
||||
@group(2) @binding(15) var specular_transmission_texture: texture_2d<f32>;
|
||||
@group(2) @binding(16) var specular_transmission_sampler: sampler;
|
||||
@group(2) @binding(17) var thickness_texture: texture_2d<f32>;
|
||||
@group(2) @binding(18) var thickness_sampler: sampler;
|
||||
@group(2) @binding(19) var diffuse_transmission_texture: texture_2d<f32>;
|
||||
@group(2) @binding(20) var diffuse_transmission_sampler: sampler;
|
||||
#endif // BINDLESS
|
||||
#endif // PBR_TRANSMISSION_TEXTURES_SUPPORTED
|
||||
|
||||
#ifdef PBR_MULTI_LAYER_MATERIAL_TEXTURES_SUPPORTED
|
||||
#ifdef BINDLESS
|
||||
@group(2) @binding(21) var clearcoat_texture: binding_array<texture_2d<f32>, 16>;
|
||||
@group(2) @binding(22) var clearcoat_sampler: binding_array<sampler, 16>;
|
||||
@group(2) @binding(23) var clearcoat_roughness_texture: binding_array<texture_2d<f32>, 16>;
|
||||
@group(2) @binding(24) var clearcoat_roughness_sampler: binding_array<sampler, 16>;
|
||||
@group(2) @binding(25) var clearcoat_normal_texture: binding_array<texture_2d<f32>, 16>;
|
||||
@group(2) @binding(26) var clearcoat_normal_sampler: binding_array<sampler, 16>;
|
||||
#else // BINDLESS
|
||||
@group(2) @binding(21) var clearcoat_texture: texture_2d<f32>;
|
||||
@group(2) @binding(22) var clearcoat_sampler: sampler;
|
||||
@group(2) @binding(23) var clearcoat_roughness_texture: texture_2d<f32>;
|
||||
@group(2) @binding(24) var clearcoat_roughness_sampler: sampler;
|
||||
@group(2) @binding(25) var clearcoat_normal_texture: texture_2d<f32>;
|
||||
@group(2) @binding(26) var clearcoat_normal_sampler: sampler;
|
||||
#endif // BINDLESS
|
||||
#endif // PBR_MULTI_LAYER_MATERIAL_TEXTURES_SUPPORTED
|
||||
|
||||
#ifdef PBR_SPECULAR_TEXTURES_SUPPORTED
|
||||
#ifdef BINDLESS
|
||||
@group(2) @binding(27) var specular_texture: binding_array<texture_2d<f32>, 16>;
|
||||
@group(2) @binding(28) var specular_sampler: binding_array<sampler, 16>;
|
||||
@group(2) @binding(29) var specular_tint_texture: binding_array<texture_2d<f32>, 16>;
|
||||
@group(2) @binding(30) var specular_tint_sampler: binding_array<sampler, 16>;
|
||||
#else
|
||||
@group(2) @binding(27) var specular_texture: texture_2d<f32>;
|
||||
@group(2) @binding(28) var specular_sampler: sampler;
|
||||
@group(2) @binding(29) var specular_tint_texture: texture_2d<f32>;
|
||||
@group(2) @binding(30) var specular_tint_sampler: sampler;
|
||||
#endif // BINDLESS
|
||||
#endif // PBR_SPECULAR_TEXTURES_SUPPORTED
|
||||
|
||||
#endif // BINDLESS
|
||||
|
@ -1,5 +1,7 @@
|
||||
#define_import_path bevy_pbr::pbr_fragment
|
||||
|
||||
#import bevy_render::bindless::{bindless_samplers_filtering, bindless_textures_2d}
|
||||
|
||||
#import bevy_pbr::{
|
||||
pbr_functions,
|
||||
pbr_functions::SampleBias,
|
||||
@ -26,6 +28,10 @@
|
||||
#import bevy_pbr::forward_io::VertexOutput
|
||||
#endif
|
||||
|
||||
#ifdef BINDLESS
|
||||
#import bevy_pbr::pbr_bindings::material_indices
|
||||
#endif // BINDLESS
|
||||
|
||||
// prepare a basic PbrInput from the vertex stage output, mesh binding and view binding
|
||||
fn pbr_input_from_vertex_output(
|
||||
in: VertexOutput,
|
||||
@ -76,9 +82,10 @@ fn pbr_input_from_standard_material(
|
||||
let slot = mesh[in.instance_index].material_and_lightmap_bind_group_slot & 0xffffu;
|
||||
#endif // MESHLET_MESH_MATERIAL_PASS
|
||||
#ifdef BINDLESS
|
||||
let flags = pbr_bindings::material[slot].flags;
|
||||
let base_color = pbr_bindings::material[slot].base_color;
|
||||
let deferred_lighting_pass_id = pbr_bindings::material[slot].deferred_lighting_pass_id;
|
||||
let flags = pbr_bindings::material_array[material_indices[slot].material].flags;
|
||||
let base_color = pbr_bindings::material_array[material_indices[slot].material].base_color;
|
||||
let deferred_lighting_pass_id =
|
||||
pbr_bindings::material_array[material_indices[slot].material].deferred_lighting_pass_id;
|
||||
#else // BINDLESS
|
||||
let flags = pbr_bindings::material.flags;
|
||||
let base_color = pbr_bindings::material.base_color;
|
||||
@ -108,7 +115,7 @@ fn pbr_input_from_standard_material(
|
||||
#ifdef VERTEX_UVS
|
||||
|
||||
#ifdef BINDLESS
|
||||
let uv_transform = pbr_bindings::material[slot].uv_transform;
|
||||
let uv_transform = pbr_bindings::material_array[material_indices[slot].material].uv_transform;
|
||||
#else // BINDLESS
|
||||
let uv_transform = pbr_bindings::material.uv_transform;
|
||||
#endif // BINDLESS
|
||||
@ -137,9 +144,9 @@ fn pbr_input_from_standard_material(
|
||||
// TODO: Transforming UVs mean we need to apply derivative chain rule for meshlet mesh material pass
|
||||
uv = parallaxed_uv(
|
||||
#ifdef BINDLESS
|
||||
pbr_bindings::material[slot].parallax_depth_scale,
|
||||
pbr_bindings::material[slot].max_parallax_layer_count,
|
||||
pbr_bindings::material[slot].max_relief_mapping_search_steps,
|
||||
pbr_bindings::material_array[material_indices[slot].material].parallax_depth_scale,
|
||||
pbr_bindings::material_array[material_indices[slot].material].max_parallax_layer_count,
|
||||
pbr_bindings::material_array[material_indices[slot].material].max_relief_mapping_search_steps,
|
||||
#else // BINDLESS
|
||||
pbr_bindings::material.parallax_depth_scale,
|
||||
pbr_bindings::material.max_parallax_layer_count,
|
||||
@ -158,9 +165,9 @@ fn pbr_input_from_standard_material(
|
||||
// TODO: Transforming UVs mean we need to apply derivative chain rule for meshlet mesh material pass
|
||||
uv_b = parallaxed_uv(
|
||||
#ifdef BINDLESS
|
||||
pbr_bindings::material[slot].parallax_depth_scale,
|
||||
pbr_bindings::material[slot].max_parallax_layer_count,
|
||||
pbr_bindings::material[slot].max_relief_mapping_search_steps,
|
||||
pbr_bindings::material_array[material_indices[slot].material].parallax_depth_scale,
|
||||
pbr_bindings::material_array[material_indices[slot].material].max_parallax_layer_count,
|
||||
pbr_bindings::material_array[material_indices[slot].material].max_relief_mapping_search_steps,
|
||||
#else // BINDLESS
|
||||
pbr_bindings::material.parallax_depth_scale,
|
||||
pbr_bindings::material.max_parallax_layer_count,
|
||||
@ -187,8 +194,8 @@ fn pbr_input_from_standard_material(
|
||||
textureSampleBias(
|
||||
#endif // MESHLET_MESH_MATERIAL_PASS
|
||||
#ifdef BINDLESS
|
||||
pbr_bindings::base_color_texture[slot],
|
||||
pbr_bindings::base_color_sampler[slot],
|
||||
bindless_textures_2d[material_indices[slot].base_color_texture],
|
||||
bindless_samplers_filtering[material_indices[slot].base_color_sampler],
|
||||
#else // BINDLESS
|
||||
pbr_bindings::base_color_texture,
|
||||
pbr_bindings::base_color_sampler,
|
||||
@ -214,7 +221,7 @@ fn pbr_input_from_standard_material(
|
||||
if alpha_mode == pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_ALPHA_TO_COVERAGE {
|
||||
|
||||
#ifdef BINDLESS
|
||||
let alpha_cutoff = pbr_bindings::material[slot].alpha_cutoff;
|
||||
let alpha_cutoff = pbr_bindings::material_array[material_indices[slot].material].alpha_cutoff;
|
||||
#else // BINDLESS
|
||||
let alpha_cutoff = pbr_bindings::material.alpha_cutoff;
|
||||
#endif // BINDLESS
|
||||
@ -232,10 +239,13 @@ fn pbr_input_from_standard_material(
|
||||
// NOTE: Unlit bit not set means == 0 is true, so the true case is if lit
|
||||
if ((flags & pbr_types::STANDARD_MATERIAL_FLAGS_UNLIT_BIT) == 0u) {
|
||||
#ifdef BINDLESS
|
||||
pbr_input.material.ior = pbr_bindings::material[slot].ior;
|
||||
pbr_input.material.attenuation_color = pbr_bindings::material[slot].attenuation_color;
|
||||
pbr_input.material.attenuation_distance = pbr_bindings::material[slot].attenuation_distance;
|
||||
pbr_input.material.alpha_cutoff = pbr_bindings::material[slot].alpha_cutoff;
|
||||
pbr_input.material.ior = pbr_bindings::material_array[material_indices[slot].material].ior;
|
||||
pbr_input.material.attenuation_color =
|
||||
pbr_bindings::material_array[material_indices[slot].material].attenuation_color;
|
||||
pbr_input.material.attenuation_distance =
|
||||
pbr_bindings::material_array[material_indices[slot].material].attenuation_distance;
|
||||
pbr_input.material.alpha_cutoff =
|
||||
pbr_bindings::material_array[material_indices[slot].material].alpha_cutoff;
|
||||
#else // BINDLESS
|
||||
pbr_input.material.ior = pbr_bindings::material.ior;
|
||||
pbr_input.material.attenuation_color = pbr_bindings::material.attenuation_color;
|
||||
@ -245,7 +255,8 @@ fn pbr_input_from_standard_material(
|
||||
|
||||
// reflectance
|
||||
#ifdef BINDLESS
|
||||
pbr_input.material.reflectance = pbr_bindings::material[slot].reflectance;
|
||||
pbr_input.material.reflectance =
|
||||
pbr_bindings::material_array[material_indices[slot].material].reflectance;
|
||||
#else // BINDLESS
|
||||
pbr_input.material.reflectance = pbr_bindings::material.reflectance;
|
||||
#endif // BINDLESS
|
||||
@ -262,8 +273,8 @@ fn pbr_input_from_standard_material(
|
||||
textureSampleBias(
|
||||
#endif // MESHLET_MESH_MATERIAL_PASS
|
||||
#ifdef BINDLESS
|
||||
pbr_bindings::specular_texture[slot],
|
||||
pbr_bindings::specular_sampler[slot],
|
||||
bindless_textures_2d[material_indices[slot].specular_texture],
|
||||
bindless_samplers_filtering[material_indices[slot].specular_sampler],
|
||||
#else // BINDLESS
|
||||
pbr_bindings::specular_texture,
|
||||
pbr_bindings::specular_sampler,
|
||||
@ -294,8 +305,8 @@ fn pbr_input_from_standard_material(
|
||||
textureSampleBias(
|
||||
#endif // MESHLET_MESH_MATERIAL_PASS
|
||||
#ifdef BINDLESS
|
||||
pbr_bindings::specular_tint_texture[slot],
|
||||
pbr_bindings::specular_tint_sampler[slot],
|
||||
bindless_textures_2d[material_indices[slot].specular_tint_texture],
|
||||
bindless_samplers_filtering[material_indices[slot].specular_tint_sampler],
|
||||
#else // BINDLESS
|
||||
pbr_bindings::specular_tint_texture,
|
||||
pbr_bindings::specular_tint_sampler,
|
||||
@ -320,7 +331,7 @@ fn pbr_input_from_standard_material(
|
||||
|
||||
// emissive
|
||||
#ifdef BINDLESS
|
||||
var emissive: vec4<f32> = pbr_bindings::material[slot].emissive;
|
||||
var emissive: vec4<f32> = pbr_bindings::material_array[material_indices[slot].material].emissive;
|
||||
#else // BINDLESS
|
||||
var emissive: vec4<f32> = pbr_bindings::material.emissive;
|
||||
#endif // BINDLESS
|
||||
@ -334,8 +345,8 @@ fn pbr_input_from_standard_material(
|
||||
textureSampleBias(
|
||||
#endif // MESHLET_MESH_MATERIAL_PASS
|
||||
#ifdef BINDLESS
|
||||
pbr_bindings::emissive_texture[slot],
|
||||
pbr_bindings::emissive_sampler[slot],
|
||||
bindless_textures_2d[material_indices[slot].emissive_texture],
|
||||
bindless_samplers_filtering[material_indices[slot].emissive_sampler],
|
||||
#else // BINDLESS
|
||||
pbr_bindings::emissive_texture,
|
||||
pbr_bindings::emissive_sampler,
|
||||
@ -359,8 +370,8 @@ fn pbr_input_from_standard_material(
|
||||
|
||||
// metallic and perceptual roughness
|
||||
#ifdef BINDLESS
|
||||
var metallic: f32 = pbr_bindings::material[slot].metallic;
|
||||
var perceptual_roughness: f32 = pbr_bindings::material[slot].perceptual_roughness;
|
||||
var metallic: f32 = pbr_bindings::material_array[material_indices[slot].material].metallic;
|
||||
var perceptual_roughness: f32 = pbr_bindings::material_array[material_indices[slot].material].perceptual_roughness;
|
||||
#else // BINDLESS
|
||||
var metallic: f32 = pbr_bindings::material.metallic;
|
||||
var perceptual_roughness: f32 = pbr_bindings::material.perceptual_roughness;
|
||||
@ -376,8 +387,8 @@ fn pbr_input_from_standard_material(
|
||||
textureSampleBias(
|
||||
#endif // MESHLET_MESH_MATERIAL_PASS
|
||||
#ifdef BINDLESS
|
||||
pbr_bindings::metallic_roughness_texture[slot],
|
||||
pbr_bindings::metallic_roughness_sampler[slot],
|
||||
bindless_textures_2d[material_indices[slot].metallic_roughness_texture],
|
||||
bindless_samplers_filtering[material_indices[slot].metallic_roughness_sampler],
|
||||
#else // BINDLESS
|
||||
pbr_bindings::metallic_roughness_texture,
|
||||
pbr_bindings::metallic_roughness_sampler,
|
||||
@ -404,7 +415,8 @@ fn pbr_input_from_standard_material(
|
||||
|
||||
// Clearcoat factor
|
||||
#ifdef BINDLESS
|
||||
pbr_input.material.clearcoat = pbr_bindings::material[slot].clearcoat;
|
||||
pbr_input.material.clearcoat =
|
||||
pbr_bindings::material_array[material_indices[slot].material].clearcoat;
|
||||
#else // BINDLESS
|
||||
pbr_input.material.clearcoat = pbr_bindings::material.clearcoat;
|
||||
#endif // BINDLESS
|
||||
@ -419,8 +431,8 @@ fn pbr_input_from_standard_material(
|
||||
textureSampleBias(
|
||||
#endif // MESHLET_MESH_MATERIAL_PASS
|
||||
#ifdef BINDLESS
|
||||
pbr_bindings::clearcoat_texture[slot],
|
||||
pbr_bindings::clearcoat_sampler[slot],
|
||||
bindless_textures_2d[material_indices[slot].clearcoat_texture],
|
||||
bindless_samplers_filtering[material_indices[slot].clearcoat_sampler],
|
||||
#else // BINDLESS
|
||||
pbr_bindings::clearcoat_texture,
|
||||
pbr_bindings::clearcoat_sampler,
|
||||
@ -444,7 +456,7 @@ fn pbr_input_from_standard_material(
|
||||
// Clearcoat roughness
|
||||
#ifdef BINDLESS
|
||||
pbr_input.material.clearcoat_perceptual_roughness =
|
||||
pbr_bindings::material[slot].clearcoat_perceptual_roughness;
|
||||
pbr_bindings::material_array[material_indices[slot].material].clearcoat_perceptual_roughness;
|
||||
#else // BINDLESS
|
||||
pbr_input.material.clearcoat_perceptual_roughness =
|
||||
pbr_bindings::material.clearcoat_perceptual_roughness;
|
||||
@ -460,8 +472,8 @@ fn pbr_input_from_standard_material(
|
||||
textureSampleBias(
|
||||
#endif // MESHLET_MESH_MATERIAL_PASS
|
||||
#ifdef BINDLESS
|
||||
pbr_bindings::clearcoat_roughness_texture[slot],
|
||||
pbr_bindings::clearcoat_roughness_sampler[slot],
|
||||
bindless_textures_2d[material_indices[slot].clearcoat_roughness_texture],
|
||||
bindless_samplers_filtering[material_indices[slot].clearcoat_roughness_sampler],
|
||||
#else // BINDLESS
|
||||
pbr_bindings::clearcoat_roughness_texture,
|
||||
pbr_bindings::clearcoat_roughness_sampler,
|
||||
@ -483,7 +495,7 @@ fn pbr_input_from_standard_material(
|
||||
#endif // VERTEX_UVS
|
||||
|
||||
#ifdef BINDLESS
|
||||
var specular_transmission: f32 = pbr_bindings::material[slot].specular_transmission;
|
||||
var specular_transmission: f32 = pbr_bindings::material_array[slot].specular_transmission;
|
||||
#else // BINDLESS
|
||||
var specular_transmission: f32 = pbr_bindings::material.specular_transmission;
|
||||
#endif // BINDLESS
|
||||
@ -498,8 +510,12 @@ fn pbr_input_from_standard_material(
|
||||
textureSampleBias(
|
||||
#endif // MESHLET_MESH_MATERIAL_PASS
|
||||
#ifdef BINDLESS
|
||||
pbr_bindings::specular_transmission_texture[slot],
|
||||
pbr_bindings::specular_transmission_sampler[slot],
|
||||
bindless_textures_2d[
|
||||
material_indices[slot].specular_transmission_texture
|
||||
],
|
||||
bindless_samplers_filtering[
|
||||
material_indices[slot].specular_transmission_sampler
|
||||
],
|
||||
#else // BINDLESS
|
||||
pbr_bindings::specular_transmission_texture,
|
||||
pbr_bindings::specular_transmission_sampler,
|
||||
@ -522,7 +538,7 @@ fn pbr_input_from_standard_material(
|
||||
pbr_input.material.specular_transmission = specular_transmission;
|
||||
|
||||
#ifdef BINDLESS
|
||||
var thickness: f32 = pbr_bindings::material[slot].thickness;
|
||||
var thickness: f32 = pbr_bindings::material_array[material_indices[slot].material].thickness;
|
||||
#else // BINDLESS
|
||||
var thickness: f32 = pbr_bindings::material.thickness;
|
||||
#endif // BINDLESS
|
||||
@ -537,8 +553,8 @@ fn pbr_input_from_standard_material(
|
||||
textureSampleBias(
|
||||
#endif // MESHLET_MESH_MATERIAL_PASS
|
||||
#ifdef BINDLESS
|
||||
pbr_bindings::thickness_texture[slot],
|
||||
pbr_bindings::thickness_sampler[slot],
|
||||
bindless_textures_2d[material_indices[slot].thickness_texture],
|
||||
bindless_samplers_filtering[material_indices[slot].thickness_sampler],
|
||||
#else // BINDLESS
|
||||
pbr_bindings::thickness_texture,
|
||||
pbr_bindings::thickness_sampler,
|
||||
@ -568,7 +584,8 @@ fn pbr_input_from_standard_material(
|
||||
pbr_input.material.thickness = thickness;
|
||||
|
||||
#ifdef BINDLESS
|
||||
var diffuse_transmission = pbr_bindings::material[slot].diffuse_transmission;
|
||||
var diffuse_transmission =
|
||||
pbr_bindings::material_array[material_indices[slot].material].diffuse_transmission;
|
||||
#else // BINDLESS
|
||||
var diffuse_transmission = pbr_bindings::material.diffuse_transmission;
|
||||
#endif // BINDLESS
|
||||
@ -583,8 +600,8 @@ fn pbr_input_from_standard_material(
|
||||
textureSampleBias(
|
||||
#endif // MESHLET_MESH_MATERIAL_PASS
|
||||
#ifdef BINDLESS
|
||||
pbr_bindings::diffuse_transmission_texture[slot],
|
||||
pbr_bindings::diffuse_transmission_sampler[slot],
|
||||
bindless_textures_2d[material_indices[slot].diffuse_transmission_texture],
|
||||
bindless_samplers_filtering[material_indices[slot].diffuse_transmission_sampler],
|
||||
#else // BINDLESS
|
||||
pbr_bindings::diffuse_transmission_texture,
|
||||
pbr_bindings::diffuse_transmission_sampler,
|
||||
@ -617,8 +634,8 @@ fn pbr_input_from_standard_material(
|
||||
textureSampleBias(
|
||||
#endif // MESHLET_MESH_MATERIAL_PASS
|
||||
#ifdef BINDLESS
|
||||
pbr_bindings::occlusion_texture[slot],
|
||||
pbr_bindings::occlusion_sampler[slot],
|
||||
bindless_textures_2d[material_indices[slot].occlusion_texture],
|
||||
bindless_samplers_filtering[material_indices[slot].occlusion_sampler],
|
||||
#else // BINDLESS
|
||||
pbr_bindings::occlusion_texture,
|
||||
pbr_bindings::occlusion_sampler,
|
||||
@ -668,8 +685,8 @@ fn pbr_input_from_standard_material(
|
||||
textureSampleBias(
|
||||
#endif // MESHLET_MESH_MATERIAL_PASS
|
||||
#ifdef BINDLESS
|
||||
pbr_bindings::normal_map_texture[slot],
|
||||
pbr_bindings::normal_map_sampler[slot],
|
||||
bindless_textures_2d[material_indices[slot].normal_map_texture],
|
||||
bindless_samplers_filtering[material_indices[slot].normal_map_sampler],
|
||||
#else // BINDLESS
|
||||
pbr_bindings::normal_map_texture,
|
||||
pbr_bindings::normal_map_sampler,
|
||||
@ -706,8 +723,8 @@ fn pbr_input_from_standard_material(
|
||||
textureSampleBias(
|
||||
#endif // MESHLET_MESH_MATERIAL_PASS
|
||||
#ifdef BINDLESS
|
||||
pbr_bindings::clearcoat_normal_texture[slot],
|
||||
pbr_bindings::clearcoat_normal_sampler[slot],
|
||||
bindless_textures_2d[material_indices[slot].clearcoat_normal_texture],
|
||||
bindless_samplers_filtering[material_indices[slot].clearcoat_normal_sampler],
|
||||
#else // BINDLESS
|
||||
pbr_bindings::clearcoat_normal_texture,
|
||||
pbr_bindings::clearcoat_normal_sampler,
|
||||
@ -749,8 +766,10 @@ fn pbr_input_from_standard_material(
|
||||
#ifdef STANDARD_MATERIAL_ANISOTROPY
|
||||
|
||||
#ifdef BINDLESS
|
||||
var anisotropy_strength = pbr_bindings::material[slot].anisotropy_strength;
|
||||
var anisotropy_direction = pbr_bindings::material[slot].anisotropy_rotation;
|
||||
var anisotropy_strength =
|
||||
pbr_bindings::material_array[material_indices[slot].material].anisotropy_strength;
|
||||
var anisotropy_direction =
|
||||
pbr_bindings::material_array[material_indices[slot].material].anisotropy_rotation;
|
||||
#else // BINDLESS
|
||||
var anisotropy_strength = pbr_bindings::material.anisotropy_strength;
|
||||
var anisotropy_direction = pbr_bindings::material.anisotropy_rotation;
|
||||
@ -765,8 +784,8 @@ fn pbr_input_from_standard_material(
|
||||
textureSampleBias(
|
||||
#endif // MESHLET_MESH_MATERIAL_PASS
|
||||
#ifdef BINDLESS
|
||||
pbr_bindings::anisotropy_texture[slot],
|
||||
pbr_bindings::anisotropy_sampler[slot],
|
||||
bindless_textures_2d[material_indices[slot].anisotropy_texture],
|
||||
bindless_samplers_filtering[material_indices[slot].anisotropy_sampler],
|
||||
#else // BINDLESS
|
||||
pbr_bindings::anisotropy_texture,
|
||||
pbr_bindings::anisotropy_sampler,
|
||||
@ -809,7 +828,8 @@ fn pbr_input_from_standard_material(
|
||||
#ifdef LIGHTMAP
|
||||
|
||||
#ifdef BINDLESS
|
||||
let lightmap_exposure = pbr_bindings::material[slot].lightmap_exposure;
|
||||
let lightmap_exposure =
|
||||
pbr_bindings::material_array[material_indices[slot].material].lightmap_exposure;
|
||||
#else // BINDLESS
|
||||
let lightmap_exposure = pbr_bindings::material.lightmap_exposure;
|
||||
#endif // BINDLESS
|
||||
|
@ -14,6 +14,10 @@
|
||||
#import bevy_pbr::meshlet_visibility_buffer_resolve::resolve_vertex_output
|
||||
#endif
|
||||
|
||||
#ifdef BINDLESS
|
||||
#import bevy_pbr::pbr_bindings::material_indices
|
||||
#endif // BINDLESS
|
||||
|
||||
#ifdef PREPASS_FRAGMENT
|
||||
@fragment
|
||||
fn fragment(
|
||||
@ -31,8 +35,8 @@ fn fragment(
|
||||
|
||||
#ifdef BINDLESS
|
||||
let slot = mesh[in.instance_index].material_and_lightmap_bind_group_slot & 0xffffu;
|
||||
let flags = pbr_bindings::material[slot].flags;
|
||||
let uv_transform = pbr_bindings::material[slot].uv_transform;
|
||||
let flags = pbr_bindings::material_array[material_indices[slot].material].flags;
|
||||
let uv_transform = pbr_bindings::material_array[material_indices[slot].material].uv_transform;
|
||||
#else // BINDLESS
|
||||
let flags = pbr_bindings::material.flags;
|
||||
let uv_transform = pbr_bindings::material.uv_transform;
|
||||
@ -93,8 +97,8 @@ fn fragment(
|
||||
textureSampleBias(
|
||||
#endif // MESHLET_MESH_MATERIAL_PASS
|
||||
#ifdef BINDLESS
|
||||
pbr_bindings::normal_map_texture[slot],
|
||||
pbr_bindings::normal_map_sampler[slot],
|
||||
bindless_textures_2d[material_indices[slot].normal_map_texture],
|
||||
bindless_samplers_filtering[material_indices[slot].normal_map_sampler],
|
||||
#else // BINDLESS
|
||||
pbr_bindings::normal_map_texture,
|
||||
pbr_bindings::normal_map_sampler,
|
||||
|
@ -1,5 +1,7 @@
|
||||
#define_import_path bevy_pbr::pbr_prepass_functions
|
||||
|
||||
#import bevy_render::bindless::{bindless_samplers_filtering, bindless_textures_2d}
|
||||
|
||||
#import bevy_pbr::{
|
||||
prepass_io::VertexOutput,
|
||||
prepass_bindings::previous_view_uniforms,
|
||||
@ -9,6 +11,10 @@
|
||||
pbr_types,
|
||||
}
|
||||
|
||||
#ifdef BINDLESS
|
||||
#import bevy_pbr::pbr_bindings::material_indices
|
||||
#endif // BINDLESS
|
||||
|
||||
// Cutoff used for the premultiplied alpha modes BLEND, ADD, and ALPHA_TO_COVERAGE.
|
||||
const PREMULTIPLIED_ALPHA_CUTOFF = 0.05;
|
||||
|
||||
@ -18,7 +24,7 @@ fn prepass_alpha_discard(in: VertexOutput) {
|
||||
#ifdef MAY_DISCARD
|
||||
#ifdef BINDLESS
|
||||
let slot = mesh[in.instance_index].material_and_lightmap_bind_group_slot & 0xffffu;
|
||||
var output_color: vec4<f32> = pbr_bindings::material[slot].base_color;
|
||||
var output_color: vec4<f32> = pbr_bindings::material_array[material_indices[slot].material].base_color;
|
||||
#else // BINDLESS
|
||||
var output_color: vec4<f32> = pbr_bindings::material.base_color;
|
||||
#endif // BINDLESS
|
||||
@ -31,8 +37,8 @@ fn prepass_alpha_discard(in: VertexOutput) {
|
||||
#endif // STANDARD_MATERIAL_BASE_COLOR_UV_B
|
||||
|
||||
#ifdef BINDLESS
|
||||
let uv_transform = pbr_bindings::material[slot].uv_transform;
|
||||
let flags = pbr_bindings::material[slot].flags;
|
||||
let uv_transform = pbr_bindings::material_array[material_indices[slot].material].uv_transform;
|
||||
let flags = pbr_bindings::material_array[material_indices[slot].material].flags;
|
||||
#else // BINDLESS
|
||||
let uv_transform = pbr_bindings::material.uv_transform;
|
||||
let flags = pbr_bindings::material.flags;
|
||||
@ -42,8 +48,8 @@ fn prepass_alpha_discard(in: VertexOutput) {
|
||||
if (flags & pbr_types::STANDARD_MATERIAL_FLAGS_BASE_COLOR_TEXTURE_BIT) != 0u {
|
||||
output_color = output_color * textureSampleBias(
|
||||
#ifdef BINDLESS
|
||||
pbr_bindings::base_color_texture[slot],
|
||||
pbr_bindings::base_color_sampler[slot],
|
||||
bindless_textures_2d[material_indices[slot].base_color_texture],
|
||||
bindless_samplers_filtering[material_indices[slot].base_color_sampler],
|
||||
#else // BINDLESS
|
||||
pbr_bindings::base_color_texture,
|
||||
pbr_bindings::base_color_sampler,
|
||||
@ -57,7 +63,7 @@ fn prepass_alpha_discard(in: VertexOutput) {
|
||||
let alpha_mode = flags & pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_RESERVED_BITS;
|
||||
if alpha_mode == pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK {
|
||||
#ifdef BINDLESS
|
||||
let alpha_cutoff = pbr_bindings::material[slot].alpha_cutoff;
|
||||
let alpha_cutoff = pbr_bindings::material_array[material_indices[slot].material].alpha_cutoff;
|
||||
#else // BINDLESS
|
||||
let alpha_cutoff = pbr_bindings::material.alpha_cutoff;
|
||||
#endif // BINDLESS
|
||||
|
@ -3,10 +3,11 @@ use proc_macro::TokenStream;
|
||||
use proc_macro2::{Ident, Span};
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::{
|
||||
parenthesized,
|
||||
parse::{Parse, ParseStream},
|
||||
punctuated::Punctuated,
|
||||
token::Comma,
|
||||
Data, DataStruct, Error, Fields, Lit, LitInt, LitStr, Meta, MetaList, Result,
|
||||
Data, DataStruct, Error, Fields, LitInt, LitStr, Meta, MetaList, Result,
|
||||
};
|
||||
|
||||
const UNIFORM_ATTRIBUTE_NAME: Symbol = Symbol("uniform");
|
||||
@ -16,6 +17,8 @@ const SAMPLER_ATTRIBUTE_NAME: Symbol = Symbol("sampler");
|
||||
const STORAGE_ATTRIBUTE_NAME: Symbol = Symbol("storage");
|
||||
const BIND_GROUP_DATA_ATTRIBUTE_NAME: Symbol = Symbol("bind_group_data");
|
||||
const BINDLESS_ATTRIBUTE_NAME: Symbol = Symbol("bindless");
|
||||
const BINDING_ARRAY_MODIFIER_NAME: Symbol = Symbol("binding_array");
|
||||
const LIMIT_MODIFIER_NAME: Symbol = Symbol("limit");
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
enum BindingType {
|
||||
@ -39,6 +42,11 @@ enum BindingState<'a> {
|
||||
},
|
||||
}
|
||||
|
||||
enum BindlessSlabResourceLimitAttr {
|
||||
Auto,
|
||||
Limit(LitInt),
|
||||
}
|
||||
|
||||
pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result<TokenStream> {
|
||||
let manifest = BevyManifest::shared();
|
||||
let render_path = manifest.get_path("bevy_render");
|
||||
@ -48,14 +56,20 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result<TokenStream> {
|
||||
|
||||
let mut binding_states: Vec<BindingState> = Vec::new();
|
||||
let mut binding_impls = Vec::new();
|
||||
let mut binding_layouts = Vec::new();
|
||||
let mut bindless_binding_layouts = Vec::new();
|
||||
let mut non_bindless_binding_layouts = Vec::new();
|
||||
let mut bindless_resource_types = Vec::new();
|
||||
let mut bindless_buffer_descriptors = Vec::new();
|
||||
let mut attr_prepared_data_ident = None;
|
||||
// After the first attribute pass, this will be `None` if the object isn't
|
||||
// bindless and `Some` if it is.
|
||||
let mut attr_bindless_count = None;
|
||||
|
||||
// `actual_bindless_slot_count` holds the actual number of bindless slots
|
||||
// per bind group, taking into account whether the current platform supports
|
||||
// bindless resources.
|
||||
let actual_bindless_slot_count = Ident::new("actual_bindless_slot_count", Span::call_site());
|
||||
let bind_group_layout_entries = Ident::new("bind_group_layout_entries", Span::call_site());
|
||||
|
||||
// The `BufferBindingType` and corresponding `BufferUsages` used for
|
||||
// uniforms. We need this because bindless uniforms don't exist, so in
|
||||
@ -63,7 +77,7 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result<TokenStream> {
|
||||
let uniform_binding_type = Ident::new("uniform_binding_type", Span::call_site());
|
||||
let uniform_buffer_usages = Ident::new("uniform_buffer_usages", Span::call_site());
|
||||
|
||||
// Read struct-level attributes
|
||||
// Read struct-level attributes, first pass.
|
||||
for attr in &ast.attrs {
|
||||
if let Some(attr_ident) = attr.path().get_ident() {
|
||||
if attr_ident == BIND_GROUP_DATA_ATTRIBUTE_NAME {
|
||||
@ -72,8 +86,43 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result<TokenStream> {
|
||||
{
|
||||
attr_prepared_data_ident = Some(prepared_data_ident);
|
||||
}
|
||||
} else if attr_ident == UNIFORM_ATTRIBUTE_NAME {
|
||||
let (binding_index, converted_shader_type) = get_uniform_binding_attr(attr)?;
|
||||
} else if attr_ident == BINDLESS_ATTRIBUTE_NAME {
|
||||
match attr.meta {
|
||||
Meta::Path(_) => {
|
||||
attr_bindless_count = Some(BindlessSlabResourceLimitAttr::Auto);
|
||||
}
|
||||
Meta::List(_) => {
|
||||
// Parse bindless features. For now, the only one we
|
||||
// support is `limit(N)`.
|
||||
attr.parse_nested_meta(|submeta| {
|
||||
if submeta.path.is_ident(&LIMIT_MODIFIER_NAME) {
|
||||
let content;
|
||||
parenthesized!(content in submeta.input);
|
||||
let lit: LitInt = content.parse()?;
|
||||
|
||||
attr_bindless_count =
|
||||
Some(BindlessSlabResourceLimitAttr::Limit(lit));
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err(Error::new_spanned(attr, "Expected `limit(N)`"))
|
||||
})?;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Read struct-level attributes, second pass.
|
||||
for attr in &ast.attrs {
|
||||
if let Some(attr_ident) = attr.path().get_ident() {
|
||||
if attr_ident == UNIFORM_ATTRIBUTE_NAME {
|
||||
let UniformBindingAttr {
|
||||
binding_index,
|
||||
converted_shader_type,
|
||||
binding_array: binding_array_binding,
|
||||
} = get_uniform_binding_attr(attr)?;
|
||||
binding_impls.push(quote! {{
|
||||
use #render_path::render_resource::AsBindGroupShaderType;
|
||||
let mut buffer = #render_path::render_resource::encase::UniformBuffer::new(Vec::new());
|
||||
@ -91,30 +140,89 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result<TokenStream> {
|
||||
)
|
||||
}});
|
||||
|
||||
binding_layouts.push(quote!{
|
||||
#render_path::render_resource::BindGroupLayoutEntry {
|
||||
binding: #binding_index,
|
||||
visibility: #render_path::render_resource::ShaderStages::all(),
|
||||
ty: #render_path::render_resource::BindingType::Buffer {
|
||||
ty: #uniform_binding_type,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: Some(<#converted_shader_type as #render_path::render_resource::ShaderType>::min_size()),
|
||||
},
|
||||
count: #actual_bindless_slot_count,
|
||||
}
|
||||
// Push the binding layout. This depends on whether we're bindless or not.
|
||||
|
||||
non_bindless_binding_layouts.push(quote!{
|
||||
#bind_group_layout_entries.push(
|
||||
#render_path::render_resource::BindGroupLayoutEntry {
|
||||
binding: #binding_index,
|
||||
visibility: #render_path::render_resource::ShaderStages::all(),
|
||||
ty: #render_path::render_resource::BindingType::Buffer {
|
||||
ty: #uniform_binding_type,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: Some(<#converted_shader_type as #render_path::render_resource::ShaderType>::min_size()),
|
||||
},
|
||||
count: None,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
match binding_array_binding {
|
||||
None => {
|
||||
if attr_bindless_count.is_some() {
|
||||
return Err(Error::new_spanned(
|
||||
attr,
|
||||
"Must specify `binding_array(...)` with `#[uniform]` if the \
|
||||
object is bindless",
|
||||
));
|
||||
}
|
||||
}
|
||||
Some(binding_array_binding) => {
|
||||
if attr_bindless_count.is_none() {
|
||||
return Err(Error::new_spanned(
|
||||
attr,
|
||||
"`binding_array(...)` with `#[uniform]` requires the object to be \
|
||||
bindless",
|
||||
));
|
||||
}
|
||||
|
||||
bindless_binding_layouts.push(quote!{
|
||||
#bind_group_layout_entries.push(
|
||||
#render_path::render_resource::BindGroupLayoutEntry {
|
||||
binding: #binding_array_binding,
|
||||
visibility: #render_path::render_resource::ShaderStages::all(),
|
||||
ty: #render_path::render_resource::BindingType::Buffer {
|
||||
ty: #uniform_binding_type,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: Some(<#converted_shader_type as #render_path::render_resource::ShaderType>::min_size()),
|
||||
},
|
||||
count: #actual_bindless_slot_count,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
bindless_buffer_descriptors.push(quote! {
|
||||
#render_path::render_resource::BindlessBufferDescriptor {
|
||||
// Note that, because this is bindless, *binding
|
||||
// index* here refers to the index in the
|
||||
// bindless index table (`bindless_index`), and
|
||||
// the actual binding number is the *binding
|
||||
// array binding*.
|
||||
binding_number: #render_path::render_resource::BindingNumber(
|
||||
#binding_array_binding
|
||||
),
|
||||
bindless_index:
|
||||
#render_path::render_resource::BindlessIndex(#binding_index),
|
||||
size: <#converted_shader_type as
|
||||
#render_path::render_resource::ShaderType>::min_size().get() as
|
||||
usize,
|
||||
}
|
||||
});
|
||||
|
||||
add_bindless_resource_type(
|
||||
&render_path,
|
||||
&mut bindless_resource_types,
|
||||
binding_index,
|
||||
quote! { #render_path::render_resource::BindlessResourceType::Buffer },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let required_len = binding_index as usize + 1;
|
||||
if required_len > binding_states.len() {
|
||||
binding_states.resize(required_len, BindingState::Free);
|
||||
}
|
||||
binding_states[binding_index as usize] = BindingState::OccupiedConvertedUniform;
|
||||
} else if attr_ident == BINDLESS_ATTRIBUTE_NAME {
|
||||
if let Ok(count_lit) =
|
||||
attr.parse_args_with(|input: ParseStream| input.parse::<Lit>())
|
||||
{
|
||||
attr_bindless_count = Some(count_lit);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -135,7 +243,7 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result<TokenStream> {
|
||||
// Count the number of sampler fields needed. We might have to disable
|
||||
// bindless if bindless arrays take the GPU over the maximum number of
|
||||
// samplers.
|
||||
let mut sampler_binding_count = 0;
|
||||
let mut sampler_binding_count: u32 = 0;
|
||||
|
||||
// Read field-level attributes
|
||||
for field in fields {
|
||||
@ -225,9 +333,25 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result<TokenStream> {
|
||||
|
||||
match binding_type {
|
||||
BindingType::Uniform => {
|
||||
if attr_bindless_count.is_some() {
|
||||
return Err(Error::new_spanned(
|
||||
attr,
|
||||
"Only structure-level `#[uniform]` attributes are supported in \
|
||||
bindless mode",
|
||||
));
|
||||
}
|
||||
|
||||
// uniform codegen is deferred to account for combined uniform bindings
|
||||
}
|
||||
|
||||
BindingType::Storage => {
|
||||
if attr_bindless_count.is_some() {
|
||||
return Err(Error::new_spanned(
|
||||
attr,
|
||||
"Storage buffers are unsupported in bindless mode",
|
||||
));
|
||||
}
|
||||
|
||||
let StorageAttrs {
|
||||
visibility,
|
||||
read_only,
|
||||
@ -259,20 +383,32 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result<TokenStream> {
|
||||
});
|
||||
}
|
||||
|
||||
binding_layouts.push(quote! {
|
||||
#render_path::render_resource::BindGroupLayoutEntry {
|
||||
binding: #binding_index,
|
||||
visibility: #visibility,
|
||||
ty: #render_path::render_resource::BindingType::Buffer {
|
||||
ty: #render_path::render_resource::BufferBindingType::Storage { read_only: #read_only },
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: #actual_bindless_slot_count,
|
||||
}
|
||||
// TODO: Support bindless buffers that aren't
|
||||
// structure-level `#[uniform]` attributes.
|
||||
non_bindless_binding_layouts.push(quote! {
|
||||
#bind_group_layout_entries.push(
|
||||
#render_path::render_resource::BindGroupLayoutEntry {
|
||||
binding: #binding_index,
|
||||
visibility: #visibility,
|
||||
ty: #render_path::render_resource::BindingType::Buffer {
|
||||
ty: #render_path::render_resource::BufferBindingType::Storage { read_only: #read_only },
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: #actual_bindless_slot_count,
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
BindingType::StorageTexture => {
|
||||
if attr_bindless_count.is_some() {
|
||||
return Err(Error::new_spanned(
|
||||
attr,
|
||||
"Storage textures are unsupported in bindless mode",
|
||||
));
|
||||
}
|
||||
|
||||
let StorageTextureAttrs {
|
||||
dimension,
|
||||
image_format,
|
||||
@ -302,19 +438,22 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result<TokenStream> {
|
||||
)
|
||||
});
|
||||
|
||||
binding_layouts.push(quote! {
|
||||
#render_path::render_resource::BindGroupLayoutEntry {
|
||||
binding: #binding_index,
|
||||
visibility: #visibility,
|
||||
ty: #render_path::render_resource::BindingType::StorageTexture {
|
||||
access: #render_path::render_resource::StorageTextureAccess::#access,
|
||||
format: #render_path::render_resource::TextureFormat::#image_format,
|
||||
view_dimension: #render_path::render_resource::#dimension,
|
||||
},
|
||||
count: #actual_bindless_slot_count,
|
||||
}
|
||||
non_bindless_binding_layouts.push(quote! {
|
||||
#bind_group_layout_entries.push(
|
||||
#render_path::render_resource::BindGroupLayoutEntry {
|
||||
binding: #binding_index,
|
||||
visibility: #visibility,
|
||||
ty: #render_path::render_resource::BindingType::StorageTexture {
|
||||
access: #render_path::render_resource::StorageTextureAccess::#access,
|
||||
format: #render_path::render_resource::TextureFormat::#image_format,
|
||||
view_dimension: #render_path::render_resource::#dimension,
|
||||
},
|
||||
count: #actual_bindless_slot_count,
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
BindingType::Texture => {
|
||||
let TextureAttrs {
|
||||
dimension,
|
||||
@ -348,19 +487,64 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result<TokenStream> {
|
||||
|
||||
sampler_binding_count += 1;
|
||||
|
||||
binding_layouts.push(quote! {
|
||||
#render_path::render_resource::BindGroupLayoutEntry {
|
||||
binding: #binding_index,
|
||||
visibility: #visibility,
|
||||
ty: #render_path::render_resource::BindingType::Texture {
|
||||
multisampled: #multisampled,
|
||||
sample_type: #render_path::render_resource::#sample_type,
|
||||
view_dimension: #render_path::render_resource::#dimension,
|
||||
},
|
||||
count: #actual_bindless_slot_count,
|
||||
}
|
||||
non_bindless_binding_layouts.push(quote! {
|
||||
#bind_group_layout_entries.push(
|
||||
#render_path::render_resource::BindGroupLayoutEntry {
|
||||
binding: #binding_index,
|
||||
visibility: #visibility,
|
||||
ty: #render_path::render_resource::BindingType::Texture {
|
||||
multisampled: #multisampled,
|
||||
sample_type: #render_path::render_resource::#sample_type,
|
||||
view_dimension: #render_path::render_resource::#dimension,
|
||||
},
|
||||
count: #actual_bindless_slot_count,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
let bindless_resource_type = match *dimension {
|
||||
BindingTextureDimension::D1 => {
|
||||
quote! {
|
||||
#render_path::render_resource::BindlessResourceType::Texture1d
|
||||
}
|
||||
}
|
||||
BindingTextureDimension::D2 => {
|
||||
quote! {
|
||||
#render_path::render_resource::BindlessResourceType::Texture2d
|
||||
}
|
||||
}
|
||||
BindingTextureDimension::D2Array => {
|
||||
quote! {
|
||||
#render_path::render_resource::BindlessResourceType::Texture2dArray
|
||||
}
|
||||
}
|
||||
BindingTextureDimension::Cube => {
|
||||
quote! {
|
||||
#render_path::render_resource::BindlessResourceType::TextureCube
|
||||
}
|
||||
}
|
||||
BindingTextureDimension::CubeArray => {
|
||||
quote! {
|
||||
#render_path::render_resource::BindlessResourceType::TextureCubeArray
|
||||
}
|
||||
}
|
||||
BindingTextureDimension::D3 => {
|
||||
quote! {
|
||||
#render_path::render_resource::BindlessResourceType::Texture3d
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Add the texture to the `BindlessResourceType` list in the
|
||||
// bindless descriptor.
|
||||
add_bindless_resource_type(
|
||||
&render_path,
|
||||
&mut bindless_resource_types,
|
||||
binding_index,
|
||||
bindless_resource_type,
|
||||
);
|
||||
}
|
||||
|
||||
BindingType::Sampler => {
|
||||
let SamplerAttrs {
|
||||
sampler_binding_type,
|
||||
@ -394,7 +578,10 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result<TokenStream> {
|
||||
binding_impls.insert(0, quote! {
|
||||
(
|
||||
#binding_index,
|
||||
#render_path::render_resource::OwnedBindingResource::Sampler({
|
||||
#render_path::render_resource::OwnedBindingResource::Sampler(
|
||||
// TODO: Support other types.
|
||||
#render_path::render_resource::WgpuSamplerBindingType::Filtering,
|
||||
{
|
||||
let handle: Option<&#asset_path::Handle<#image_path::Image>> = (&self.#field_name).into();
|
||||
if let Some(handle) = handle {
|
||||
let image = images.get(handle).ok_or_else(|| #render_path::render_resource::AsBindGroupError::RetryNextUpdate)?;
|
||||
@ -426,14 +613,29 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result<TokenStream> {
|
||||
|
||||
sampler_binding_count += 1;
|
||||
|
||||
binding_layouts.push(quote!{
|
||||
#render_path::render_resource::BindGroupLayoutEntry {
|
||||
binding: #binding_index,
|
||||
visibility: #visibility,
|
||||
ty: #render_path::render_resource::BindingType::Sampler(#render_path::render_resource::#sampler_binding_type),
|
||||
count: #actual_bindless_slot_count,
|
||||
}
|
||||
non_bindless_binding_layouts.push(quote!{
|
||||
#bind_group_layout_entries.push(
|
||||
#render_path::render_resource::BindGroupLayoutEntry {
|
||||
binding: #binding_index,
|
||||
visibility: #visibility,
|
||||
ty: #render_path::render_resource::BindingType::Sampler(#render_path::render_resource::#sampler_binding_type),
|
||||
count: #actual_bindless_slot_count,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// Add the sampler to the `BindlessResourceType` list in the
|
||||
// bindless descriptor.
|
||||
//
|
||||
// TODO: Support other types of samplers.
|
||||
add_bindless_resource_type(
|
||||
&render_path,
|
||||
&mut bindless_resource_types,
|
||||
binding_index,
|
||||
quote! {
|
||||
#render_path::render_resource::BindlessResourceType::SamplerFiltering
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -495,17 +697,19 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result<TokenStream> {
|
||||
)
|
||||
}});
|
||||
|
||||
binding_layouts.push(quote!{
|
||||
#render_path::render_resource::BindGroupLayoutEntry {
|
||||
binding: #binding_index,
|
||||
visibility: #render_path::render_resource::ShaderStages::all(),
|
||||
ty: #render_path::render_resource::BindingType::Buffer {
|
||||
ty: #uniform_binding_type,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: Some(<#field_ty as #render_path::render_resource::ShaderType>::min_size()),
|
||||
},
|
||||
count: actual_bindless_slot_count,
|
||||
}
|
||||
non_bindless_binding_layouts.push(quote!{
|
||||
#bind_group_layout_entries.push(
|
||||
#render_path::render_resource::BindGroupLayoutEntry {
|
||||
binding: #binding_index,
|
||||
visibility: #render_path::render_resource::ShaderStages::all(),
|
||||
ty: #render_path::render_resource::BindingType::Buffer {
|
||||
ty: #uniform_binding_type,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: Some(<#field_ty as #render_path::render_resource::ShaderType>::min_size()),
|
||||
},
|
||||
count: #actual_bindless_slot_count,
|
||||
}
|
||||
);
|
||||
});
|
||||
// multi-field uniform bindings for a given index require an intermediate struct to derive ShaderType
|
||||
} else {
|
||||
@ -541,8 +745,8 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result<TokenStream> {
|
||||
)
|
||||
}});
|
||||
|
||||
binding_layouts.push(quote!{
|
||||
#render_path::render_resource::BindGroupLayoutEntry {
|
||||
non_bindless_binding_layouts.push(quote!{
|
||||
#bind_group_layout_entries.push(#render_path::render_resource::BindGroupLayoutEntry {
|
||||
binding: #binding_index,
|
||||
visibility: #render_path::render_resource::ShaderStages::all(),
|
||||
ty: #render_path::render_resource::BindingType::Buffer {
|
||||
@ -550,8 +754,8 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result<TokenStream> {
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: Some(<#uniform_struct_name as #render_path::render_resource::ShaderType>::min_size()),
|
||||
},
|
||||
count: actual_bindless_slot_count,
|
||||
}
|
||||
count: #actual_bindless_slot_count,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -571,46 +775,100 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result<TokenStream> {
|
||||
// Calculate the number of samplers that we need, so that we don't go over
|
||||
// the limit on certain platforms. See
|
||||
// https://github.com/bevyengine/bevy/issues/16988.
|
||||
let samplers_needed = match attr_bindless_count {
|
||||
Some(Lit::Int(ref bindless_count)) => match bindless_count.base10_parse::<u32>() {
|
||||
Ok(bindless_count) => sampler_binding_count * bindless_count,
|
||||
Err(_) => 0,
|
||||
},
|
||||
_ => 0,
|
||||
let bindless_count_syntax = match attr_bindless_count {
|
||||
Some(BindlessSlabResourceLimitAttr::Auto) => {
|
||||
quote! { #render_path::render_resource::AUTO_BINDLESS_SLAB_RESOURCE_LIMIT }
|
||||
}
|
||||
Some(BindlessSlabResourceLimitAttr::Limit(ref count)) => {
|
||||
quote! { #count }
|
||||
}
|
||||
None => quote! { 0 },
|
||||
};
|
||||
|
||||
// Calculate the actual number of bindless slots, taking hardware
|
||||
// limitations into account.
|
||||
let (bindless_slot_count, actual_bindless_slot_count_declaration) = match attr_bindless_count {
|
||||
Some(bindless_count) => (
|
||||
quote! {
|
||||
fn bindless_slot_count() -> Option<u32> {
|
||||
Some(#bindless_count)
|
||||
}
|
||||
|
||||
fn bindless_supported(render_device: &#render_path::renderer::RenderDevice) -> bool {
|
||||
render_device.features().contains(
|
||||
#render_path::settings::WgpuFeatures::BUFFER_BINDING_ARRAY |
|
||||
#render_path::settings::WgpuFeatures::TEXTURE_BINDING_ARRAY
|
||||
) &&
|
||||
render_device.limits().max_storage_buffers_per_shader_stage > 0 &&
|
||||
render_device.limits().max_samplers_per_shader_stage >= #samplers_needed
|
||||
}
|
||||
},
|
||||
quote! {
|
||||
let #actual_bindless_slot_count = if Self::bindless_supported(render_device) &&
|
||||
!force_no_bindless {
|
||||
::core::num::NonZeroU32::new(#bindless_count)
|
||||
} else {
|
||||
None
|
||||
let (bindless_slot_count, actual_bindless_slot_count_declaration, bindless_descriptor_syntax) =
|
||||
match attr_bindless_count {
|
||||
Some(ref bindless_count) => {
|
||||
let bindless_supported_syntax = quote! {
|
||||
fn bindless_supported(
|
||||
render_device: &#render_path::renderer::RenderDevice
|
||||
) -> bool {
|
||||
render_device.features().contains(
|
||||
#render_path::settings::WgpuFeatures::BUFFER_BINDING_ARRAY |
|
||||
#render_path::settings::WgpuFeatures::TEXTURE_BINDING_ARRAY
|
||||
) &&
|
||||
render_device.limits().max_storage_buffers_per_shader_stage > 0 &&
|
||||
render_device.limits().max_samplers_per_shader_stage >=
|
||||
(#sampler_binding_count * #bindless_count_syntax)
|
||||
}
|
||||
};
|
||||
},
|
||||
),
|
||||
None => (
|
||||
TokenStream::new().into(),
|
||||
quote! { let #actual_bindless_slot_count: Option<::core::num::NonZeroU32> = None; },
|
||||
),
|
||||
};
|
||||
let actual_bindless_slot_count_declaration = quote! {
|
||||
let #actual_bindless_slot_count = if Self::bindless_supported(render_device) &&
|
||||
!force_no_bindless {
|
||||
::core::num::NonZeroU32::new(#bindless_count_syntax)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
};
|
||||
let bindless_slot_count_declaration = match bindless_count {
|
||||
BindlessSlabResourceLimitAttr::Auto => {
|
||||
quote! {
|
||||
fn bindless_slot_count() -> Option<
|
||||
#render_path::render_resource::BindlessSlabResourceLimit
|
||||
> {
|
||||
Some(#render_path::render_resource::BindlessSlabResourceLimit::Auto)
|
||||
}
|
||||
}
|
||||
}
|
||||
BindlessSlabResourceLimitAttr::Limit(lit) => {
|
||||
quote! {
|
||||
fn bindless_slot_count() -> Option<
|
||||
#render_path::render_resource::BindlessSlabResourceLimit
|
||||
> {
|
||||
Some(#render_path::render_resource::BindlessSlabResourceLimit::Custom(#lit))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let bindless_buffer_descriptor_count = bindless_buffer_descriptors.len();
|
||||
|
||||
// We use `LazyLock` so that we can call `min_size`, which isn't
|
||||
// a `const fn`.
|
||||
let bindless_descriptor_syntax = quote! {
|
||||
static RESOURCES: &[#render_path::render_resource::BindlessResourceType] = &[
|
||||
#(#bindless_resource_types),*
|
||||
];
|
||||
static BUFFERS: ::std::sync::LazyLock<[
|
||||
#render_path::render_resource::BindlessBufferDescriptor;
|
||||
#bindless_buffer_descriptor_count
|
||||
]> = ::std::sync::LazyLock::new(|| {
|
||||
[#(#bindless_buffer_descriptors),*]
|
||||
});
|
||||
Some(#render_path::render_resource::BindlessDescriptor {
|
||||
resources: ::std::borrow::Cow::Borrowed(RESOURCES),
|
||||
buffers: ::std::borrow::Cow::Borrowed(&*BUFFERS),
|
||||
})
|
||||
};
|
||||
|
||||
(
|
||||
quote! {
|
||||
#bindless_slot_count_declaration
|
||||
#bindless_supported_syntax
|
||||
},
|
||||
actual_bindless_slot_count_declaration,
|
||||
bindless_descriptor_syntax,
|
||||
)
|
||||
}
|
||||
None => (
|
||||
TokenStream::new().into(),
|
||||
quote! { let #actual_bindless_slot_count: Option<::core::num::NonZeroU32> = None; },
|
||||
quote! { None },
|
||||
),
|
||||
};
|
||||
|
||||
let bindless_resource_count = bindless_resource_types.len() as u32;
|
||||
|
||||
Ok(TokenStream::from(quote! {
|
||||
#(#field_struct_impls)*
|
||||
@ -654,12 +912,54 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result<TokenStream> {
|
||||
#actual_bindless_slot_count_declaration
|
||||
#uniform_binding_type_declarations
|
||||
|
||||
vec![#(#binding_layouts,)*]
|
||||
let mut #bind_group_layout_entries = Vec::new();
|
||||
match #actual_bindless_slot_count {
|
||||
Some(bindless_slot_count) => {
|
||||
#bind_group_layout_entries.extend(
|
||||
#render_path::render_resource::create_bindless_bind_group_layout_entries(
|
||||
#bindless_resource_count,
|
||||
bindless_slot_count.into(),
|
||||
).into_iter()
|
||||
);
|
||||
#(#bindless_binding_layouts)*;
|
||||
}
|
||||
None => {
|
||||
#(#non_bindless_binding_layouts)*;
|
||||
}
|
||||
};
|
||||
#bind_group_layout_entries
|
||||
}
|
||||
|
||||
fn bindless_descriptor() -> Option<#render_path::render_resource::BindlessDescriptor> {
|
||||
#bindless_descriptor_syntax
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
/// Adds a bindless resource type to the `BindlessResourceType` array in the
|
||||
/// bindless descriptor we're building up.
|
||||
///
|
||||
/// See the `bevy_render::render_resource::bindless::BindlessResourceType`
|
||||
/// documentation for more information.
|
||||
fn add_bindless_resource_type(
|
||||
render_path: &syn::Path,
|
||||
bindless_resource_types: &mut Vec<proc_macro2::TokenStream>,
|
||||
binding_index: u32,
|
||||
bindless_resource_type: proc_macro2::TokenStream,
|
||||
) {
|
||||
// If we need to grow the array, pad the unused fields with
|
||||
// `BindlessResourceType::None`.
|
||||
if bindless_resource_types.len() < (binding_index as usize + 1) {
|
||||
bindless_resource_types.resize_with(binding_index as usize + 1, || {
|
||||
quote! { #render_path::render_resource::BindlessResourceType::None }
|
||||
});
|
||||
}
|
||||
|
||||
// Assign the `BindlessResourceType`.
|
||||
bindless_resource_types[binding_index as usize] = bindless_resource_type;
|
||||
}
|
||||
|
||||
fn get_fallback_image(
|
||||
render_path: &syn::Path,
|
||||
dimension: BindingTextureDimension,
|
||||
@ -682,8 +982,21 @@ fn get_fallback_image(
|
||||
/// like `#[uniform(LitInt, Ident)]`
|
||||
struct UniformBindingMeta {
|
||||
lit_int: LitInt,
|
||||
_comma: Comma,
|
||||
ident: Ident,
|
||||
binding_array: Option<LitInt>,
|
||||
}
|
||||
|
||||
/// The parsed structure-level `#[uniform]` attribute.
|
||||
///
|
||||
/// The corresponding syntax is `#[uniform(BINDING_INDEX, CONVERTED_SHADER_TYPE,
|
||||
/// binding_array(BINDING_ARRAY)]`.
|
||||
struct UniformBindingAttr {
|
||||
/// The binding index.
|
||||
binding_index: u32,
|
||||
/// The uniform data type.
|
||||
converted_shader_type: Ident,
|
||||
/// The binding number of the binding array, if this is a bindless material.
|
||||
binding_array: Option<u32>,
|
||||
}
|
||||
|
||||
/// Represents the arguments for any general binding attribute.
|
||||
@ -725,22 +1038,52 @@ impl Parse for BindingIndexOptions {
|
||||
}
|
||||
|
||||
impl Parse for UniformBindingMeta {
|
||||
// Parse syntax like `#[uniform(0, StandardMaterial, binding_array(10))]`.
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let lit_int = input.parse()?;
|
||||
input.parse::<Comma>()?;
|
||||
let ident = input.parse()?;
|
||||
|
||||
// Look for a `binding_array(BINDING_NUMBER)` declaration.
|
||||
let mut binding_array: Option<LitInt> = None;
|
||||
if input.parse::<Comma>().is_ok() {
|
||||
if input
|
||||
.parse::<syn::Path>()?
|
||||
.get_ident()
|
||||
.is_none_or(|ident| *ident != BINDING_ARRAY_MODIFIER_NAME)
|
||||
{
|
||||
return Err(Error::new_spanned(ident, "Expected `binding_array`"));
|
||||
}
|
||||
let parser;
|
||||
parenthesized!(parser in input);
|
||||
binding_array = Some(parser.parse()?);
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
lit_int: input.parse()?,
|
||||
_comma: input.parse()?,
|
||||
ident: input.parse()?,
|
||||
lit_int,
|
||||
ident,
|
||||
binding_array,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn get_uniform_binding_attr(attr: &syn::Attribute) -> Result<(u32, Ident)> {
|
||||
/// Parses a structure-level `#[uniform]` attribute (not a field-level
|
||||
/// `#[uniform]` attribute).
|
||||
fn get_uniform_binding_attr(attr: &syn::Attribute) -> Result<UniformBindingAttr> {
|
||||
let uniform_binding_meta = attr.parse_args_with(UniformBindingMeta::parse)?;
|
||||
|
||||
let binding_index = uniform_binding_meta.lit_int.base10_parse()?;
|
||||
let ident = uniform_binding_meta.ident;
|
||||
let binding_array = match uniform_binding_meta.binding_array {
|
||||
None => None,
|
||||
Some(binding_array) => Some(binding_array.base10_parse()?),
|
||||
};
|
||||
|
||||
Ok((binding_index, ident))
|
||||
Ok(UniformBindingAttr {
|
||||
binding_index,
|
||||
converted_shader_type: ident,
|
||||
binding_array,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_binding_nested_attr(attr: &syn::Attribute) -> Result<(u32, Vec<Meta>)> {
|
||||
|
37
crates/bevy_render/src/bindless.wgsl
Normal file
37
crates/bevy_render/src/bindless.wgsl
Normal file
@ -0,0 +1,37 @@
|
||||
// Defines the common arrays used to access bindless resources.
|
||||
//
|
||||
// This need to be kept up to date with the `BINDING_NUMBERS` table in
|
||||
// `bindless.rs`.
|
||||
//
|
||||
// You access these by indexing into the bindless index table, and from there
|
||||
// indexing into the appropriate binding array. For example, to access the base
|
||||
// color texture of a `StandardMaterial` in bindless mode, write
|
||||
// `bindless_textures_2d[materials[slot].base_color_texture]`, where
|
||||
// `materials` is the bindless index table and `slot` is the index into that
|
||||
// table (which can be found in the `Mesh`).
|
||||
|
||||
#define_import_path bevy_render::bindless
|
||||
|
||||
#ifdef BINDLESS
|
||||
|
||||
// Binding 0 is the bindless index table.
|
||||
// Filtering samplers.
|
||||
@group(2) @binding(1) var bindless_samplers_filtering: binding_array<sampler>;
|
||||
// Non-filtering samplers (nearest neighbor).
|
||||
@group(2) @binding(2) var bindless_samplers_non_filtering: binding_array<sampler>;
|
||||
// Comparison samplers (typically for shadow mapping).
|
||||
@group(2) @binding(3) var bindless_samplers_comparison: binding_array<sampler>;
|
||||
// 1D textures.
|
||||
@group(2) @binding(4) var bindless_textures_1d: binding_array<texture_1d<f32>>;
|
||||
// 2D textures.
|
||||
@group(2) @binding(5) var bindless_textures_2d: binding_array<texture_2d<f32>>;
|
||||
// 2D array textures.
|
||||
@group(2) @binding(6) var bindless_textures_2d_array: binding_array<texture_2d_array<f32>>;
|
||||
// 3D textures.
|
||||
@group(2) @binding(7) var bindless_textures_3d: binding_array<texture_3d<f32>>;
|
||||
// Cubemap textures.
|
||||
@group(2) @binding(8) var bindless_textures_cube: binding_array<texture_cube<f32>>;
|
||||
// Cubemap array textures.
|
||||
@group(2) @binding(9) var bindless_textures_cube_array: binding_array<texture_cube_array<f32>>;
|
||||
|
||||
#endif // BINDLESS
|
@ -289,6 +289,8 @@ pub const MATHS_SHADER_HANDLE: Handle<Shader> =
|
||||
weak_handle!("d94d70d4-746d-49c4-bfc3-27d63f2acda0");
|
||||
pub const COLOR_OPERATIONS_SHADER_HANDLE: Handle<Shader> =
|
||||
weak_handle!("33a80b2f-aaf7-4c86-b828-e7ae83b72f1a");
|
||||
pub const BINDLESS_SHADER_HANDLE: Handle<Shader> =
|
||||
weak_handle!("13f1baaa-41bf-448e-929e-258f9307a522");
|
||||
|
||||
impl Plugin for RenderPlugin {
|
||||
/// Initializes the renderer, sets up the [`RenderSet`] and creates the rendering sub-app.
|
||||
@ -434,6 +436,12 @@ impl Plugin for RenderPlugin {
|
||||
"color_operations.wgsl",
|
||||
Shader::from_wgsl
|
||||
);
|
||||
load_internal_asset!(
|
||||
app,
|
||||
BINDLESS_SHADER_HANDLE,
|
||||
"bindless.wgsl",
|
||||
Shader::from_wgsl
|
||||
);
|
||||
if let Some(future_render_resources) =
|
||||
app.world_mut().remove_resource::<FutureRenderResources>()
|
||||
{
|
||||
|
@ -12,7 +12,11 @@ pub use bevy_render_macros::AsBindGroup;
|
||||
use core::ops::Deref;
|
||||
use encase::ShaderType;
|
||||
use thiserror::Error;
|
||||
use wgpu::{BindGroupEntry, BindGroupLayoutEntry, BindingResource, TextureViewDimension};
|
||||
use wgpu::{
|
||||
BindGroupEntry, BindGroupLayoutEntry, BindingResource, SamplerBindingType, TextureViewDimension,
|
||||
};
|
||||
|
||||
use super::{BindlessDescriptor, BindlessSlabResourceLimit};
|
||||
|
||||
define_atomic_id!(BindGroupId);
|
||||
|
||||
@ -259,12 +263,33 @@ impl Deref for BindGroup {
|
||||
/// Some less common scenarios will require "struct-level" attributes. These are the currently supported struct-level attributes:
|
||||
/// ## `uniform(BINDING_INDEX, ConvertedShaderType)`
|
||||
///
|
||||
/// * This also creates a [`Buffer`] using [`ShaderType`] and binds it as a uniform, much
|
||||
/// like the field-level `uniform` attribute. The difference is that the entire [`AsBindGroup`] value is converted to `ConvertedShaderType`,
|
||||
/// which must implement [`ShaderType`], instead of a specific field implementing [`ShaderType`]. This is useful if more complicated conversion
|
||||
/// logic is required. The conversion is done using the [`AsBindGroupShaderType<ConvertedShaderType>`] trait, which is automatically implemented
|
||||
/// if `&Self` implements [`Into<ConvertedShaderType>`]. Only use [`AsBindGroupShaderType`] if access to resources like [`RenderAssets<GpuImage>`] is
|
||||
/// required.
|
||||
/// * This also creates a [`Buffer`] using [`ShaderType`] and binds it as a
|
||||
/// uniform, much like the field-level `uniform` attribute. The difference is
|
||||
/// that the entire [`AsBindGroup`] value is converted to `ConvertedShaderType`,
|
||||
/// which must implement [`ShaderType`], instead of a specific field
|
||||
/// implementing [`ShaderType`]. This is useful if more complicated conversion
|
||||
/// logic is required, or when using bindless mode (see below). The conversion
|
||||
/// is done using the [`AsBindGroupShaderType<ConvertedShaderType>`] trait,
|
||||
/// which is automatically implemented if `&Self` implements
|
||||
/// [`Into<ConvertedShaderType>`]. Outside of bindless mode, only use
|
||||
/// [`AsBindGroupShaderType`] if access to resources like
|
||||
/// [`RenderAssets<GpuImage>`] is required.
|
||||
///
|
||||
/// * In bindless mode (see `bindless(COUNT)`), this attribute becomes
|
||||
/// `uniform(BINDLESS_INDEX, ConvertedShaderType,
|
||||
/// binding_array(BINDING_INDEX))`. The resulting uniform buffers will be
|
||||
/// available in the shader as a binding array at the given `BINDING_INDEX`. The
|
||||
/// `BINDLESS_INDEX` specifies the offset of the buffer in the bindless index
|
||||
/// table.
|
||||
///
|
||||
/// For example, suppose that the material slot is stored in a variable named
|
||||
/// `slot`, the bindless index table is named `material_indices`, and that the
|
||||
/// first field (index 0) of the bindless index table type is named
|
||||
/// `material`. Then specifying `#[uniform(0, StandardMaterialUniform,
|
||||
/// binding_array(10)]` will create a binding array buffer declared in the
|
||||
/// shader as `var<storage> material_array:
|
||||
/// binding_array<StandardMaterialUniform>` and accessible as
|
||||
/// `material_array[material_indices[slot].material]`.
|
||||
///
|
||||
/// ## `bind_group_data(DataType)`
|
||||
///
|
||||
@ -273,30 +298,77 @@ impl Deref for BindGroup {
|
||||
/// the generated bind group, such as a unique identifier for a material's bind group. The most common use case for this attribute
|
||||
/// is "shader pipeline specialization". See [`SpecializedRenderPipeline`](crate::render_resource::SpecializedRenderPipeline).
|
||||
///
|
||||
/// ## `bindless(COUNT)`
|
||||
/// ## `bindless`
|
||||
///
|
||||
/// * This switch enables *bindless resources*, which changes the way Bevy
|
||||
/// supplies resources (uniforms, textures, and samplers) to the shader.
|
||||
/// When bindless resources are enabled, and the current platform supports
|
||||
/// them, instead of presenting a single instance of a resource to your
|
||||
/// shader Bevy will instead present a *binding array* of `COUNT` elements.
|
||||
/// In your shader, the index of the element of each binding array
|
||||
/// corresponding to the mesh currently being drawn can be retrieved with
|
||||
/// `mesh[in.instance_index].material_and_lightmap_bind_group_slot &
|
||||
/// 0xffffu`.
|
||||
/// * Bindless uniforms don't exist, so in bindless mode all uniforms and
|
||||
/// uniform buffers are automatically replaced with read-only storage
|
||||
/// buffers.
|
||||
/// * The purpose of bindless mode is to improve performance by reducing
|
||||
/// state changes. By grouping resources together into binding arrays, Bevy
|
||||
/// doesn't have to modify GPU state as often, decreasing API and driver
|
||||
/// overhead.
|
||||
/// supplies resources (textures, and samplers) to the shader. When bindless
|
||||
/// resources are enabled, and the current platform supports them, Bevy will
|
||||
/// allocate textures, and samplers into *binding arrays*, separated based on
|
||||
/// type and will supply your shader with indices into those arrays.
|
||||
/// * Bindless textures and samplers are placed into the appropriate global
|
||||
/// array defined in `bevy_render::bindless` (`bindless.wgsl`).
|
||||
/// * Bevy doesn't currently support bindless buffers, except for those created
|
||||
/// with the `uniform(BINDLESS_INDEX, ConvertedShaderType,
|
||||
/// binding_array(BINDING_INDEX))` attribute. If you need to include a buffer in
|
||||
/// your object, and you can't create the data in that buffer with the `uniform`
|
||||
/// attribute, consider a non-bindless object instead.
|
||||
/// * If bindless mode is enabled, the `BINDLESS` definition will be
|
||||
/// available. Because not all platforms support bindless resources, you
|
||||
/// should check for the presence of this definition via `#ifdef` and fall
|
||||
/// back to standard bindings if it isn't present.
|
||||
/// * In bindless mode, binding 0 becomes the *bindless index table*, which is
|
||||
/// an array of structures, each of which contains as many fields of type `u32`
|
||||
/// as the highest binding number in the structure annotated with
|
||||
/// `#[derive(AsBindGroup)]`. The *i*th field of the bindless index table
|
||||
/// contains the index of the resource with binding *i* within the appropriate
|
||||
/// binding array.
|
||||
/// * In the case of materials, the index of the applicable table within the
|
||||
/// bindless index table list corresponding to the mesh currently being drawn
|
||||
/// can be retrieved with
|
||||
/// `mesh[in.instance_index].material_and_lightmap_bind_group_slot & 0xffffu`.
|
||||
/// * You can limit the size of the bindless slabs to N resources with the
|
||||
/// `limit(N)` declaration. For example, `#[bindless(limit(16))]` ensures that
|
||||
/// each slab will have no more than 16 total resources in it. If you don't
|
||||
/// specify a limit, Bevy automatically picks a reasonable one for the current
|
||||
/// platform.
|
||||
/// * The purpose of bindless mode is to improve performance by reducing
|
||||
/// state changes. By grouping resources together into binding arrays, Bevy
|
||||
/// doesn't have to modify GPU state as often, decreasing API and driver
|
||||
/// overhead.
|
||||
/// * See the `shaders/shader_material_bindless` example for an example of
|
||||
/// how to use bindless mode.
|
||||
/// * The following diagram illustrates how bindless mode works using a subset
|
||||
/// of `StandardMaterial`:
|
||||
///
|
||||
/// ```text
|
||||
/// Shader Bindings Sampler Binding Array
|
||||
/// +----+-----------------------------+ +-----------+-----------+-----+
|
||||
/// +---| 0 | material_indices | +->| sampler 0 | sampler 1 | ... |
|
||||
/// | +----+-----------------------------+ | +-----------+-----------+-----+
|
||||
/// | | 1 | bindless_samplers_filtering +--+ ^
|
||||
/// | +----+-----------------------------+ +-------------------------------+
|
||||
/// | | .. | ... | |
|
||||
/// | +----+-----------------------------+ Texture Binding Array |
|
||||
/// | | 5 | bindless_textures_2d +--+ +-----------+-----------+-----+ |
|
||||
/// | +----+-----------------------------+ +->| texture 0 | texture 1 | ... | |
|
||||
/// | | .. | ... | +-----------+-----------+-----+ |
|
||||
/// | +----+-----------------------------+ ^ |
|
||||
/// | + 10 | material_array +--+ +---------------------------+ |
|
||||
/// | +----+-----------------------------+ | | |
|
||||
/// | | Buffer Binding Array | |
|
||||
/// | | +----------+----------+-----+ | |
|
||||
/// | +->| buffer 0 | buffer 1 | ... | | |
|
||||
/// | Material Bindless Indices +----------+----------+-----+ | |
|
||||
/// | +----+-----------------------------+ ^ | |
|
||||
/// +-->| 0 | material +----------+ | |
|
||||
/// +----+-----------------------------+ | |
|
||||
/// | 1 | base_color_texture +---------------------------------------+ |
|
||||
/// +----+-----------------------------+ |
|
||||
/// | 2 | base_color_sampler +-------------------------------------------+
|
||||
/// +----+-----------------------------+
|
||||
/// | .. | ... |
|
||||
/// +----+-----------------------------+
|
||||
/// ```
|
||||
///
|
||||
/// The previous `CoolMaterial` example illustrating "combining multiple field-level uniform attributes with the same binding index" can
|
||||
/// also be equivalently represented with a single struct-level uniform attribute:
|
||||
@ -364,7 +436,7 @@ pub trait AsBindGroup {
|
||||
/// Note that the *actual* slot count may be different from this value, due
|
||||
/// to platform limitations. For example, if bindless resources aren't
|
||||
/// supported on this platform, the actual slot count will be 1.
|
||||
fn bindless_slot_count() -> Option<u32> {
|
||||
fn bindless_slot_count() -> Option<BindlessSlabResourceLimit> {
|
||||
None
|
||||
}
|
||||
|
||||
@ -451,6 +523,10 @@ pub trait AsBindGroup {
|
||||
) -> Vec<BindGroupLayoutEntry>
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
fn bindless_descriptor() -> Option<BindlessDescriptor> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// An error that occurs during [`AsBindGroup::as_bind_group`] calls.
|
||||
@ -490,7 +566,7 @@ pub struct BindingResources(pub Vec<(u32, OwnedBindingResource)>);
|
||||
pub enum OwnedBindingResource {
|
||||
Buffer(Buffer),
|
||||
TextureView(TextureViewDimension, TextureView),
|
||||
Sampler(Sampler),
|
||||
Sampler(SamplerBindingType, Sampler),
|
||||
}
|
||||
|
||||
impl OwnedBindingResource {
|
||||
@ -498,7 +574,7 @@ impl OwnedBindingResource {
|
||||
match self {
|
||||
OwnedBindingResource::Buffer(buffer) => buffer.as_entire_binding(),
|
||||
OwnedBindingResource::TextureView(_, view) => BindingResource::TextureView(view),
|
||||
OwnedBindingResource::Sampler(sampler) => BindingResource::Sampler(sampler),
|
||||
OwnedBindingResource::Sampler(_, sampler) => BindingResource::Sampler(sampler),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
313
crates/bevy_render/src/render_resource/bindless.rs
Normal file
313
crates/bevy_render/src/render_resource/bindless.rs
Normal file
@ -0,0 +1,313 @@
|
||||
//! Types and functions relating to bindless resources.
|
||||
|
||||
use alloc::borrow::Cow;
|
||||
use core::num::{NonZeroU32, NonZeroU64};
|
||||
|
||||
use bevy_derive::{Deref, DerefMut};
|
||||
use wgpu::{
|
||||
BindGroupLayoutEntry, SamplerBindingType, ShaderStages, TextureSampleType, TextureViewDimension,
|
||||
};
|
||||
|
||||
use crate::render_resource::binding_types::storage_buffer_read_only_sized;
|
||||
|
||||
use super::binding_types::{
|
||||
sampler, texture_1d, texture_2d, texture_2d_array, texture_3d, texture_cube, texture_cube_array,
|
||||
};
|
||||
|
||||
/// The default value for the number of resources that can be stored in a slab
|
||||
/// on this platform.
|
||||
///
|
||||
/// See the documentation for [`BindlessSlabResourceLimit`] for more
|
||||
/// information.
|
||||
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
||||
pub const AUTO_BINDLESS_SLAB_RESOURCE_LIMIT: u32 = 64;
|
||||
/// The default value for the number of resources that can be stored in a slab
|
||||
/// on this platform.
|
||||
///
|
||||
/// See the documentation for [`BindlessSlabResourceLimit`] for more
|
||||
/// information.
|
||||
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
|
||||
pub const AUTO_BINDLESS_SLAB_RESOURCE_LIMIT: u32 = 2048;
|
||||
|
||||
/// The binding numbers for the built-in binding arrays of each bindless
|
||||
/// resource type.
|
||||
///
|
||||
/// In the case of materials, the material allocator manages these binding
|
||||
/// arrays.
|
||||
///
|
||||
/// `bindless.wgsl` contains declarations of these arrays for use in your
|
||||
/// shaders. If you change these, make sure to update that file as well.
|
||||
pub static BINDING_NUMBERS: [(BindlessResourceType, BindingNumber); 9] = [
|
||||
(BindlessResourceType::SamplerFiltering, BindingNumber(1)),
|
||||
(BindlessResourceType::SamplerNonFiltering, BindingNumber(2)),
|
||||
(BindlessResourceType::SamplerComparison, BindingNumber(3)),
|
||||
(BindlessResourceType::Texture1d, BindingNumber(4)),
|
||||
(BindlessResourceType::Texture2d, BindingNumber(5)),
|
||||
(BindlessResourceType::Texture2dArray, BindingNumber(6)),
|
||||
(BindlessResourceType::Texture3d, BindingNumber(7)),
|
||||
(BindlessResourceType::TextureCube, BindingNumber(8)),
|
||||
(BindlessResourceType::TextureCubeArray, BindingNumber(9)),
|
||||
];
|
||||
|
||||
/// The maximum number of resources that can be stored in a slab.
|
||||
///
|
||||
/// This limit primarily exists in order to work around `wgpu` performance
|
||||
/// problems involving large numbers of bindless resources. Also, some
|
||||
/// platforms, such as Metal, currently enforce limits on the number of
|
||||
/// resources in use.
|
||||
///
|
||||
/// This corresponds to `LIMIT` in the `#[bindless(LIMIT)]` attribute when
|
||||
/// deriving [`crate::render_resource::AsBindGroup`].
|
||||
#[derive(Clone, Copy, Default, PartialEq, Debug)]
|
||||
pub enum BindlessSlabResourceLimit {
|
||||
/// Allows the renderer to choose a reasonable value for the resource limit
|
||||
/// based on the platform.
|
||||
///
|
||||
/// This value has been tuned, so you should default to this value unless
|
||||
/// you have special platform-specific considerations that prevent you from
|
||||
/// using it.
|
||||
#[default]
|
||||
Auto,
|
||||
|
||||
/// A custom value for the resource limit.
|
||||
///
|
||||
/// Bevy will allocate no more than this number of resources in a slab,
|
||||
/// unless exceeding this value is necessary in order to allocate at all
|
||||
/// (i.e. unless the number of bindless resources in your bind group exceeds
|
||||
/// this value), in which case Bevy can exceed it.
|
||||
Custom(u32),
|
||||
}
|
||||
|
||||
/// Information about the bindless resources in this object.
|
||||
///
|
||||
/// The material bind group allocator uses this descriptor in order to create
|
||||
/// and maintain bind groups. The fields within this bindless descriptor are
|
||||
/// [`Cow`]s in order to support both the common case in which the fields are
|
||||
/// simply `static` constants and the more unusual case in which the fields are
|
||||
/// dynamically generated efficiently. An example of the latter case is
|
||||
/// `ExtendedMaterial`, which needs to assemble a bindless descriptor from those
|
||||
/// of the base material and the material extension at runtime.
|
||||
///
|
||||
/// This structure will only be present if this object is bindless.
|
||||
pub struct BindlessDescriptor {
|
||||
/// The bindless resource types that this object uses, in order of bindless
|
||||
/// index.
|
||||
///
|
||||
/// The resource assigned to binding index 0 will be at index 0, the
|
||||
/// resource assigned to binding index will be at index 1 in this array, and
|
||||
/// so on. Unused binding indices are set to [`BindlessResourceType::None`].
|
||||
pub resources: Cow<'static, [BindlessResourceType]>,
|
||||
/// The [`BindlessBufferDescriptor`] for each bindless buffer that this
|
||||
/// object uses.
|
||||
///
|
||||
/// The order of this array is irrelevant.
|
||||
pub buffers: Cow<'static, [BindlessBufferDescriptor]>,
|
||||
}
|
||||
|
||||
/// The type of potentially-bindless resource.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||
pub enum BindlessResourceType {
|
||||
/// No bindless resource.
|
||||
///
|
||||
/// This is used as a placeholder to fill holes in the
|
||||
/// [`BindlessDescriptor::resources`] list.
|
||||
None,
|
||||
/// A storage buffer.
|
||||
Buffer,
|
||||
/// A filtering sampler.
|
||||
SamplerFiltering,
|
||||
/// A non-filtering sampler (nearest neighbor).
|
||||
SamplerNonFiltering,
|
||||
/// A comparison sampler (typically used for shadow maps).
|
||||
SamplerComparison,
|
||||
/// A 1D texture.
|
||||
Texture1d,
|
||||
/// A 2D texture.
|
||||
Texture2d,
|
||||
/// A 2D texture array.
|
||||
///
|
||||
/// Note that this differs from a binding array. 2D texture arrays must all
|
||||
/// have the same size and format.
|
||||
Texture2dArray,
|
||||
/// A 3D texture.
|
||||
Texture3d,
|
||||
/// A cubemap texture.
|
||||
TextureCube,
|
||||
/// A cubemap texture array.
|
||||
///
|
||||
/// Note that this differs from a binding array. Cubemap texture arrays must
|
||||
/// all have the same size and format.
|
||||
TextureCubeArray,
|
||||
}
|
||||
|
||||
/// Describes a bindless buffer.
|
||||
///
|
||||
/// Unlike samplers and textures, each buffer in a bind group gets its own
|
||||
/// unique bind group entry. That is, there isn't any `bindless_buffers` binding
|
||||
/// array to go along with `bindless_textures_2d`,
|
||||
/// `bindless_samplers_filtering`, etc. Therefore, this descriptor contains two
|
||||
/// indices: the *binding number* and the *bindless index*. The binding number
|
||||
/// is the `@binding` number used in the shader, while the bindless index is the
|
||||
/// index of the buffer in the bindless index table (which is itself
|
||||
/// conventionally bound to binding number 0).
|
||||
///
|
||||
/// When declaring the buffer in a derived implementation
|
||||
/// [`crate::render_resource::AsBindGroup`] with syntax like
|
||||
/// `#[uniform(BINDLESS_INDEX, StandardMaterialUniform,
|
||||
/// bindless(BINDING_NUMBER)]`, the bindless index is `BINDLESS_INDEX`, and the
|
||||
/// binding number is `BINDING_NUMBER`. Note the order.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct BindlessBufferDescriptor {
|
||||
/// The actual binding number of the buffer.
|
||||
///
|
||||
/// This is declared with `@binding` in WGSL. When deriving
|
||||
/// [`crate::render_resource::AsBindGroup`], this is the `BINDING_NUMBER` in
|
||||
/// `#[uniform(BINDLESS_INDEX, StandardMaterialUniform,
|
||||
/// bindless(BINDING_NUMBER)]`.
|
||||
pub binding_number: BindingNumber,
|
||||
/// The index of the buffer in the bindless index table.
|
||||
///
|
||||
/// In the shader, this is the index into the table bound to binding 0. When
|
||||
/// deriving [`crate::render_resource::AsBindGroup`], this is the
|
||||
/// `BINDLESS_INDEX` in `#[uniform(BINDLESS_INDEX, StandardMaterialUniform,
|
||||
/// bindless(BINDING_NUMBER)]`.
|
||||
pub bindless_index: BindlessIndex,
|
||||
/// The size of the buffer in bytes.
|
||||
pub size: usize,
|
||||
}
|
||||
|
||||
/// The index of the actual binding in the bind group.
|
||||
///
|
||||
/// This is the value specified in WGSL as `@binding`.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Deref, DerefMut)]
|
||||
pub struct BindingNumber(pub u32);
|
||||
|
||||
/// The index in the bindless index table.
|
||||
///
|
||||
/// This table is conventionally bound to binding number 0.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Deref, DerefMut)]
|
||||
pub struct BindlessIndex(pub u32);
|
||||
|
||||
/// Creates the bind group layout entries common to all shaders that use
|
||||
/// bindless bind groups.
|
||||
///
|
||||
/// `bindless_resource_count` specifies the total number of bindless resources.
|
||||
/// `bindless_slab_resource_limit` specifies the resolved
|
||||
/// [`BindlessSlabResourceLimit`] value.
|
||||
pub fn create_bindless_bind_group_layout_entries(
|
||||
bindless_resource_count: u32,
|
||||
bindless_slab_resource_limit: u32,
|
||||
) -> Vec<BindGroupLayoutEntry> {
|
||||
let bindless_slab_resource_limit =
|
||||
NonZeroU32::new(bindless_slab_resource_limit).expect("Bindless slot count must be nonzero");
|
||||
|
||||
// The maximum size of a binding array is the
|
||||
// `bindless_slab_resource_limit`, which would occur if all of the bindless
|
||||
// resources were of the same type. So we create our binding arrays with
|
||||
// that size.
|
||||
|
||||
vec![
|
||||
// Start with the bindless index table, bound to binding number 0.
|
||||
storage_buffer_read_only_sized(
|
||||
false,
|
||||
NonZeroU64::new(bindless_resource_count as u64 * size_of::<u32>() as u64),
|
||||
)
|
||||
.build(0, ShaderStages::all()),
|
||||
// Continue with the common bindless buffers.
|
||||
sampler(SamplerBindingType::Filtering)
|
||||
.count(bindless_slab_resource_limit)
|
||||
.build(1, ShaderStages::all()),
|
||||
sampler(SamplerBindingType::NonFiltering)
|
||||
.count(bindless_slab_resource_limit)
|
||||
.build(2, ShaderStages::all()),
|
||||
sampler(SamplerBindingType::Comparison)
|
||||
.count(bindless_slab_resource_limit)
|
||||
.build(3, ShaderStages::all()),
|
||||
texture_1d(TextureSampleType::Float { filterable: true })
|
||||
.count(bindless_slab_resource_limit)
|
||||
.build(4, ShaderStages::all()),
|
||||
texture_2d(TextureSampleType::Float { filterable: true })
|
||||
.count(bindless_slab_resource_limit)
|
||||
.build(5, ShaderStages::all()),
|
||||
texture_2d_array(TextureSampleType::Float { filterable: true })
|
||||
.count(bindless_slab_resource_limit)
|
||||
.build(6, ShaderStages::all()),
|
||||
texture_3d(TextureSampleType::Float { filterable: true })
|
||||
.count(bindless_slab_resource_limit)
|
||||
.build(7, ShaderStages::all()),
|
||||
texture_cube(TextureSampleType::Float { filterable: true })
|
||||
.count(bindless_slab_resource_limit)
|
||||
.build(8, ShaderStages::all()),
|
||||
texture_cube_array(TextureSampleType::Float { filterable: true })
|
||||
.count(bindless_slab_resource_limit)
|
||||
.build(9, ShaderStages::all()),
|
||||
]
|
||||
}
|
||||
|
||||
impl BindlessSlabResourceLimit {
|
||||
/// Determines the actual bindless slab resource limit on this platform.
|
||||
pub fn resolve(&self) -> u32 {
|
||||
match *self {
|
||||
BindlessSlabResourceLimit::Auto => AUTO_BINDLESS_SLAB_RESOURCE_LIMIT,
|
||||
BindlessSlabResourceLimit::Custom(limit) => limit,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BindlessResourceType {
|
||||
/// Returns the binding number for the common array of this resource type.
|
||||
///
|
||||
/// For example, if you pass `BindlessResourceType::Texture2d`, this will
|
||||
/// return 5, in order to match the `@group(2) @binding(5) var
|
||||
/// bindless_textures_2d: binding_array<texture_2d<f32>>` declaration in
|
||||
/// `bindless.wgsl`.
|
||||
///
|
||||
/// Not all resource types have fixed binding numbers. If you call
|
||||
/// [`Self::binding_number`] on such a resource type, it returns `None`.
|
||||
///
|
||||
/// Note that this returns a static reference to the binding number, not the
|
||||
/// binding number itself. This is to conform to an idiosyncratic API in
|
||||
/// `wgpu` whereby binding numbers for binding arrays are taken by `&u32`
|
||||
/// *reference*, not by `u32` value.
|
||||
pub fn binding_number(&self) -> Option<&'static BindingNumber> {
|
||||
match BINDING_NUMBERS.binary_search_by_key(self, |(key, _)| *key) {
|
||||
Ok(binding_number) => Some(&BINDING_NUMBERS[binding_number].1),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TextureViewDimension> for BindlessResourceType {
|
||||
fn from(texture_view_dimension: TextureViewDimension) -> Self {
|
||||
match texture_view_dimension {
|
||||
TextureViewDimension::D1 => BindlessResourceType::Texture1d,
|
||||
TextureViewDimension::D2 => BindlessResourceType::Texture2d,
|
||||
TextureViewDimension::D2Array => BindlessResourceType::Texture2dArray,
|
||||
TextureViewDimension::Cube => BindlessResourceType::TextureCube,
|
||||
TextureViewDimension::CubeArray => BindlessResourceType::TextureCubeArray,
|
||||
TextureViewDimension::D3 => BindlessResourceType::Texture3d,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SamplerBindingType> for BindlessResourceType {
|
||||
fn from(sampler_binding_type: SamplerBindingType) -> Self {
|
||||
match sampler_binding_type {
|
||||
SamplerBindingType::Filtering => BindlessResourceType::SamplerFiltering,
|
||||
SamplerBindingType::NonFiltering => BindlessResourceType::SamplerNonFiltering,
|
||||
SamplerBindingType::Comparison => BindlessResourceType::SamplerComparison,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for BindlessIndex {
|
||||
fn from(value: u32) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for BindingNumber {
|
||||
fn from(value: u32) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ mod bind_group;
|
||||
mod bind_group_entries;
|
||||
mod bind_group_layout;
|
||||
mod bind_group_layout_entries;
|
||||
mod bindless;
|
||||
mod buffer;
|
||||
mod buffer_vec;
|
||||
mod gpu_array_buffer;
|
||||
@ -19,6 +20,7 @@ pub use bind_group::*;
|
||||
pub use bind_group_entries::*;
|
||||
pub use bind_group_layout::*;
|
||||
pub use bind_group_layout_entries::*;
|
||||
pub use bindless::*;
|
||||
pub use buffer::*;
|
||||
pub use buffer_vec::*;
|
||||
pub use gpu_array_buffer::*;
|
||||
@ -49,10 +51,11 @@ pub use wgpu::{
|
||||
PrimitiveState, PrimitiveTopology, PushConstantRange, RenderPassColorAttachment,
|
||||
RenderPassDepthStencilAttachment, RenderPassDescriptor,
|
||||
RenderPipelineDescriptor as RawRenderPipelineDescriptor, Sampler as WgpuSampler,
|
||||
SamplerBindingType, SamplerDescriptor, ShaderModule, ShaderModuleDescriptor, ShaderSource,
|
||||
ShaderStages, StencilFaceState, StencilOperation, StencilState, StorageTextureAccess, StoreOp,
|
||||
TexelCopyBufferInfo, TexelCopyBufferLayout, TexelCopyTextureInfo, TextureAspect,
|
||||
TextureDescriptor, TextureDimension, TextureFormat, TextureSampleType, TextureUsages,
|
||||
SamplerBindingType, SamplerBindingType as WgpuSamplerBindingType, SamplerDescriptor,
|
||||
ShaderModule, ShaderModuleDescriptor, ShaderSource, ShaderStages, StencilFaceState,
|
||||
StencilOperation, StencilState, StorageTextureAccess, StoreOp, TexelCopyBufferInfo,
|
||||
TexelCopyBufferLayout, TexelCopyTextureInfo, TextureAspect, TextureDescriptor,
|
||||
TextureDimension, TextureFormat, TextureSampleType, TextureUsages,
|
||||
TextureView as WgpuTextureView, TextureViewDescriptor, TextureViewDimension, VertexAttribute,
|
||||
VertexBufferLayout as RawVertexBufferLayout, VertexFormat, VertexState as RawVertexState,
|
||||
VertexStepMode, COPY_BUFFER_ALIGNMENT,
|
||||
|
@ -1,18 +1,18 @@
|
||||
//! A material that uses bindless textures.
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy::render::render_resource::{AsBindGroup, ShaderRef};
|
||||
use bevy::render::render_resource::{AsBindGroup, ShaderRef, ShaderType};
|
||||
|
||||
const SHADER_ASSET_PATH: &str = "shaders/bindless_material.wgsl";
|
||||
|
||||
// `#[bindless(4)]` indicates that we want Bevy to group materials into bind
|
||||
// groups of at most 4 materials each.
|
||||
// `#[bindless(limit(4))]` indicates that we want Bevy to group materials into
|
||||
// bind groups of at most 4 materials each.
|
||||
// Note that we use the structure-level `#[uniform]` attribute to supply
|
||||
// ordinary data to the shader.
|
||||
#[derive(Asset, TypePath, AsBindGroup, Debug, Clone)]
|
||||
#[bindless(4)]
|
||||
#[uniform(0, BindlessMaterialUniform, binding_array(10))]
|
||||
#[bindless(limit(4))]
|
||||
struct BindlessMaterial {
|
||||
// This will be exposed to the shader as a binding array of 4 *storage*
|
||||
// buffers (as bindless uniforms don't exist).
|
||||
#[uniform(0)]
|
||||
color: LinearRgba,
|
||||
// This will be exposed to the shader as a binding array of 4 textures and a
|
||||
// binding array of 4 samplers.
|
||||
@ -21,6 +21,20 @@ struct BindlessMaterial {
|
||||
color_texture: Option<Handle<Image>>,
|
||||
}
|
||||
|
||||
// This buffer will be presented to the shader as `@binding(10)`.
|
||||
#[derive(ShaderType)]
|
||||
struct BindlessMaterialUniform {
|
||||
color: LinearRgba,
|
||||
}
|
||||
|
||||
impl<'a> From<&'a BindlessMaterial> for BindlessMaterialUniform {
|
||||
fn from(material: &'a BindlessMaterial) -> Self {
|
||||
BindlessMaterialUniform {
|
||||
color: material.color,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The entry point.
|
||||
fn main() {
|
||||
App::new()
|
||||
|
Loading…
Reference in New Issue
Block a user