Move non-generic parts of the PrepassPipeline to internal field (#18322)

# Objective

- The prepass pipeline has a generic bound on the specialize function
but 95% of it doesn't need it

## Solution

- Move most of the fields to an internal struct and use a separate
specialize function for those fields

## Testing

- Ran the 3d_scene and it worked like before

---

## Migration Guide

If you were using a field of the `PrepassPipeline`, most of them have
now been move to `PrepassPipeline::internal`.

## Notes

Here's the cargo bloat size comparison (from this tool
https://github.com/bevyengine/bevy/discussions/14864):

```
before:
    (
        "<bevy_pbr::prepass::PrepassPipeline<M> as bevy_render::render_resource::pipeline_specializer::SpecializedMeshPipeline>::specialize",
        25416,
        0.05582993,
    ),

after:
    (
        "<bevy_pbr::prepass::PrepassPipeline<M> as bevy_render::render_resource::pipeline_specializer::SpecializedMeshPipeline>::specialize",
        2496,
        0.005490916,
    ),
    (
        "bevy_pbr::prepass::PrepassPipelineInternal::specialize",
        11444,
        0.025175499,
    ),
```

The size for the specialize function that is generic is now much
smaller, so users won't need to recompile it for every material.
This commit is contained in:
IceSentry 2025-03-25 14:47:31 -04:00 committed by François Mockers
parent a021ed1ce8
commit af9fd4e939
2 changed files with 73 additions and 97 deletions

View File

@ -336,9 +336,12 @@ pub fn prepare_material_meshlet_meshes_prepass<M: Material>(
shader_defs.push("MESHLET_MESH_MATERIAL_PASS".into());
let view_layout = if view_key.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) {
prepass_pipeline.view_layout_motion_vectors.clone()
prepass_pipeline.internal.view_layout_motion_vectors.clone()
} else {
prepass_pipeline.view_layout_no_motion_vectors.clone()
prepass_pipeline
.internal
.view_layout_no_motion_vectors
.clone()
};
let fragment_shader = if view_key.contains(MeshPipelineKey::DEFERRED_PREPASS) {
@ -357,7 +360,7 @@ pub fn prepare_material_meshlet_meshes_prepass<M: Material>(
layout: vec![
view_layout,
resource_manager.material_shade_bind_group_layout.clone(),
prepass_pipeline.material_layout.clone(),
prepass_pipeline.internal.material_layout.clone(),
],
push_constant_ranges: vec![],
vertex: VertexState {

View File

@ -287,6 +287,13 @@ pub fn update_mesh_previous_global_transforms(
#[derive(Resource)]
pub struct PrepassPipeline<M: Material> {
pub internal: PrepassPipelineInternal,
pub material_pipeline: MaterialPipeline<M>,
}
/// Internal fields of the `PrepassPipeline` that don't need the generic bound
/// This is done as an optimization to not recompile the same code multiple time
pub struct PrepassPipelineInternal {
pub view_layout_motion_vectors: BindGroupLayout,
pub view_layout_no_motion_vectors: BindGroupLayout,
pub mesh_layouts: MeshLayouts,
@ -295,7 +302,6 @@ pub struct PrepassPipeline<M: Material> {
pub prepass_material_fragment_shader: Option<Handle<Shader>>,
pub deferred_material_vertex_shader: Option<Handle<Shader>>,
pub deferred_material_fragment_shader: Option<Handle<Shader>>,
pub material_pipeline: MaterialPipeline<M>,
/// Whether skins will use uniform buffers on account of storage buffers
/// being unavailable on this platform.
@ -306,8 +312,6 @@ pub struct PrepassPipeline<M: Material> {
/// Whether binding arrays (a.k.a. bindless textures) are usable on the
/// current render device.
pub binding_arrays_are_usable: bool,
_marker: PhantomData<M>,
}
impl<M: Material> FromWorld for PrepassPipeline<M> {
@ -372,8 +376,7 @@ impl<M: Material> FromWorld for PrepassPipeline<M> {
let depth_clip_control_supported = render_device
.features()
.contains(WgpuFeatures::DEPTH_CLIP_CONTROL);
PrepassPipeline {
let internal = PrepassPipelineInternal {
view_layout_motion_vectors,
view_layout_no_motion_vectors,
mesh_layouts: mesh_pipeline.mesh_layouts.clone(),
@ -398,11 +401,13 @@ impl<M: Material> FromWorld for PrepassPipeline<M> {
ShaderRef::Path(path) => Some(asset_server.load(path)),
},
material_layout: M::bind_group_layout(render_device),
material_pipeline: world.resource::<MaterialPipeline<M>>().clone(),
skins_use_uniform_buffers: skin::skins_use_uniform_buffers(render_device),
depth_clip_control_supported,
binding_arrays_are_usable: binding_arrays_are_usable(render_device, render_adapter),
_marker: PhantomData,
};
PrepassPipeline {
internal,
material_pipeline: world.resource::<MaterialPipeline<M>>().clone(),
}
}
}
@ -418,15 +423,38 @@ where
key: Self::Key,
layout: &MeshVertexBufferLayoutRef,
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
let mut bind_group_layouts = vec![if key
.mesh_key
let mut shader_defs = Vec::new();
if self.material_pipeline.bindless {
shader_defs.push("BINDLESS".into());
}
let mut descriptor = self
.internal
.specialize(key.mesh_key, shader_defs, layout)?;
// This is a bit risky because it's possible to change something that would
// break the prepass but be fine in the main pass.
// Since this api is pretty low-level it doesn't matter that much, but it is a potential issue.
M::specialize(&self.material_pipeline, &mut descriptor, layout, key)?;
Ok(descriptor)
}
}
impl PrepassPipelineInternal {
fn specialize(
&self,
mesh_key: MeshPipelineKey,
shader_defs: Vec<ShaderDefVal>,
layout: &MeshVertexBufferLayoutRef,
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
let mut shader_defs = shader_defs;
let mut bind_group_layouts = vec![if mesh_key
.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS)
{
self.view_layout_motion_vectors.clone()
} else {
self.view_layout_no_motion_vectors.clone()
}];
let mut shader_defs = Vec::new();
let mut vertex_attributes = Vec::new();
// Let the shader code know that it's running in a prepass pipeline.
@ -437,40 +465,29 @@ where
// NOTE: Eventually, it would be nice to only add this when the shaders are overloaded by the Material.
// The main limitation right now is that bind group order is hardcoded in shaders.
bind_group_layouts.push(self.material_layout.clone());
#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
shader_defs.push("WEBGL2".into());
shader_defs.push("VERTEX_OUTPUT_INSTANCE_INDEX".into());
if key.mesh_key.contains(MeshPipelineKey::DEPTH_PREPASS) {
if mesh_key.contains(MeshPipelineKey::DEPTH_PREPASS) {
shader_defs.push("DEPTH_PREPASS".into());
}
if key.mesh_key.contains(MeshPipelineKey::MAY_DISCARD) {
if mesh_key.contains(MeshPipelineKey::MAY_DISCARD) {
shader_defs.push("MAY_DISCARD".into());
}
let blend_key = key
.mesh_key
.intersection(MeshPipelineKey::BLEND_RESERVED_BITS);
let blend_key = mesh_key.intersection(MeshPipelineKey::BLEND_RESERVED_BITS);
if blend_key == MeshPipelineKey::BLEND_PREMULTIPLIED_ALPHA {
shader_defs.push("BLEND_PREMULTIPLIED_ALPHA".into());
}
if blend_key == MeshPipelineKey::BLEND_ALPHA {
shader_defs.push("BLEND_ALPHA".into());
}
if layout.0.contains(Mesh::ATTRIBUTE_POSITION) {
shader_defs.push("VERTEX_POSITIONS".into());
vertex_attributes.push(Mesh::ATTRIBUTE_POSITION.at_shader_location(0));
}
// For directional light shadow map views, use unclipped depth via either the native GPU feature,
// or emulated by setting depth in the fragment shader for GPUs that don't support it natively.
let emulate_unclipped_depth = key
.mesh_key
.contains(MeshPipelineKey::UNCLIPPED_DEPTH_ORTHO)
let emulate_unclipped_depth = mesh_key.contains(MeshPipelineKey::UNCLIPPED_DEPTH_ORTHO)
&& !self.depth_clip_control_supported;
if emulate_unclipped_depth {
shader_defs.push("UNCLIPPED_DEPTH_ORTHO_EMULATION".into());
@ -482,36 +499,28 @@ where
// https://github.com/bevyengine/bevy/pull/8877
shader_defs.push("PREPASS_FRAGMENT".into());
}
let unclipped_depth = key
.mesh_key
.contains(MeshPipelineKey::UNCLIPPED_DEPTH_ORTHO)
let unclipped_depth = mesh_key.contains(MeshPipelineKey::UNCLIPPED_DEPTH_ORTHO)
&& self.depth_clip_control_supported;
if layout.0.contains(Mesh::ATTRIBUTE_UV_0) {
shader_defs.push("VERTEX_UVS".into());
shader_defs.push("VERTEX_UVS_A".into());
vertex_attributes.push(Mesh::ATTRIBUTE_UV_0.at_shader_location(1));
}
if layout.0.contains(Mesh::ATTRIBUTE_UV_1) {
shader_defs.push("VERTEX_UVS".into());
shader_defs.push("VERTEX_UVS_B".into());
vertex_attributes.push(Mesh::ATTRIBUTE_UV_1.at_shader_location(2));
}
if key.mesh_key.contains(MeshPipelineKey::NORMAL_PREPASS) {
if mesh_key.contains(MeshPipelineKey::NORMAL_PREPASS) {
shader_defs.push("NORMAL_PREPASS".into());
}
if key
.mesh_key
.intersects(MeshPipelineKey::NORMAL_PREPASS | MeshPipelineKey::DEFERRED_PREPASS)
if mesh_key.intersects(MeshPipelineKey::NORMAL_PREPASS | MeshPipelineKey::DEFERRED_PREPASS)
{
shader_defs.push("NORMAL_PREPASS_OR_DEFERRED_PREPASS".into());
if layout.0.contains(Mesh::ATTRIBUTE_NORMAL) {
shader_defs.push("VERTEX_NORMALS".into());
vertex_attributes.push(Mesh::ATTRIBUTE_NORMAL.at_shader_location(3));
} else if key.mesh_key.contains(MeshPipelineKey::NORMAL_PREPASS) {
} else if mesh_key.contains(MeshPipelineKey::NORMAL_PREPASS) {
warn!(
"The default normal prepass expects the mesh to have vertex normal attributes."
);
@ -521,91 +530,62 @@ where
vertex_attributes.push(Mesh::ATTRIBUTE_TANGENT.at_shader_location(4));
}
}
if key
.mesh_key
if mesh_key
.intersects(MeshPipelineKey::MOTION_VECTOR_PREPASS | MeshPipelineKey::DEFERRED_PREPASS)
{
shader_defs.push("MOTION_VECTOR_PREPASS_OR_DEFERRED_PREPASS".into());
}
if key.mesh_key.contains(MeshPipelineKey::DEFERRED_PREPASS) {
if mesh_key.contains(MeshPipelineKey::DEFERRED_PREPASS) {
shader_defs.push("DEFERRED_PREPASS".into());
}
if key.mesh_key.contains(MeshPipelineKey::LIGHTMAPPED) {
if mesh_key.contains(MeshPipelineKey::LIGHTMAPPED) {
shader_defs.push("LIGHTMAP".into());
}
if key
.mesh_key
.contains(MeshPipelineKey::LIGHTMAP_BICUBIC_SAMPLING)
{
if mesh_key.contains(MeshPipelineKey::LIGHTMAP_BICUBIC_SAMPLING) {
shader_defs.push("LIGHTMAP_BICUBIC_SAMPLING".into());
}
if layout.0.contains(Mesh::ATTRIBUTE_COLOR) {
shader_defs.push("VERTEX_COLORS".into());
vertex_attributes.push(Mesh::ATTRIBUTE_COLOR.at_shader_location(7));
}
if key
.mesh_key
.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS)
{
if mesh_key.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) {
shader_defs.push("MOTION_VECTOR_PREPASS".into());
}
if key.mesh_key.contains(MeshPipelineKey::HAS_PREVIOUS_SKIN) {
if mesh_key.contains(MeshPipelineKey::HAS_PREVIOUS_SKIN) {
shader_defs.push("HAS_PREVIOUS_SKIN".into());
}
if key.mesh_key.contains(MeshPipelineKey::HAS_PREVIOUS_MORPH) {
if mesh_key.contains(MeshPipelineKey::HAS_PREVIOUS_MORPH) {
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 self.binding_arrays_are_usable {
shader_defs.push("MULTIPLE_LIGHTMAPS_IN_ARRAY".into());
}
if key
.mesh_key
.contains(MeshPipelineKey::VISIBILITY_RANGE_DITHER)
{
if mesh_key.contains(MeshPipelineKey::VISIBILITY_RANGE_DITHER) {
shader_defs.push("VISIBILITY_RANGE_DITHER".into());
}
if key.mesh_key.intersects(
if mesh_key.intersects(
MeshPipelineKey::NORMAL_PREPASS
| MeshPipelineKey::MOTION_VECTOR_PREPASS
| MeshPipelineKey::DEFERRED_PREPASS,
) {
shader_defs.push("PREPASS_FRAGMENT".into());
}
let bind_group = setup_morph_and_skinning_defs(
&self.mesh_layouts,
layout,
5,
&key.mesh_key,
&mesh_key,
&mut shader_defs,
&mut vertex_attributes,
self.skins_use_uniform_buffers,
);
bind_group_layouts.insert(1, bind_group);
let vertex_buffer_layout = layout.0.get_layout(&vertex_attributes)?;
// Setup prepass fragment targets - normals in slot 0 (or None if not needed), motion vectors in slot 1
let mut targets = prepass_target_descriptors(
key.mesh_key.contains(MeshPipelineKey::NORMAL_PREPASS),
key.mesh_key
.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS),
key.mesh_key.contains(MeshPipelineKey::DEFERRED_PREPASS),
mesh_key.contains(MeshPipelineKey::NORMAL_PREPASS),
mesh_key.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS),
mesh_key.contains(MeshPipelineKey::DEFERRED_PREPASS),
);
if targets.iter().all(Option::is_none) {
@ -619,12 +599,12 @@ where
// prepass shader, or we are emulating unclipped depth in the fragment shader.
let fragment_required = !targets.is_empty()
|| emulate_unclipped_depth
|| (key.mesh_key.contains(MeshPipelineKey::MAY_DISCARD)
|| (mesh_key.contains(MeshPipelineKey::MAY_DISCARD)
&& self.prepass_material_fragment_shader.is_some());
let fragment = fragment_required.then(|| {
// Use the fragment shader from the material
let frag_shader_handle = if key.mesh_key.contains(MeshPipelineKey::DEFERRED_PREPASS) {
let frag_shader_handle = if mesh_key.contains(MeshPipelineKey::DEFERRED_PREPASS) {
match self.deferred_material_fragment_shader.clone() {
Some(frag_shader_handle) => frag_shader_handle,
_ => PREPASS_SHADER_HANDLE,
@ -645,7 +625,7 @@ where
});
// Use the vertex shader from the material if present
let vert_shader_handle = if key.mesh_key.contains(MeshPipelineKey::DEFERRED_PREPASS) {
let vert_shader_handle = if mesh_key.contains(MeshPipelineKey::DEFERRED_PREPASS) {
if let Some(handle) = &self.deferred_material_vertex_shader {
handle.clone()
} else {
@ -656,8 +636,7 @@ where
} else {
PREPASS_SHADER_HANDLE
};
let mut descriptor = RenderPipelineDescriptor {
let descriptor = RenderPipelineDescriptor {
vertex: VertexState {
shader: vert_shader_handle,
entry_point: "vertex".into(),
@ -667,7 +646,7 @@ where
fragment,
layout: bind_group_layouts,
primitive: PrimitiveState {
topology: key.mesh_key.primitive_topology(),
topology: mesh_key.primitive_topology(),
strip_index_format: None,
front_face: FrontFace::Ccw,
cull_mode: None,
@ -692,7 +671,7 @@ where
},
}),
multisample: MultisampleState {
count: key.mesh_key.msaa_samples(),
count: mesh_key.msaa_samples(),
mask: !0,
alpha_to_coverage_enabled: false,
},
@ -700,12 +679,6 @@ where
label: Some("prepass_pipeline".into()),
zero_initialize_workgroup_memory: false,
};
// This is a bit risky because it's possible to change something that would
// break the prepass but be fine in the main pass.
// Since this api is pretty low-level it doesn't matter that much, but it is a potential issue.
M::specialize(&self.material_pipeline, &mut descriptor, layout, key)?;
Ok(descriptor)
}
}
@ -790,7 +763,7 @@ pub fn prepare_prepass_view_bind_group<M: Material>(
) {
prepass_view_bind_group.no_motion_vectors = Some(render_device.create_bind_group(
"prepass_view_no_motion_vectors_bind_group",
&prepass_pipeline.view_layout_no_motion_vectors,
&prepass_pipeline.internal.view_layout_no_motion_vectors,
&BindGroupEntries::with_indices((
(0, view_binding.clone()),
(1, globals_binding.clone()),
@ -801,7 +774,7 @@ pub fn prepare_prepass_view_bind_group<M: Material>(
if let Some(previous_view_uniforms_binding) = previous_view_uniforms.uniforms.binding() {
prepass_view_bind_group.motion_vectors = Some(render_device.create_bind_group(
"prepass_view_motion_vectors_bind_group",
&prepass_pipeline.view_layout_motion_vectors,
&prepass_pipeline.internal.view_layout_motion_vectors,
&BindGroupEntries::with_indices((
(0, view_binding),
(1, globals_binding),