Skip to content

Commit

Permalink
Make StandardMaterial bindless. (#16644)
Browse files Browse the repository at this point in the history
This commit makes `StandardMaterial` use bindless textures, as
implemented in PR #16368. Non-bindless mode, as used for example in
Metal and WebGL 2, remains fully supported via a plethora of `#ifdef
BINDLESS` preprocessor definitions.

Unfortunately, this PR introduces quite a bit of unsightliness into the
PBR shaders. This is a result of the fact that WGSL supports neither
passing binding arrays to functions nor passing individual *elements* of
binding arrays to functions, except directly to texture sample
functions. Thus we're unable to use the `sample_texture` abstraction
that helped abstract over the meshlet and non-meshlet paths. I don't
think there's anything we can do to help this other than to suggest
improvements to upstream Naga.
  • Loading branch information
pcwalton authored Dec 10, 2024
1 parent 7236070 commit 7ed1f32
Show file tree
Hide file tree
Showing 14 changed files with 574 additions and 166 deletions.
38 changes: 33 additions & 5 deletions crates/bevy_pbr/src/extended_material.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,14 +155,32 @@ 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,
) -> 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
|| B::BINDLESS_SLOT_COUNT.is_none()
|| E::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)?;
let extended_bindgroup =
E::unprepared_bind_group(&self.extension, layout, render_device, extended_param)?;
} = 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,
)?;

bindings.extend(extended_bindgroup.bindings.0);

Expand All @@ -174,13 +192,23 @@ impl<B: Material, E: MaterialExtension> AsBindGroup for ExtendedMaterial<B, E> {

fn bind_group_layout_entries(
render_device: &RenderDevice,
mut force_no_bindless: 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
|| B::BINDLESS_SLOT_COUNT.is_none()
|| E::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);
entries.extend(E::bind_group_layout_entries(render_device));
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,
));
entries
}
}
Expand Down
1 change: 1 addition & 0 deletions crates/bevy_pbr/src/material.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1062,6 +1062,7 @@ impl<M: Material> RenderAsset for PreparedMaterial<M> {
&pipeline.material_layout,
render_device,
material_param,
false,
) {
Ok(unprepared) => {
bind_group_allocator.init(render_device, *material_binding_id, unprepared);
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_pbr/src/material_bind_groups.rs
Original file line number Diff line number Diff line change
Expand Up @@ -767,7 +767,7 @@ where
fn from_world(world: &mut World) -> Self {
// Create a new bind group allocator.
let render_device = world.resource::<RenderDevice>();
let bind_group_layout_entries = M::bind_group_layout_entries(render_device);
let bind_group_layout_entries = M::bind_group_layout_entries(render_device, false);
let bind_group_layout =
render_device.create_bind_group_layout(M::label(), &bind_group_layout_entries);
let fallback_buffers =
Expand Down
1 change: 1 addition & 0 deletions crates/bevy_pbr/src/pbr_material.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ pub enum UvChannel {
#[derive(Asset, AsBindGroup, Reflect, Debug, Clone)]
#[bind_group_data(StandardMaterialKey)]
#[uniform(0, StandardMaterialUniform)]
#[bindless(16)]
#[reflect(Default, Debug)]
pub struct StandardMaterial {
/// The color of the surface of the material before lighting.
Expand Down
5 changes: 5 additions & 0 deletions crates/bevy_pbr/src/prepass/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,11 @@ where
shader_defs.push("HAS_PREVIOUS_MORPH".into());
}

// If bindless mode is on, add a `BINDLESS` define.
if self.material_pipeline.bindless {
shader_defs.push("BINDLESS".into());
}

if key
.mesh_key
.contains(MeshPipelineKey::VISIBILITY_RANGE_DITHER)
Expand Down
29 changes: 22 additions & 7 deletions crates/bevy_pbr/src/render/parallax_mapping.wgsl
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
#define_import_path bevy_pbr::parallax_mapping

#import bevy_pbr::pbr_bindings::{depth_map_texture, depth_map_sampler}
#import bevy_pbr::{
pbr_bindings::{depth_map_texture, depth_map_sampler},
mesh_bindings::mesh
}

fn sample_depth_map(uv: vec2<f32>) -> f32 {
fn sample_depth_map(uv: vec2<f32>, instance_index: u32) -> f32 {
let slot = mesh[instance_index].material_bind_group_slot;
// We use `textureSampleLevel` over `textureSample` because the wgpu DX12
// backend (Fxc) panics when using "gradient instructions" inside a loop.
// It results in the whole loop being unrolled by the shader compiler,
Expand All @@ -13,7 +17,17 @@ fn sample_depth_map(uv: vec2<f32>) -> f32 {
// the MIP level, so no gradient instructions are used, and we can use
// sample_depth_map in our loop.
// See https://stackoverflow.com/questions/56581141/direct3d11-gradient-instruction-used-in-a-loop-with-varying-iteration-forcing
return textureSampleLevel(depth_map_texture, depth_map_sampler, uv, 0.0).r;
return textureSampleLevel(
#ifdef BINDLESS
depth_map_texture[slot],
depth_map_sampler[slot],
#else // BINDLESS
depth_map_texture,
depth_map_sampler,
#endif // BINDLESS
uv,
0.0
).r;
}

// An implementation of parallax mapping, see https://en.wikipedia.org/wiki/Parallax_mapping
Expand All @@ -26,6 +40,7 @@ fn parallaxed_uv(
original_uv: vec2<f32>,
// The vector from the camera to the fragment at the surface in tangent space
Vt: vec3<f32>,
instance_index: u32,
) -> vec2<f32> {
if max_layer_count < 1.0 {
return original_uv;
Expand Down Expand Up @@ -53,15 +68,15 @@ fn parallaxed_uv(
var delta_uv = depth_scale * layer_depth * Vt.xy * vec2(1.0, -1.0) / view_steepness;

var current_layer_depth = 0.0;
var texture_depth = sample_depth_map(uv);
var texture_depth = sample_depth_map(uv, instance_index);

// texture_depth > current_layer_depth means the depth map depth is deeper
// than the depth the ray would be at this UV offset so the ray has not
// intersected the surface
for (var i: i32 = 0; texture_depth > current_layer_depth && i <= i32(layer_count); i++) {
current_layer_depth += layer_depth;
uv += delta_uv;
texture_depth = sample_depth_map(uv);
texture_depth = sample_depth_map(uv, instance_index);
}

#ifdef RELIEF_MAPPING
Expand All @@ -79,7 +94,7 @@ fn parallaxed_uv(
current_layer_depth -= delta_depth;

for (var i: u32 = 0u; i < max_steps; i++) {
texture_depth = sample_depth_map(uv);
texture_depth = sample_depth_map(uv, instance_index);

// Halve the deltas for the next step
delta_uv *= 0.5;
Expand All @@ -103,7 +118,7 @@ fn parallaxed_uv(
// may skip small details and result in writhing material artifacts.
let previous_uv = uv - delta_uv;
let next_depth = texture_depth - current_layer_depth;
let previous_depth = sample_depth_map(previous_uv) - current_layer_depth + layer_depth;
let previous_depth = sample_depth_map(previous_uv, instance_index) - current_layer_depth + layer_depth;

let weight = next_depth / (next_depth - previous_depth);

Expand Down
48 changes: 45 additions & 3 deletions crates/bevy_pbr/src/render/pbr_bindings.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@

#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>;
#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;
Expand All @@ -15,23 +30,50 @@
@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
#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
#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
#endif // BINDLESS
#endif // PBR_MULTI_LAYER_MATERIAL_TEXTURES_SUPPORTED
Loading

0 comments on commit 7ed1f32

Please sign in to comment.