Variable MeshPipeline View Bind Group Layout (#10156)

# Objective

This PR aims to make it so that we don't accidentally go over
`MAX_TEXTURE_IMAGE_UNITS` (in WebGL) or
`maxSampledTexturesPerShaderStage` (in WebGPU), giving us some extra
leeway to add more view bind group textures.

(This PR is extracted from—and unblocks—#8015)

## Solution

- We replace the existing `view_layout` and `view_layout_multisampled`
pair with an array of 32 bind group layouts, generated ahead of time;
- For now, these layouts cover all the possible combinations of:
`multisampled`, `depth_prepass`, `normal_prepass`,
`motion_vector_prepass` and `deferred_prepass`:
- In the future, as @JMS55 pointed out, we can likely take out
`motion_vector_prepass` and `deferred_prepass`, as these are not really
needed for the mesh pipeline and can use separate pipelines. This would
bring the possible combinations down to 8;
- We can also add more "optional" textures as they become needed,
allowing the engine to scale to a wider variety of use cases in lower
end/web environments (e.g. some apps might just want normal and depth
prepasses, others might only want light probes), while still keeping a
high ceiling for high end native environments where more textures are
supported.
- While preallocating bind group layouts is relatively cheap, the number
of combinations grows exponentially, so we should likely limit ourselves
to something like at most 256–1024 total layouts until we find a better
solution (like generating them lazily)
- To make this mechanism a little bit more explicit/discoverable, so
that compatibility with WebGPU/WebGL is not broken by accident, we add a
`MESH_PIPELINE_VIEW_LAYOUT_SAFE_MAX_TEXTURES` const and warn whenever
the number of textures in the layout crosses it.
- The warning is gated by `#[cfg(debug_assertions)]` and not issued in
release builds;
- We're counting the actual textures in the bind group layout instead of
using some roundabout metric so it should be accurate;
- Right now `MESH_PIPELINE_VIEW_LAYOUT_SAFE_MAX_TEXTURES` is set to 10
in order to leave 6 textures free for other groups;
- Currently there's no combination that would cause us to go over the
limit, but that will change once #8015 lands.

---

## Changelog

- `MeshPipeline` view bind group layouts now vary based on the current
multisampling and prepass states, saving a couple of texture binding
entries when prepasses are not in use.

## Migration Guide

- `MeshPipeline::view_layout` and
`MeshPipeline::view_layout_multisampled` have been replaced with a
private array to accomodate for variable view bind group layouts. To
obtain a view bind group layout for the current pipeline state, use the
new `MeshPipeline::get_view_layout()` or
`MeshPipeline::get_view_layout_from_key()` methods.
This commit is contained in:
Marco Buono 2023-10-21 08:19:44 -03:00 committed by GitHub
parent 6f27e0e35f
commit 9b80205acb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 760 additions and 515 deletions

View file

@ -1 +1 @@
doc-valid-idents = ["sRGB", "NaN", "iOS", "glTF", "GitHub", "WebGPU", "GilRs"]
doc-valid-idents = ["sRGB", "NaN", "iOS", "glTF", "GitHub", "WebGL", "WebGPU", "GilRs"]

View file

@ -93,11 +93,10 @@ impl SpecializedRenderPipeline for LineGizmoPipeline {
TextureFormat::bevy_default()
};
let view_layout = if key.mesh_key.msaa_samples() == 1 {
self.mesh_pipeline.view_layout.clone()
} else {
self.mesh_pipeline.view_layout_multisampled.clone()
};
let view_layout = self
.mesh_pipeline
.get_view_layout(key.mesh_key.into())
.clone();
let layout = vec![view_layout, self.uniform_layout.clone()];

View file

@ -243,7 +243,7 @@ impl ViewNode for DeferredOpaquePass3dPbrLightingNode {
#[derive(Resource)]
pub struct DeferredLightingLayout {
bind_group_layout_0: BindGroupLayout,
mesh_pipeline: MeshPipeline,
bind_group_layout_1: BindGroupLayout,
}
@ -332,7 +332,7 @@ impl SpecializedRenderPipeline for DeferredLightingLayout {
RenderPipelineDescriptor {
label: Some("deferred_lighting_pipeline".into()),
layout: vec![
self.bind_group_layout_0.clone(),
self.mesh_pipeline.get_view_layout(key.into()).clone(),
self.bind_group_layout_1.clone(),
],
vertex: VertexState {
@ -395,7 +395,7 @@ impl FromWorld for DeferredLightingLayout {
}],
});
Self {
bind_group_layout_0: world.resource::<MeshPipeline>().view_layout.clone(),
mesh_pipeline: world.resource::<MeshPipeline>().clone(),
bind_group_layout_1: layout,
}
}

View file

@ -1,3 +1,7 @@
mod prepass_bindings;
pub use prepass_bindings::*;
use bevy_app::{Plugin, PreUpdate};
use bevy_asset::{load_internal_asset, AssetServer, Handle};
use bevy_core_pipeline::{
@ -9,7 +13,7 @@ use bevy_core_pipeline::{
prelude::Camera3d,
prepass::{
AlphaMask3dPrepass, DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass,
Opaque3dPrepass, ViewPrepassTextures, MOTION_VECTOR_PREPASS_FORMAT, NORMAL_PREPASS_FORMAT,
Opaque3dPrepass, MOTION_VECTOR_PREPASS_FORMAT, NORMAL_PREPASS_FORMAT,
},
};
use bevy_ecs::{
@ -32,21 +36,18 @@ use bevy_render::{
},
render_resource::{
BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor,
BindGroupLayoutEntry, BindingResource, BindingType, BufferBindingType, ColorTargetState,
ColorWrites, CompareFunction, DepthBiasState, DepthStencilState, DynamicUniformBuffer,
FragmentState, FrontFace, MultisampleState, PipelineCache, PolygonMode, PrimitiveState,
PushConstantRange, RenderPipelineDescriptor, Shader, ShaderRef, ShaderStages, ShaderType,
BindGroupLayoutEntry, BindingType, BufferBindingType, ColorTargetState, ColorWrites,
CompareFunction, DepthBiasState, DepthStencilState, DynamicUniformBuffer, FragmentState,
FrontFace, MultisampleState, PipelineCache, PolygonMode, PrimitiveState, PushConstantRange,
RenderPipelineDescriptor, Shader, ShaderRef, ShaderStages, ShaderType,
SpecializedMeshPipeline, SpecializedMeshPipelineError, SpecializedMeshPipelines,
StencilFaceState, StencilState, TextureAspect, TextureFormat, TextureSampleType,
TextureView, TextureViewDescriptor, TextureViewDimension, VertexState,
StencilFaceState, StencilState, VertexState,
},
renderer::{RenderDevice, RenderQueue},
texture::{BevyDefault, FallbackImageMsaa},
view::{ExtractedView, Msaa, ViewUniform, ViewUniformOffset, ViewUniforms, VisibleEntities},
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
};
use bevy_transform::prelude::GlobalTransform;
use bevy_utils::default;
use bevy_utils::tracing::error;
use crate::{
@ -634,131 +635,6 @@ where
}
}
pub fn get_bind_group_layout_entries(
bindings: [u32; 4],
multisampled: bool,
) -> [BindGroupLayoutEntry; 4] {
[
// Depth texture
BindGroupLayoutEntry {
binding: bindings[0],
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Texture {
multisampled,
sample_type: TextureSampleType::Depth,
view_dimension: TextureViewDimension::D2,
},
count: None,
},
// Normal texture
BindGroupLayoutEntry {
binding: bindings[1],
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Texture {
multisampled,
sample_type: TextureSampleType::Float { filterable: false },
view_dimension: TextureViewDimension::D2,
},
count: None,
},
// Motion Vectors texture
BindGroupLayoutEntry {
binding: bindings[2],
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Texture {
multisampled,
sample_type: TextureSampleType::Float { filterable: false },
view_dimension: TextureViewDimension::D2,
},
count: None,
},
// Deferred texture
BindGroupLayoutEntry {
binding: bindings[3],
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Texture {
multisampled: false,
sample_type: TextureSampleType::Uint,
view_dimension: TextureViewDimension::D2,
},
count: None,
},
]
}
// Needed so the texture views can live long enough.
pub struct PrepassBindingsSet([TextureView; 4]);
impl PrepassBindingsSet {
pub fn get_entries(&self, bindings: [u32; 4]) -> [BindGroupEntry; 4] {
[
BindGroupEntry {
binding: bindings[0],
resource: BindingResource::TextureView(&self.0[0]),
},
BindGroupEntry {
binding: bindings[1],
resource: BindingResource::TextureView(&self.0[1]),
},
BindGroupEntry {
binding: bindings[2],
resource: BindingResource::TextureView(&self.0[2]),
},
BindGroupEntry {
binding: bindings[3],
resource: BindingResource::TextureView(&self.0[3]),
},
]
}
}
pub fn get_bindings(
prepass_textures: Option<&ViewPrepassTextures>,
fallback_images: &mut FallbackImageMsaa,
msaa: &Msaa,
) -> PrepassBindingsSet {
let depth_desc = TextureViewDescriptor {
label: Some("prepass_depth"),
aspect: TextureAspect::DepthOnly,
..default()
};
let depth_view = match prepass_textures.and_then(|x| x.depth.as_ref()) {
Some(texture) => texture.texture.create_view(&depth_desc),
None => fallback_images
.image_for_samplecount(msaa.samples(), CORE_3D_DEPTH_FORMAT)
.texture
.create_view(&depth_desc),
};
let normal_motion_vectors_fallback = &fallback_images
.image_for_samplecount(msaa.samples(), TextureFormat::bevy_default())
.texture_view;
let normal_view = match prepass_textures.and_then(|x| x.normal.as_ref()) {
Some(texture) => &texture.default_view,
None => normal_motion_vectors_fallback,
}
.clone();
let motion_vectors_view = match prepass_textures.and_then(|x| x.motion_vectors.as_ref()) {
Some(texture) => &texture.default_view,
None => normal_motion_vectors_fallback,
}
.clone();
let deferred_fallback = &fallback_images
.image_for_samplecount(1, TextureFormat::Rgba32Uint)
.texture_view;
let deferred_view = match prepass_textures.and_then(|x| x.deferred.as_ref()) {
Some(texture) => &texture.default_view,
None => deferred_fallback,
}
.clone();
PrepassBindingsSet([depth_view, normal_view, motion_vectors_view, deferred_view])
}
// Extract the render phases for the prepass
pub fn extract_camera_previous_view_projection(
mut commands: Commands,

View file

@ -0,0 +1,158 @@
use bevy_core_pipeline::prepass::ViewPrepassTextures;
use bevy_render::render_resource::{
BindGroupEntry, BindGroupLayoutEntry, BindingResource, BindingType, ShaderStages,
TextureAspect, TextureSampleType, TextureView, TextureViewDescriptor, TextureViewDimension,
};
use bevy_utils::default;
use smallvec::SmallVec;
use crate::MeshPipelineViewLayoutKey;
pub fn get_bind_group_layout_entries(
bindings: [u32; 4],
layout_key: MeshPipelineViewLayoutKey,
) -> SmallVec<[BindGroupLayoutEntry; 4]> {
let mut result = SmallVec::<[BindGroupLayoutEntry; 4]>::new();
let multisampled = layout_key.contains(MeshPipelineViewLayoutKey::MULTISAMPLED);
if layout_key.contains(MeshPipelineViewLayoutKey::DEPTH_PREPASS) {
result.push(
// Depth texture
BindGroupLayoutEntry {
binding: bindings[0],
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Texture {
multisampled,
sample_type: TextureSampleType::Depth,
view_dimension: TextureViewDimension::D2,
},
count: None,
},
);
}
if layout_key.contains(MeshPipelineViewLayoutKey::NORMAL_PREPASS) {
result.push(
// Normal texture
BindGroupLayoutEntry {
binding: bindings[1],
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Texture {
multisampled,
sample_type: TextureSampleType::Float { filterable: false },
view_dimension: TextureViewDimension::D2,
},
count: None,
},
);
}
if layout_key.contains(MeshPipelineViewLayoutKey::MOTION_VECTOR_PREPASS) {
result.push(
// Motion Vectors texture
BindGroupLayoutEntry {
binding: bindings[2],
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Texture {
multisampled,
sample_type: TextureSampleType::Float { filterable: false },
view_dimension: TextureViewDimension::D2,
},
count: None,
},
);
}
if layout_key.contains(MeshPipelineViewLayoutKey::DEFERRED_PREPASS) {
result.push(
// Deferred texture
BindGroupLayoutEntry {
binding: bindings[3],
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Texture {
multisampled: false,
sample_type: TextureSampleType::Uint,
view_dimension: TextureViewDimension::D2,
},
count: None,
},
);
}
result
}
// Needed so the texture views can live long enough.
pub struct PrepassBindingsSet {
depth_view: Option<TextureView>,
normal_view: Option<TextureView>,
motion_vectors_view: Option<TextureView>,
deferred_view: Option<TextureView>,
}
impl PrepassBindingsSet {
pub fn get_entries(&self, bindings: [u32; 4]) -> SmallVec<[BindGroupEntry; 4]> {
let mut result = SmallVec::<[BindGroupEntry; 4]>::new();
if let Some(ref depth_view) = self.depth_view {
result.push(BindGroupEntry {
binding: bindings[0],
resource: BindingResource::TextureView(depth_view),
});
}
if let Some(ref normal_view) = self.normal_view {
result.push(BindGroupEntry {
binding: bindings[1],
resource: BindingResource::TextureView(normal_view),
});
}
if let Some(ref motion_vectors_view) = self.motion_vectors_view {
result.push(BindGroupEntry {
binding: bindings[2],
resource: BindingResource::TextureView(motion_vectors_view),
});
}
if let Some(ref deferred_view) = self.deferred_view {
result.push(BindGroupEntry {
binding: bindings[3],
resource: BindingResource::TextureView(deferred_view),
});
}
result
}
}
pub fn get_bindings(prepass_textures: Option<&ViewPrepassTextures>) -> PrepassBindingsSet {
let depth_desc = TextureViewDescriptor {
label: Some("prepass_depth"),
aspect: TextureAspect::DepthOnly,
..default()
};
let depth_view = prepass_textures
.and_then(|x| x.depth.as_ref())
.map(|texture| texture.texture.create_view(&depth_desc));
let normal_view = prepass_textures
.and_then(|x| x.normal.as_ref())
.map(|texture| texture.default_view.clone());
let motion_vectors_view = prepass_textures
.and_then(|x| x.motion_vectors.as_ref())
.map(|texture| texture.default_view.clone());
let deferred_view = prepass_textures
.and_then(|x| x.deferred.as_ref())
.map(|texture| texture.default_view.clone());
PrepassBindingsSet {
depth_view,
normal_view,
motion_vectors_view,
deferred_view,
}
}

View file

@ -1,19 +1,15 @@
use crate::{
environment_map, prepass, EnvironmentMapLight, FogMeta, GlobalLightMeta, GpuFog, GpuLights,
GpuPointLights, LightMeta, MaterialBindGroupId, NotShadowCaster, NotShadowReceiver,
PreviousGlobalTransform, ScreenSpaceAmbientOcclusionTextures, Shadow, ShadowSamplers,
ViewClusterBindings, ViewFogUniformOffset, ViewLightsUniformOffset, ViewShadowBindings,
CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT, MAX_CASCADES_PER_LIGHT, MAX_DIRECTIONAL_LIGHTS,
generate_view_layouts, prepare_mesh_view_bind_groups, MaterialBindGroupId,
MeshPipelineViewLayout, MeshPipelineViewLayoutKey, MeshViewBindGroup, NotShadowCaster,
NotShadowReceiver, PreviousGlobalTransform, Shadow, ViewFogUniformOffset,
ViewLightsUniformOffset, CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT, MAX_CASCADES_PER_LIGHT,
MAX_DIRECTIONAL_LIGHTS,
};
use bevy_app::{Plugin, PostUpdate};
use bevy_asset::{load_internal_asset, AssetId, Handle};
use bevy_core_pipeline::{
core_3d::{AlphaMask3d, Opaque3d, Transparent3d, CORE_3D_DEPTH_FORMAT},
deferred::{AlphaMask3dDeferred, Opaque3dDeferred},
prepass::ViewPrepassTextures,
tonemapping::{
get_lut_bind_group_layout_entries, get_lut_bindings, Tonemapping, TonemappingLuts,
},
};
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{
@ -27,21 +23,18 @@ use bevy_render::{
batch_and_prepare_render_phase, write_batched_instance_buffer, GetBatchData,
NoAutomaticBatching,
},
globals::{GlobalsBuffer, GlobalsUniform},
mesh::{
GpuBufferInfo, InnerMeshVertexBufferLayout, Mesh, MeshVertexBufferLayout,
VertexAttributeDescriptor,
},
prelude::Msaa,
render_asset::RenderAssets,
render_phase::{PhaseItem, RenderCommand, RenderCommandResult, TrackedRenderPass},
render_resource::*,
renderer::{RenderDevice, RenderQueue},
texture::{
BevyDefault, DefaultImageSampler, FallbackImageCubemap, FallbackImageMsaa, GpuImage, Image,
ImageSampler, TextureFormatPixelInfo,
BevyDefault, DefaultImageSampler, GpuImage, Image, ImageSampler, TextureFormatPixelInfo,
},
view::{ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms, ViewVisibility},
view::{ViewTarget, ViewUniformOffset, ViewVisibility},
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
};
use bevy_transform::components::GlobalTransform;
@ -49,6 +42,15 @@ use bevy_utils::{tracing::error, EntityHashMap, HashMap, Hashed};
use std::cell::Cell;
use thread_local::ThreadLocal;
#[cfg(debug_assertions)]
use bevy_utils::tracing::warn;
#[cfg(debug_assertions)]
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc,
};
use crate::render::{
morph::{
extract_morphs, no_automatic_morph_batching, prepare_morphs, MorphIndices, MorphUniform,
@ -72,6 +74,17 @@ pub const MESH_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(3252377289
pub const SKINNING_HANDLE: Handle<Shader> = Handle::weak_from_u128(13215291596265391738);
pub const MORPH_HANDLE: Handle<Shader> = Handle::weak_from_u128(970982813587607345);
/// How many textures are allowed in the view bind group layout (`@group(0)`) before
/// broader compatibility with WebGL and WebGPU is at risk, due to the minimum guaranteed
/// values for `MAX_TEXTURE_IMAGE_UNITS` (in WebGL) and `maxSampledTexturesPerShaderStage` (in WebGPU),
/// currently both at 16.
///
/// We use 10 here because it still leaves us, in a worst case scenario, with 6 textures for the other bind groups.
///
/// See: <https://gpuweb.github.io/gpuweb/#limits>
#[cfg(debug_assertions)]
pub const MESH_PIPELINE_VIEW_LAYOUT_SAFE_MAX_TEXTURES: usize = 10;
impl Plugin for MeshRenderPlugin {
fn build(&self, app: &mut bevy_app::App) {
load_internal_asset!(app, FORWARD_IO_HANDLE, "forward_io.wgsl", Shader::from_wgsl);
@ -319,8 +332,7 @@ pub fn extract_meshes(
#[derive(Resource, Clone)]
pub struct MeshPipeline {
pub view_layout: BindGroupLayout,
pub view_layout_multisampled: BindGroupLayout,
view_layouts: [MeshPipelineViewLayout; MeshPipelineViewLayoutKey::COUNT],
// This dummy white texture is to be used in place of optional StandardMaterial textures
pub dummy_white_gpu_image: GpuImage,
pub clustered_forward_buffer_binding_type: BufferBindingType,
@ -337,6 +349,9 @@ pub struct MeshPipeline {
/// ##endif // PER_OBJECT_BUFFER_BATCH_SIZE
/// ```
pub per_object_buffer_batch_size: Option<u32>,
#[cfg(debug_assertions)]
pub did_warn_about_too_many_textures: Arc<AtomicBool>,
}
impl FromWorld for MeshPipeline {
@ -350,185 +365,8 @@ impl FromWorld for MeshPipeline {
let clustered_forward_buffer_binding_type = render_device
.get_supported_read_only_binding_type(CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT);
/// Returns the appropriate bind group layout vec based on the parameters
fn layout_entries(
clustered_forward_buffer_binding_type: BufferBindingType,
multisampled: bool,
) -> Vec<BindGroupLayoutEntry> {
let mut entries = vec![
// View
BindGroupLayoutEntry {
binding: 0,
visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT,
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: true,
min_binding_size: Some(ViewUniform::min_size()),
},
count: None,
},
// Lights
BindGroupLayoutEntry {
binding: 1,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: true,
min_binding_size: Some(GpuLights::min_size()),
},
count: None,
},
// Point Shadow Texture Cube Array
BindGroupLayoutEntry {
binding: 2,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Texture {
multisampled: false,
sample_type: TextureSampleType::Depth,
#[cfg(any(not(feature = "webgl"), not(target_arch = "wasm32")))]
view_dimension: TextureViewDimension::CubeArray,
#[cfg(all(feature = "webgl", target_arch = "wasm32"))]
view_dimension: TextureViewDimension::Cube,
},
count: None,
},
// Point Shadow Texture Array Sampler
BindGroupLayoutEntry {
binding: 3,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Sampler(SamplerBindingType::Comparison),
count: None,
},
// Directional Shadow Texture Array
BindGroupLayoutEntry {
binding: 4,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Texture {
multisampled: false,
sample_type: TextureSampleType::Depth,
#[cfg(any(not(feature = "webgl"), not(target_arch = "wasm32")))]
view_dimension: TextureViewDimension::D2Array,
#[cfg(all(feature = "webgl", target_arch = "wasm32"))]
view_dimension: TextureViewDimension::D2,
},
count: None,
},
// Directional Shadow Texture Array Sampler
BindGroupLayoutEntry {
binding: 5,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Sampler(SamplerBindingType::Comparison),
count: None,
},
// PointLights
BindGroupLayoutEntry {
binding: 6,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Buffer {
ty: clustered_forward_buffer_binding_type,
has_dynamic_offset: false,
min_binding_size: Some(GpuPointLights::min_size(
clustered_forward_buffer_binding_type,
)),
},
count: None,
},
// ClusteredLightIndexLists
BindGroupLayoutEntry {
binding: 7,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Buffer {
ty: clustered_forward_buffer_binding_type,
has_dynamic_offset: false,
min_binding_size: Some(
ViewClusterBindings::min_size_cluster_light_index_lists(
clustered_forward_buffer_binding_type,
),
),
},
count: None,
},
// ClusterOffsetsAndCounts
BindGroupLayoutEntry {
binding: 8,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Buffer {
ty: clustered_forward_buffer_binding_type,
has_dynamic_offset: false,
min_binding_size: Some(
ViewClusterBindings::min_size_cluster_offsets_and_counts(
clustered_forward_buffer_binding_type,
),
),
},
count: None,
},
// Globals
BindGroupLayoutEntry {
binding: 9,
visibility: ShaderStages::VERTEX_FRAGMENT,
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: Some(GlobalsUniform::min_size()),
},
count: None,
},
// Fog
BindGroupLayoutEntry {
binding: 10,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: true,
min_binding_size: Some(GpuFog::min_size()),
},
count: None,
},
// Screen space ambient occlusion texture
BindGroupLayoutEntry {
binding: 11,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Texture {
multisampled: false,
sample_type: TextureSampleType::Float { filterable: false },
view_dimension: TextureViewDimension::D2,
},
count: None,
},
];
// EnvironmentMapLight
let environment_map_entries =
environment_map::get_bind_group_layout_entries([12, 13, 14]);
entries.extend_from_slice(&environment_map_entries);
// Tonemapping
let tonemapping_lut_entries = get_lut_bind_group_layout_entries([15, 16]);
entries.extend_from_slice(&tonemapping_lut_entries);
if cfg!(any(not(feature = "webgl"), not(target_arch = "wasm32")))
|| (cfg!(all(feature = "webgl", target_arch = "wasm32")) && !multisampled)
{
entries.extend_from_slice(&prepass::get_bind_group_layout_entries(
[17, 18, 19, 20],
multisampled,
));
}
entries
}
let view_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
label: Some("mesh_view_layout"),
entries: &layout_entries(clustered_forward_buffer_binding_type, false),
});
let view_layout_multisampled =
render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
label: Some("mesh_view_layout_multisampled"),
entries: &layout_entries(clustered_forward_buffer_binding_type, true),
});
let view_layouts =
generate_view_layouts(&render_device, clustered_forward_buffer_binding_type);
// A 1x1x1 'all 1.0' texture to use as a dummy texture to use in place of optional StandardMaterial textures
let dummy_white_gpu_image = {
@ -571,12 +409,13 @@ impl FromWorld for MeshPipeline {
};
MeshPipeline {
view_layout,
view_layout_multisampled,
view_layouts,
clustered_forward_buffer_binding_type,
dummy_white_gpu_image,
mesh_layouts: MeshLayouts::new(&render_device),
per_object_buffer_batch_size: GpuArrayBuffer::<MeshUniform>::batch_size(&render_device),
#[cfg(debug_assertions)]
did_warn_about_too_many_textures: Arc::new(AtomicBool::new(false)),
}
}
}
@ -597,6 +436,24 @@ impl MeshPipeline {
))
}
}
pub fn get_view_layout(&self, layout_key: MeshPipelineViewLayoutKey) -> &BindGroupLayout {
let index = layout_key.bits() as usize;
let layout = &self.view_layouts[index];
#[cfg(debug_assertions)]
if layout.texture_count > MESH_PIPELINE_VIEW_LAYOUT_SAFE_MAX_TEXTURES
&& !self.did_warn_about_too_many_textures.load(Ordering::SeqCst)
{
self.did_warn_about_too_many_textures
.store(true, Ordering::SeqCst);
// Issue our own warning here because Naga's error message is a bit cryptic in this situation
warn!("Too many textures in mesh pipeline view layout, this might cause us to hit `wgpu::Limits::max_sampled_textures_per_shader_stage` in some environments.");
}
&layout.bind_group_layout
}
}
impl GetBatchData for MeshPipeline {
@ -807,12 +664,10 @@ impl SpecializedMeshPipeline for MeshPipeline {
vertex_attributes.push(Mesh::ATTRIBUTE_COLOR.at_shader_location(5));
}
let mut bind_group_layout = match key.msaa_samples() {
1 => vec![self.view_layout.clone()],
_ => {
shader_defs.push("MULTISAMPLED".into());
vec![self.view_layout_multisampled.clone()]
}
let mut bind_group_layout = vec![self.get_view_layout(key.into()).clone()];
if key.msaa_samples() > 1 {
shader_defs.push("MULTISAMPLED".into());
};
bind_group_layout.push(setup_morph_and_skinning_defs(
@ -1088,173 +943,6 @@ pub fn prepare_mesh_bind_group(
}
}
#[derive(Component)]
pub struct MeshViewBindGroup {
pub value: BindGroup,
}
#[allow(clippy::too_many_arguments)]
pub fn prepare_mesh_view_bind_groups(
mut commands: Commands,
render_device: Res<RenderDevice>,
mesh_pipeline: Res<MeshPipeline>,
shadow_samplers: Res<ShadowSamplers>,
light_meta: Res<LightMeta>,
global_light_meta: Res<GlobalLightMeta>,
fog_meta: Res<FogMeta>,
view_uniforms: Res<ViewUniforms>,
views: Query<(
Entity,
&ViewShadowBindings,
&ViewClusterBindings,
Option<&ScreenSpaceAmbientOcclusionTextures>,
Option<&ViewPrepassTextures>,
Option<&EnvironmentMapLight>,
&Tonemapping,
)>,
(images, mut fallback_images, fallback_cubemap): (
Res<RenderAssets<Image>>,
FallbackImageMsaa,
Res<FallbackImageCubemap>,
),
msaa: Res<Msaa>,
globals_buffer: Res<GlobalsBuffer>,
tonemapping_luts: Res<TonemappingLuts>,
) {
if let (
Some(view_binding),
Some(light_binding),
Some(point_light_binding),
Some(globals),
Some(fog_binding),
) = (
view_uniforms.uniforms.binding(),
light_meta.view_gpu_lights.binding(),
global_light_meta.gpu_point_lights.binding(),
globals_buffer.buffer.binding(),
fog_meta.gpu_fogs.binding(),
) {
for (
entity,
view_shadow_bindings,
view_cluster_bindings,
ssao_textures,
prepass_textures,
environment_map,
tonemapping,
) in &views
{
let fallback_ssao = fallback_images
.image_for_samplecount(1, TextureFormat::bevy_default())
.texture_view
.clone();
let layout = if msaa.samples() > 1 {
&mesh_pipeline.view_layout_multisampled
} else {
&mesh_pipeline.view_layout
};
let mut entries = vec![
BindGroupEntry {
binding: 0,
resource: view_binding.clone(),
},
BindGroupEntry {
binding: 1,
resource: light_binding.clone(),
},
BindGroupEntry {
binding: 2,
resource: BindingResource::TextureView(
&view_shadow_bindings.point_light_depth_texture_view,
),
},
BindGroupEntry {
binding: 3,
resource: BindingResource::Sampler(&shadow_samplers.point_light_sampler),
},
BindGroupEntry {
binding: 4,
resource: BindingResource::TextureView(
&view_shadow_bindings.directional_light_depth_texture_view,
),
},
BindGroupEntry {
binding: 5,
resource: BindingResource::Sampler(&shadow_samplers.directional_light_sampler),
},
BindGroupEntry {
binding: 6,
resource: point_light_binding.clone(),
},
BindGroupEntry {
binding: 7,
resource: view_cluster_bindings.light_index_lists_binding().unwrap(),
},
BindGroupEntry {
binding: 8,
resource: view_cluster_bindings.offsets_and_counts_binding().unwrap(),
},
BindGroupEntry {
binding: 9,
resource: globals.clone(),
},
BindGroupEntry {
binding: 10,
resource: fog_binding.clone(),
},
BindGroupEntry {
binding: 11,
resource: BindingResource::TextureView(
ssao_textures
.map(|t| &t.screen_space_ambient_occlusion_texture.default_view)
.unwrap_or(&fallback_ssao),
),
},
];
let env_map = environment_map::get_bindings(
environment_map,
&images,
&fallback_cubemap,
[12, 13, 14],
);
entries.extend_from_slice(&env_map);
let tonemapping_luts =
get_lut_bindings(&images, &tonemapping_luts, tonemapping, [15, 16]);
entries.extend_from_slice(&tonemapping_luts);
let label = Some("mesh_view_bind_group");
// When using WebGL, we can't have a depth texture with multisampling
if cfg!(any(not(feature = "webgl"), not(target_arch = "wasm32")))
|| (cfg!(all(feature = "webgl", target_arch = "wasm32")) && msaa.samples() == 1)
{
let prepass_bindings =
prepass::get_bindings(prepass_textures, &mut fallback_images, &msaa);
entries.extend_from_slice(&prepass_bindings.get_entries([17, 18, 19, 20]));
commands.entity(entity).insert(MeshViewBindGroup {
value: render_device.create_bind_group(&BindGroupDescriptor {
entries: &entries,
label,
layout,
}),
});
} else {
commands.entity(entity).insert(MeshViewBindGroup {
value: render_device.create_bind_group(&BindGroupDescriptor {
entries: &entries,
label,
layout,
}),
});
}
}
}
}
pub struct SetMeshViewBindGroup<const I: usize>;
impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetMeshViewBindGroup<I> {
type Param = ();

View file

@ -0,0 +1,503 @@
use std::array;
use bevy_core_pipeline::{
prepass::ViewPrepassTextures,
tonemapping::{
get_lut_bind_group_layout_entries, get_lut_bindings, Tonemapping, TonemappingLuts,
},
};
use bevy_ecs::{
component::Component,
entity::Entity,
system::{Commands, Query, Res},
};
use bevy_render::{
globals::{GlobalsBuffer, GlobalsUniform},
render_asset::RenderAssets,
render_resource::{
BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor,
BindGroupLayoutEntry, BindingResource, BindingType, BufferBindingType, SamplerBindingType,
ShaderStages, ShaderType, TextureFormat, TextureSampleType, TextureViewDimension,
},
renderer::RenderDevice,
texture::{BevyDefault, FallbackImageCubemap, FallbackImageMsaa, Image},
view::{Msaa, ViewUniform, ViewUniforms},
};
use crate::{
environment_map, prepass, EnvironmentMapLight, FogMeta, GlobalLightMeta, GpuFog, GpuLights,
GpuPointLights, LightMeta, MeshPipeline, MeshPipelineKey, ScreenSpaceAmbientOcclusionTextures,
ShadowSamplers, ViewClusterBindings, ViewShadowBindings,
};
#[derive(Clone)]
pub struct MeshPipelineViewLayout {
pub bind_group_layout: BindGroupLayout,
#[cfg(debug_assertions)]
pub texture_count: usize,
}
bitflags::bitflags! {
/// A key that uniquely identifies a [`MeshPipelineViewLayout`].
///
/// Used to generate all possible layouts for the mesh pipeline in [`generate_view_layouts`],
/// so special care must be taken to not add too many flags, as the number of possible layouts
/// will grow exponentially.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct MeshPipelineViewLayoutKey: u32 {
const MULTISAMPLED = (1 << 0);
const DEPTH_PREPASS = (1 << 1);
const NORMAL_PREPASS = (1 << 2);
const MOTION_VECTOR_PREPASS = (1 << 3);
const DEFERRED_PREPASS = (1 << 4);
}
}
impl MeshPipelineViewLayoutKey {
// The number of possible layouts
pub const COUNT: usize = Self::all().bits() as usize + 1;
/// Builds a unique label for each layout based on the flags
pub fn label(&self) -> String {
use MeshPipelineViewLayoutKey as Key;
format!(
"mesh_view_layout{}{}{}{}{}",
self.contains(Key::MULTISAMPLED)
.then_some("_multisampled")
.unwrap_or_default(),
self.contains(Key::DEPTH_PREPASS)
.then_some("_depth")
.unwrap_or_default(),
self.contains(Key::NORMAL_PREPASS)
.then_some("_normal")
.unwrap_or_default(),
self.contains(Key::MOTION_VECTOR_PREPASS)
.then_some("_motion")
.unwrap_or_default(),
self.contains(Key::DEFERRED_PREPASS)
.then_some("_deferred")
.unwrap_or_default(),
)
}
}
impl From<MeshPipelineKey> for MeshPipelineViewLayoutKey {
fn from(value: MeshPipelineKey) -> Self {
let mut result = MeshPipelineViewLayoutKey::empty();
if value.msaa_samples() > 1 {
result |= MeshPipelineViewLayoutKey::MULTISAMPLED;
}
if value.contains(MeshPipelineKey::DEPTH_PREPASS) {
result |= MeshPipelineViewLayoutKey::DEPTH_PREPASS;
}
if value.contains(MeshPipelineKey::NORMAL_PREPASS) {
result |= MeshPipelineViewLayoutKey::NORMAL_PREPASS;
}
if value.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) {
result |= MeshPipelineViewLayoutKey::MOTION_VECTOR_PREPASS;
}
if value.contains(MeshPipelineKey::DEFERRED_PREPASS) {
result |= MeshPipelineViewLayoutKey::DEFERRED_PREPASS;
}
result
}
}
impl From<Msaa> for MeshPipelineViewLayoutKey {
fn from(value: Msaa) -> Self {
let mut result = MeshPipelineViewLayoutKey::empty();
if value.samples() > 1 {
result |= MeshPipelineViewLayoutKey::MULTISAMPLED;
}
result
}
}
impl From<Option<&ViewPrepassTextures>> for MeshPipelineViewLayoutKey {
fn from(value: Option<&ViewPrepassTextures>) -> Self {
let mut result = MeshPipelineViewLayoutKey::empty();
if let Some(prepass_textures) = value {
if prepass_textures.depth.is_some() {
result |= MeshPipelineViewLayoutKey::DEPTH_PREPASS;
}
if prepass_textures.normal.is_some() {
result |= MeshPipelineViewLayoutKey::NORMAL_PREPASS;
}
if prepass_textures.motion_vectors.is_some() {
result |= MeshPipelineViewLayoutKey::MOTION_VECTOR_PREPASS;
}
if prepass_textures.deferred.is_some() {
result |= MeshPipelineViewLayoutKey::DEFERRED_PREPASS;
}
}
result
}
}
/// Returns the appropriate bind group layout vec based on the parameters
fn layout_entries(
clustered_forward_buffer_binding_type: BufferBindingType,
layout_key: MeshPipelineViewLayoutKey,
) -> Vec<BindGroupLayoutEntry> {
let mut entries = vec![
// View
BindGroupLayoutEntry {
binding: 0,
visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT,
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: true,
min_binding_size: Some(ViewUniform::min_size()),
},
count: None,
},
// Lights
BindGroupLayoutEntry {
binding: 1,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: true,
min_binding_size: Some(GpuLights::min_size()),
},
count: None,
},
// Point Shadow Texture Cube Array
BindGroupLayoutEntry {
binding: 2,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Texture {
multisampled: false,
sample_type: TextureSampleType::Depth,
#[cfg(any(not(feature = "webgl"), not(target_arch = "wasm32")))]
view_dimension: TextureViewDimension::CubeArray,
#[cfg(all(feature = "webgl", target_arch = "wasm32"))]
view_dimension: TextureViewDimension::Cube,
},
count: None,
},
// Point Shadow Texture Array Sampler
BindGroupLayoutEntry {
binding: 3,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Sampler(SamplerBindingType::Comparison),
count: None,
},
// Directional Shadow Texture Array
BindGroupLayoutEntry {
binding: 4,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Texture {
multisampled: false,
sample_type: TextureSampleType::Depth,
#[cfg(any(not(feature = "webgl"), not(target_arch = "wasm32")))]
view_dimension: TextureViewDimension::D2Array,
#[cfg(all(feature = "webgl", target_arch = "wasm32"))]
view_dimension: TextureViewDimension::D2,
},
count: None,
},
// Directional Shadow Texture Array Sampler
BindGroupLayoutEntry {
binding: 5,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Sampler(SamplerBindingType::Comparison),
count: None,
},
// PointLights
BindGroupLayoutEntry {
binding: 6,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Buffer {
ty: clustered_forward_buffer_binding_type,
has_dynamic_offset: false,
min_binding_size: Some(GpuPointLights::min_size(
clustered_forward_buffer_binding_type,
)),
},
count: None,
},
// ClusteredLightIndexLists
BindGroupLayoutEntry {
binding: 7,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Buffer {
ty: clustered_forward_buffer_binding_type,
has_dynamic_offset: false,
min_binding_size: Some(ViewClusterBindings::min_size_cluster_light_index_lists(
clustered_forward_buffer_binding_type,
)),
},
count: None,
},
// ClusterOffsetsAndCounts
BindGroupLayoutEntry {
binding: 8,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Buffer {
ty: clustered_forward_buffer_binding_type,
has_dynamic_offset: false,
min_binding_size: Some(ViewClusterBindings::min_size_cluster_offsets_and_counts(
clustered_forward_buffer_binding_type,
)),
},
count: None,
},
// Globals
BindGroupLayoutEntry {
binding: 9,
visibility: ShaderStages::VERTEX_FRAGMENT,
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: Some(GlobalsUniform::min_size()),
},
count: None,
},
// Fog
BindGroupLayoutEntry {
binding: 10,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: true,
min_binding_size: Some(GpuFog::min_size()),
},
count: None,
},
// Screen space ambient occlusion texture
BindGroupLayoutEntry {
binding: 11,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Texture {
multisampled: false,
sample_type: TextureSampleType::Float { filterable: false },
view_dimension: TextureViewDimension::D2,
},
count: None,
},
];
// EnvironmentMapLight
let environment_map_entries = environment_map::get_bind_group_layout_entries([12, 13, 14]);
entries.extend_from_slice(&environment_map_entries);
// Tonemapping
let tonemapping_lut_entries = get_lut_bind_group_layout_entries([15, 16]);
entries.extend_from_slice(&tonemapping_lut_entries);
if cfg!(any(not(feature = "webgl"), not(target_arch = "wasm32")))
|| (cfg!(all(feature = "webgl", target_arch = "wasm32"))
&& !layout_key.contains(MeshPipelineViewLayoutKey::MULTISAMPLED))
{
entries.extend_from_slice(&prepass::get_bind_group_layout_entries(
[17, 18, 19, 20],
layout_key,
));
}
entries
}
/// Generates all possible view layouts for the mesh pipeline, based on all combinations of
/// [`MeshPipelineViewLayoutKey`] flags.
pub fn generate_view_layouts(
render_device: &RenderDevice,
clustered_forward_buffer_binding_type: BufferBindingType,
) -> [MeshPipelineViewLayout; MeshPipelineViewLayoutKey::COUNT] {
array::from_fn(|i| {
let key = MeshPipelineViewLayoutKey::from_bits_truncate(i as u32);
let entries = layout_entries(clustered_forward_buffer_binding_type, key);
#[cfg(debug_assertions)]
let texture_count: usize = entries
.iter()
.filter(|entry| matches!(entry.ty, BindingType::Texture { .. }))
.count();
MeshPipelineViewLayout {
bind_group_layout: render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
label: Some(key.label().as_str()),
entries: &entries,
}),
#[cfg(debug_assertions)]
texture_count,
}
})
}
#[derive(Component)]
pub struct MeshViewBindGroup {
pub value: BindGroup,
}
#[allow(clippy::too_many_arguments)]
pub fn prepare_mesh_view_bind_groups(
mut commands: Commands,
render_device: Res<RenderDevice>,
mesh_pipeline: Res<MeshPipeline>,
shadow_samplers: Res<ShadowSamplers>,
light_meta: Res<LightMeta>,
global_light_meta: Res<GlobalLightMeta>,
fog_meta: Res<FogMeta>,
view_uniforms: Res<ViewUniforms>,
views: Query<(
Entity,
&ViewShadowBindings,
&ViewClusterBindings,
Option<&ScreenSpaceAmbientOcclusionTextures>,
Option<&ViewPrepassTextures>,
Option<&EnvironmentMapLight>,
&Tonemapping,
)>,
(images, mut fallback_images, fallback_cubemap): (
Res<RenderAssets<Image>>,
FallbackImageMsaa,
Res<FallbackImageCubemap>,
),
msaa: Res<Msaa>,
globals_buffer: Res<GlobalsBuffer>,
tonemapping_luts: Res<TonemappingLuts>,
) {
if let (
Some(view_binding),
Some(light_binding),
Some(point_light_binding),
Some(globals),
Some(fog_binding),
) = (
view_uniforms.uniforms.binding(),
light_meta.view_gpu_lights.binding(),
global_light_meta.gpu_point_lights.binding(),
globals_buffer.buffer.binding(),
fog_meta.gpu_fogs.binding(),
) {
for (
entity,
view_shadow_bindings,
view_cluster_bindings,
ssao_textures,
prepass_textures,
environment_map,
tonemapping,
) in &views
{
let fallback_ssao = fallback_images
.image_for_samplecount(1, TextureFormat::bevy_default())
.texture_view
.clone();
let layout = &mesh_pipeline.get_view_layout(
MeshPipelineViewLayoutKey::from(*msaa)
| MeshPipelineViewLayoutKey::from(prepass_textures),
);
let mut entries = vec![
BindGroupEntry {
binding: 0,
resource: view_binding.clone(),
},
BindGroupEntry {
binding: 1,
resource: light_binding.clone(),
},
BindGroupEntry {
binding: 2,
resource: BindingResource::TextureView(
&view_shadow_bindings.point_light_depth_texture_view,
),
},
BindGroupEntry {
binding: 3,
resource: BindingResource::Sampler(&shadow_samplers.point_light_sampler),
},
BindGroupEntry {
binding: 4,
resource: BindingResource::TextureView(
&view_shadow_bindings.directional_light_depth_texture_view,
),
},
BindGroupEntry {
binding: 5,
resource: BindingResource::Sampler(&shadow_samplers.directional_light_sampler),
},
BindGroupEntry {
binding: 6,
resource: point_light_binding.clone(),
},
BindGroupEntry {
binding: 7,
resource: view_cluster_bindings.light_index_lists_binding().unwrap(),
},
BindGroupEntry {
binding: 8,
resource: view_cluster_bindings.offsets_and_counts_binding().unwrap(),
},
BindGroupEntry {
binding: 9,
resource: globals.clone(),
},
BindGroupEntry {
binding: 10,
resource: fog_binding.clone(),
},
BindGroupEntry {
binding: 11,
resource: BindingResource::TextureView(
ssao_textures
.map(|t| &t.screen_space_ambient_occlusion_texture.default_view)
.unwrap_or(&fallback_ssao),
),
},
];
let env_map = environment_map::get_bindings(
environment_map,
&images,
&fallback_cubemap,
[12, 13, 14],
);
entries.extend_from_slice(&env_map);
let tonemapping_luts =
get_lut_bindings(&images, &tonemapping_luts, tonemapping, [15, 16]);
entries.extend_from_slice(&tonemapping_luts);
let label = Some("mesh_view_bind_group");
// When using WebGL, we can't have a depth texture with multisampling
let prepass_bindings = if cfg!(any(not(feature = "webgl"), not(target_arch = "wasm32")))
|| (cfg!(all(feature = "webgl", target_arch = "wasm32")) && msaa.samples() == 1)
{
Some(prepass::get_bindings(prepass_textures))
} else {
None
};
// This if statement is here to make the borrow checker happy.
// Ideally we could just have `entries.extend_from_slice(&prepass_bindings.get_entries([17, 18, 19, 20]));`
// in the existing if statement above, but that either doesn't allow `prepass_bindings` to live long enough,
// as its used when creating the bind group at the end of the function, or causes a `cannot move out of` error.
if let Some(prepass_bindings) = &prepass_bindings {
entries.extend_from_slice(&prepass_bindings.get_entries([17, 18, 19, 20]));
}
commands.entity(entity).insert(MeshViewBindGroup {
value: render_device.create_bind_group(&BindGroupDescriptor {
entries: &entries,
label,
layout,
}),
});
}
}
}

View file

@ -42,12 +42,31 @@
@group(0) @binding(16) var dt_lut_sampler: sampler;
#ifdef MULTISAMPLED
#ifdef DEPTH_PREPASS
@group(0) @binding(17) var depth_prepass_texture: texture_depth_multisampled_2d;
#endif // DEPTH_PREPASS
#ifdef NORMAL_PREPASS
@group(0) @binding(18) var normal_prepass_texture: texture_multisampled_2d<f32>;
#endif // NORMAL_PREPASS
#ifdef MOTION_VECTOR_PREPASS
@group(0) @binding(19) var motion_vector_prepass_texture: texture_multisampled_2d<f32>;
#else
#endif // MOTION_VECTOR_PREPASS
#else // MULTISAMPLED
#ifdef DEPTH_PREPASS
@group(0) @binding(17) var depth_prepass_texture: texture_depth_2d;
#endif // DEPTH_PREPASS
#ifdef NORMAL_PREPASS
@group(0) @binding(18) var normal_prepass_texture: texture_2d<f32>;
#endif // NORMAL_PREPASS
#ifdef MOTION_VECTOR_PREPASS
@group(0) @binding(19) var motion_vector_prepass_texture: texture_2d<f32>;
#endif // MOTION_VECTOR_PREPASS
#endif // MULTISAMPLED
#ifdef DEFERRED_PREPASS
@group(0) @binding(20) var deferred_prepass_texture: texture_2d<u32>;
#endif
#endif // DEFERRED_PREPASS

View file

@ -2,6 +2,7 @@ mod fog;
mod light;
pub(crate) mod mesh;
mod mesh_bindings;
mod mesh_view_bindings;
mod morph;
mod skin;
@ -9,4 +10,5 @@ pub use fog::*;
pub use light::*;
pub use mesh::*;
pub use mesh_bindings::MeshLayouts;
pub use mesh_view_bindings::*;
pub use skin::{extract_skins, prepare_skins, SkinIndex, SkinUniform, MAX_JOINTS};