Skip to content

Commit 2e79951

Browse files
committed
Shader Imports. Decouple Mesh logic from PBR (#3137)
## Shader Imports This adds "whole file" shader imports. These come in two flavors: ### Asset Path Imports ```rust // /assets/shaders/custom.wgsl #import "shaders/custom_material.wgsl" [[stage(fragment)]] fn fragment() -> [[location(0)]] vec4<f32> { return get_color(); } ``` ```rust // /assets/shaders/custom_material.wgsl [[block]] struct CustomMaterial { color: vec4<f32>; }; [[group(1), binding(0)]] var<uniform> material: CustomMaterial; ``` ### Custom Path Imports Enables defining custom import paths. These are intended to be used by crates to export shader functionality: ```rust // bevy_pbr2/src/render/pbr.wgsl #import bevy_pbr::mesh_view_bind_group #import bevy_pbr::mesh_bind_group [[block]] struct StandardMaterial { base_color: vec4<f32>; emissive: vec4<f32>; perceptual_roughness: f32; metallic: f32; reflectance: f32; flags: u32; }; /* rest of PBR fragment shader here */ ``` ```rust impl Plugin for MeshRenderPlugin { fn build(&self, app: &mut bevy_app::App) { let mut shaders = app.world.get_resource_mut::<Assets<Shader>>().unwrap(); shaders.set_untracked( MESH_BIND_GROUP_HANDLE, Shader::from_wgsl(include_str!("mesh_bind_group.wgsl")) .with_import_path("bevy_pbr::mesh_bind_group"), ); shaders.set_untracked( MESH_VIEW_BIND_GROUP_HANDLE, Shader::from_wgsl(include_str!("mesh_view_bind_group.wgsl")) .with_import_path("bevy_pbr::mesh_view_bind_group"), ); ``` By convention these should use rust-style module paths that start with the crate name. Ultimately we might enforce this convention. Note that this feature implements _run time_ import resolution. Ultimately we should move the import logic into an asset preprocessor once Bevy gets support for that. ## Decouple Mesh Logic from PBR Logic via MeshRenderPlugin This breaks out mesh rendering code from PBR material code, which improves the legibility of the code, decouples mesh logic from PBR logic, and opens the door for a future `MaterialPlugin<T: Material>` that handles all of the pipeline setup for arbitrary shader materials. ## Removed `RenderAsset<Shader>` in favor of extracting shaders into RenderPipelineCache This simplifies the shader import implementation and removes the need to pass around `RenderAssets<Shader>`. ## RenderCommands are now fallible This allows us to cleanly handle pipelines+shaders not being ready yet. We can abort a render command early in these cases, preventing bevy from trying to bind group / do draw calls for pipelines that couldn't be bound. This could also be used in the future for things like "components not existing on entities yet". # Next Steps * Investigate using Naga for "partial typed imports" (ex: `#import bevy_pbr::material::StandardMaterial`, which would import only the StandardMaterial struct) * Implement `MaterialPlugin<T: Material>` for low-boilerplate custom material shaders * Move shader import logic into the asset preprocessor once bevy gets support for that. Fixes #3132
1 parent 1076a8f commit 2e79951

File tree

21 files changed

+1423
-1003
lines changed

21 files changed

+1423
-1003
lines changed

assets/shaders/custom.wgsl

Lines changed: 0 additions & 46 deletions
This file was deleted.

assets/shaders/custom_material.wgsl

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[[block]]
2+
struct CustomMaterial {
3+
color: vec4<f32>;
4+
};
5+
[[group(1), binding(0)]]
6+
var<uniform> material: CustomMaterial;
7+
8+
[[stage(fragment)]]
9+
fn fragment() -> [[location(0)]] vec4<f32> {
10+
return material.color;
11+
}

assets/shaders/shader_defs.wgsl

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,6 @@
1-
[[block]]
2-
struct View {
3-
view_proj: mat4x4<f32>;
4-
projection: mat4x4<f32>;
5-
world_position: vec3<f32>;
6-
};
7-
[[group(0), binding(0)]]
8-
var<uniform> view: View;
1+
#import bevy_pbr::mesh_view_bind_group
2+
#import bevy_pbr::mesh_struct
93

10-
[[block]]
11-
struct Mesh {
12-
transform: mat4x4<f32>;
13-
};
144
[[group(1), binding(0)]]
155
var<uniform> mesh: Mesh;
166

@@ -26,7 +16,7 @@ struct VertexOutput {
2616

2717
[[stage(vertex)]]
2818
fn vertex(vertex: Vertex) -> VertexOutput {
29-
let world_position = mesh.transform * vec4<f32>(vertex.position, 1.0);
19+
let world_position = mesh.model * vec4<f32>(vertex.position, 1.0);
3020

3121
var out: VertexOutput;
3222
out.clip_position = view.view_proj * world_position;

crates/bevy_asset/src/loader.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,19 @@ impl<T: Asset> LoadedAsset<T> {
4646
}
4747
}
4848

49-
pub fn with_dependency(mut self, asset_path: AssetPath) -> Self {
49+
pub fn add_dependency(&mut self, asset_path: AssetPath) {
5050
self.dependencies.push(asset_path.to_owned());
51+
}
52+
53+
pub fn with_dependency(mut self, asset_path: AssetPath) -> Self {
54+
self.add_dependency(asset_path);
5155
self
5256
}
5357

54-
pub fn with_dependencies(mut self, asset_paths: Vec<AssetPath<'static>>) -> Self {
55-
self.dependencies.extend(asset_paths);
58+
pub fn with_dependencies(mut self, mut asset_paths: Vec<AssetPath<'static>>) -> Self {
59+
for asset_path in asset_paths.drain(..) {
60+
self.add_dependency(asset_path);
61+
}
5662
self
5763
}
5864
}

examples/shader/custom_shader_pipelined.rs

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ use bevy::{
77
},
88
math::{Vec3, Vec4},
99
pbr2::{
10-
DrawMesh, MeshUniform, PbrPipeline, PbrPipelineKey, SetMeshViewBindGroup,
11-
SetTransformBindGroup,
10+
DrawMesh, MeshPipeline, MeshPipelineKey, MeshUniform, SetMeshBindGroup,
11+
SetMeshViewBindGroup,
1212
},
1313
prelude::{AddAsset, App, AssetServer, Assets, GlobalTransform, Handle, Plugin, Transform},
1414
reflect::TypeUuid,
@@ -19,8 +19,8 @@ use bevy::{
1919
render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets},
2020
render_component::ExtractComponentPlugin,
2121
render_phase::{
22-
AddRenderCommand, DrawFunctions, EntityRenderCommand, RenderPhase, SetItemPipeline,
23-
TrackedRenderPass,
22+
AddRenderCommand, DrawFunctions, EntityRenderCommand, RenderCommandResult, RenderPhase,
23+
SetItemPipeline, TrackedRenderPass,
2424
},
2525
render_resource::*,
2626
renderer::RenderDevice,
@@ -127,22 +127,21 @@ impl Plugin for CustomMaterialPlugin {
127127
}
128128

129129
pub struct CustomPipeline {
130+
mesh_pipeline: MeshPipeline,
130131
material_layout: BindGroupLayout,
131132
shader: Handle<Shader>,
132-
pbr_pipeline: PbrPipeline,
133133
}
134134

135135
impl SpecializedPipeline for CustomPipeline {
136-
type Key = PbrPipelineKey;
136+
type Key = MeshPipelineKey;
137137

138138
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
139-
let mut descriptor = self.pbr_pipeline.specialize(key);
140-
descriptor.vertex.shader = self.shader.clone();
139+
let mut descriptor = self.mesh_pipeline.specialize(key);
141140
descriptor.fragment.as_mut().unwrap().shader = self.shader.clone();
142141
descriptor.layout = Some(vec![
143-
self.pbr_pipeline.view_layout.clone(),
144-
self.pbr_pipeline.mesh_layout.clone(),
142+
self.mesh_pipeline.view_layout.clone(),
145143
self.material_layout.clone(),
144+
self.mesh_pipeline.mesh_layout.clone(),
146145
]);
147146
descriptor
148147
}
@@ -167,8 +166,8 @@ impl FromWorld for CustomPipeline {
167166
});
168167

169168
CustomPipeline {
170-
pbr_pipeline: world.get_resource::<PbrPipeline>().unwrap().clone(),
171-
shader: asset_server.load("shaders/custom.wgsl"),
169+
mesh_pipeline: world.get_resource::<MeshPipeline>().unwrap().clone(),
170+
shader: asset_server.load("shaders/custom_material.wgsl"),
172171
material_layout,
173172
}
174173
}
@@ -189,7 +188,7 @@ pub fn queue_custom(
189188
.read()
190189
.get_id::<DrawCustom>()
191190
.unwrap();
192-
let key = PbrPipelineKey::from_msaa_samples(msaa.samples);
191+
let key = MeshPipelineKey::from_msaa_samples(msaa.samples);
193192
for (view, mut transparent_phase) in views.iter_mut() {
194193
let view_matrix = view.transform.compute_matrix();
195194
let view_row_2 = view_matrix.row(2);
@@ -213,8 +212,8 @@ pub fn queue_custom(
213212
type DrawCustom = (
214213
SetItemPipeline,
215214
SetMeshViewBindGroup<0>,
216-
SetTransformBindGroup<1>,
217215
SetCustomMaterialBindGroup,
216+
SetMeshBindGroup<2>,
218217
DrawMesh,
219218
);
220219

@@ -229,9 +228,10 @@ impl EntityRenderCommand for SetCustomMaterialBindGroup {
229228
item: Entity,
230229
(materials, query): SystemParamItem<'w, '_, Self::Param>,
231230
pass: &mut TrackedRenderPass<'w>,
232-
) {
231+
) -> RenderCommandResult {
233232
let material_handle = query.get(item).unwrap();
234233
let material = materials.into_inner().get(material_handle).unwrap();
235-
pass.set_bind_group(2, &material.bind_group, &[]);
234+
pass.set_bind_group(1, &material.bind_group, &[]);
235+
RenderCommandResult::Success
236236
}
237237
}

examples/shader/shader_defs_pipelined.rs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ use bevy::{
44
ecs::prelude::*,
55
math::Vec3,
66
pbr2::{
7-
DrawMesh, MeshUniform, PbrPipeline, PbrPipelineKey, SetMeshViewBindGroup,
8-
SetTransformBindGroup,
7+
DrawMesh, MeshPipeline, MeshPipelineKey, MeshUniform, SetMeshBindGroup,
8+
SetMeshViewBindGroup,
99
},
1010
prelude::{App, AssetServer, Assets, GlobalTransform, Handle, Plugin, Transform},
1111
render2::{
@@ -86,39 +86,39 @@ fn setup(mut commands: Commands, mut meshes: ResMut<Assets<Mesh>>) {
8686
}
8787

8888
struct IsRedPipeline {
89+
mesh_pipline: MeshPipeline,
8990
shader: Handle<Shader>,
90-
pbr_pipeline: PbrPipeline,
9191
}
9292

9393
impl FromWorld for IsRedPipeline {
9494
fn from_world(world: &mut World) -> Self {
9595
let asset_server = world.get_resource::<AssetServer>().unwrap();
96-
let pbr_pipeline = world.get_resource::<PbrPipeline>().unwrap();
96+
let mesh_pipeline = world.get_resource::<MeshPipeline>().unwrap();
9797
let shader = asset_server.load("shaders/shader_defs.wgsl");
9898
IsRedPipeline {
99+
mesh_pipline: mesh_pipeline.clone(),
99100
shader,
100-
pbr_pipeline: pbr_pipeline.clone(),
101101
}
102102
}
103103
}
104104

105105
impl SpecializedPipeline for IsRedPipeline {
106-
type Key = (IsRed, PbrPipelineKey);
106+
type Key = (IsRed, MeshPipelineKey);
107107

108108
fn specialize(&self, (is_red, pbr_pipeline_key): Self::Key) -> RenderPipelineDescriptor {
109109
let mut shader_defs = Vec::new();
110110
if is_red.0 {
111111
shader_defs.push("IS_RED".to_string());
112112
}
113-
let mut descriptor = self.pbr_pipeline.specialize(pbr_pipeline_key);
113+
let mut descriptor = self.mesh_pipline.specialize(pbr_pipeline_key);
114114
descriptor.vertex.shader = self.shader.clone();
115115
descriptor.vertex.shader_defs = shader_defs.clone();
116116
let fragment = descriptor.fragment.as_mut().unwrap();
117117
fragment.shader = self.shader.clone();
118118
fragment.shader_defs = shader_defs;
119119
descriptor.layout = Some(vec![
120-
self.pbr_pipeline.view_layout.clone(),
121-
self.pbr_pipeline.mesh_layout.clone(),
120+
self.mesh_pipline.view_layout.clone(),
121+
self.mesh_pipline.mesh_layout.clone(),
122122
]);
123123
descriptor
124124
}
@@ -127,7 +127,7 @@ impl SpecializedPipeline for IsRedPipeline {
127127
type DrawIsRed = (
128128
SetItemPipeline,
129129
SetMeshViewBindGroup<0>,
130-
SetTransformBindGroup<1>,
130+
SetMeshBindGroup<1>,
131131
DrawMesh,
132132
);
133133

@@ -144,7 +144,7 @@ fn queue_custom(
144144
.read()
145145
.get_id::<DrawIsRed>()
146146
.unwrap();
147-
let key = PbrPipelineKey::from_msaa_samples(msaa.samples);
147+
let key = MeshPipelineKey::from_msaa_samples(msaa.samples);
148148
for (view, mut transparent_phase) in views.iter_mut() {
149149
let view_matrix = view.transform.compute_matrix();
150150
let view_row_2 = view_matrix.row(2);

pipelined/bevy_pbr2/src/alpha.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use bevy_reflect::Reflect;
33

44
// FIXME: This should probably be part of bevy_render2!
55
/// Alpha mode
6-
#[derive(Debug, Reflect, Clone, PartialEq)]
6+
#[derive(Debug, Reflect, Copy, Clone, PartialEq)]
77
#[reflect(Component)]
88
pub enum AlphaMode {
99
Opaque,

pipelined/bevy_pbr2/src/lib.rs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use bevy_core_pipeline::{AlphaMask3d, Opaque3d, Transparent3d};
1616
use bevy_ecs::prelude::*;
1717
use bevy_reflect::TypeUuid;
1818
use bevy_render2::{
19-
render_component::{ExtractComponentPlugin, UniformComponentPlugin},
19+
render_component::ExtractComponentPlugin,
2020
render_graph::RenderGraph,
2121
render_phase::{sort_phase_system, AddRenderCommand, DrawFunctions},
2222
render_resource::{Shader, SpecializedPipelines},
@@ -44,14 +44,18 @@ pub struct PbrPlugin;
4444
impl Plugin for PbrPlugin {
4545
fn build(&self, app: &mut App) {
4646
let mut shaders = app.world.get_resource_mut::<Assets<Shader>>().unwrap();
47-
let pbr_shader = Shader::from_wgsl(include_str!("render/pbr.wgsl"));
48-
shaders.set_untracked(PBR_SHADER_HANDLE, pbr_shader);
49-
let shadow_shader = Shader::from_wgsl(include_str!("render/depth.wgsl"));
50-
shaders.set_untracked(SHADOW_SHADER_HANDLE, shadow_shader);
47+
shaders.set_untracked(
48+
PBR_SHADER_HANDLE,
49+
Shader::from_wgsl(include_str!("render/pbr.wgsl")),
50+
);
51+
shaders.set_untracked(
52+
SHADOW_SHADER_HANDLE,
53+
Shader::from_wgsl(include_str!("render/depth.wgsl")),
54+
);
5155

5256
app.add_plugin(StandardMaterialPlugin)
57+
.add_plugin(MeshRenderPlugin)
5358
.add_plugin(ExtractComponentPlugin::<Handle<StandardMaterial>>::default())
54-
.add_plugin(UniformComponentPlugin::<MeshUniform>::default())
5559
.init_resource::<AmbientLight>()
5660
.init_resource::<DirectionalLightShadowMap>()
5761
.init_resource::<PointLightShadowMap>()
@@ -84,7 +88,6 @@ impl Plugin for PbrPlugin {
8488

8589
let render_app = app.sub_app(RenderApp);
8690
render_app
87-
.add_system_to_stage(RenderStage::Extract, render::extract_meshes)
8891
.add_system_to_stage(
8992
RenderStage::Extract,
9093
render::extract_lights.label(RenderLightSystems::ExtractLights),
@@ -97,13 +100,12 @@ impl Plugin for PbrPlugin {
97100
.exclusive_system()
98101
.label(RenderLightSystems::PrepareLights),
99102
)
100-
.add_system_to_stage(RenderStage::Queue, render::queue_meshes)
101103
.add_system_to_stage(
102104
RenderStage::Queue,
103105
render::queue_shadows.label(RenderLightSystems::QueueShadows),
104106
)
107+
.add_system_to_stage(RenderStage::Queue, queue_meshes)
105108
.add_system_to_stage(RenderStage::Queue, render::queue_shadow_view_bind_group)
106-
.add_system_to_stage(RenderStage::Queue, render::queue_transform_bind_group)
107109
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<Shadow>)
108110
.init_resource::<PbrPipeline>()
109111
.init_resource::<ShadowPipeline>()

pipelined/bevy_pbr2/src/material.rs

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -163,38 +163,44 @@ impl RenderAsset for StandardMaterial {
163163
material: Self::ExtractedAsset,
164164
(render_device, pbr_pipeline, gpu_images): &mut SystemParamItem<Self::Param>,
165165
) -> Result<Self::PreparedAsset, PrepareAssetError<Self::ExtractedAsset>> {
166-
let (base_color_texture_view, base_color_sampler) = if let Some(result) =
167-
pbr_pipeline.image_handle_to_texture(gpu_images, &material.base_color_texture)
166+
let (base_color_texture_view, base_color_sampler) = if let Some(result) = pbr_pipeline
167+
.mesh_pipeline
168+
.get_image_texture(gpu_images, &material.base_color_texture)
168169
{
169170
result
170171
} else {
171172
return Err(PrepareAssetError::RetryNextUpdate(material));
172173
};
173174

174-
let (emissive_texture_view, emissive_sampler) = if let Some(result) =
175-
pbr_pipeline.image_handle_to_texture(gpu_images, &material.emissive_texture)
175+
let (emissive_texture_view, emissive_sampler) = if let Some(result) = pbr_pipeline
176+
.mesh_pipeline
177+
.get_image_texture(gpu_images, &material.emissive_texture)
176178
{
177179
result
178180
} else {
179181
return Err(PrepareAssetError::RetryNextUpdate(material));
180182
};
181183

182184
let (metallic_roughness_texture_view, metallic_roughness_sampler) = if let Some(result) =
183-
pbr_pipeline.image_handle_to_texture(gpu_images, &material.metallic_roughness_texture)
185+
pbr_pipeline
186+
.mesh_pipeline
187+
.get_image_texture(gpu_images, &material.metallic_roughness_texture)
184188
{
185189
result
186190
} else {
187191
return Err(PrepareAssetError::RetryNextUpdate(material));
188192
};
189-
let (normal_map_texture_view, normal_map_sampler) = if let Some(result) =
190-
pbr_pipeline.image_handle_to_texture(gpu_images, &material.normal_map_texture)
193+
let (normal_map_texture_view, normal_map_sampler) = if let Some(result) = pbr_pipeline
194+
.mesh_pipeline
195+
.get_image_texture(gpu_images, &material.normal_map_texture)
191196
{
192197
result
193198
} else {
194199
return Err(PrepareAssetError::RetryNextUpdate(material));
195200
};
196-
let (occlusion_texture_view, occlusion_sampler) = if let Some(result) =
197-
pbr_pipeline.image_handle_to_texture(gpu_images, &material.occlusion_texture)
201+
let (occlusion_texture_view, occlusion_sampler) = if let Some(result) = pbr_pipeline
202+
.mesh_pipeline
203+
.get_image_texture(gpu_images, &material.occlusion_texture)
198204
{
199205
result
200206
} else {

0 commit comments

Comments
 (0)