Skip to content

Commit

Permalink
Variable MeshPipeline View Bind Group Layout (#10156)
Browse files Browse the repository at this point in the history
# 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.
  • Loading branch information
coreh committed Oct 21, 2023
1 parent 6f27e0e commit 9b80205
Show file tree
Hide file tree
Showing 9 changed files with 760 additions and 515 deletions.
2 changes: 1 addition & 1 deletion clippy.toml
Original file line number Diff line number Diff line change
@@ -1 +1 @@
doc-valid-idents = ["sRGB", "NaN", "iOS", "glTF", "GitHub", "WebGPU", "GilRs"]
doc-valid-idents = ["sRGB", "NaN", "iOS", "glTF", "GitHub", "WebGL", "WebGPU", "GilRs"]
9 changes: 4 additions & 5 deletions crates/bevy_gizmos/src/pipeline_3d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()];

Expand Down
6 changes: 3 additions & 3 deletions crates/bevy_pbr/src/deferred/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
}
}
Expand Down
144 changes: 10 additions & 134 deletions crates/bevy_pbr/src/prepass/mod.rs
Original file line number Diff line number Diff line change
@@ -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::{
Expand All @@ -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::{
Expand All @@ -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::{
Expand Down Expand Up @@ -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,
Expand Down
158 changes: 158 additions & 0 deletions crates/bevy_pbr/src/prepass/prepass_bindings.rs
Original file line number Diff line number Diff line change
@@ -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,
}
}
Loading

0 comments on commit 9b80205

Please sign in to comment.