Skip to content

Commit 23dddcd

Browse files
committed
move plugin to bevy_pbr
1 parent dffb0d0 commit 23dddcd

File tree

3 files changed

+235
-227
lines changed

3 files changed

+235
-227
lines changed
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
use std::marker::PhantomData;
2+
3+
use bevy_app::{App, Plugin};
4+
use bevy_asset::AssetServer;
5+
use bevy_core_pipeline::{
6+
core_3d::graph::{Core3d, Node3d},
7+
FullscreenShader,
8+
};
9+
use bevy_ecs::{
10+
component::Component,
11+
query::QueryItem,
12+
resource::Resource,
13+
system::{Commands, Res},
14+
world::World,
15+
};
16+
use bevy_image::BevyDefault;
17+
use bevy_render::{
18+
extract_component::{
19+
ComponentUniforms, DynamicUniformIndex, ExtractComponent, ExtractComponentPlugin,
20+
UniformComponentPlugin,
21+
},
22+
render_graph::{
23+
NodeRunError, RenderGraphContext, RenderGraphExt, RenderLabel, ViewNode, ViewNodeRunner,
24+
},
25+
render_resource::{
26+
binding_types::{sampler, texture_2d, uniform_buffer},
27+
encase::internal::WriteInto,
28+
BindGroupEntries, BindGroupLayout, BindGroupLayoutEntries, CachedRenderPipelineId,
29+
ColorTargetState, ColorWrites, FragmentState, Operations, PipelineCache,
30+
RenderPassColorAttachment, RenderPassDescriptor, RenderPipelineDescriptor, Sampler,
31+
SamplerBindingType, SamplerDescriptor, ShaderRef, ShaderStages, ShaderType, TextureFormat,
32+
TextureSampleType,
33+
},
34+
renderer::{RenderContext, RenderDevice},
35+
view::ViewTarget,
36+
RenderApp, RenderStartup,
37+
};
38+
use bevy_utils::default;
39+
#[derive(Debug, Hash, PartialEq, Eq, Clone, RenderLabel)]
40+
struct PostProcessLabel;
41+
42+
#[derive(Default)]
43+
pub struct FullscreenMaterialPlugin<T: FullscreenMaterial> {
44+
_marker: PhantomData<T>,
45+
}
46+
impl<T: FullscreenMaterial> Plugin for FullscreenMaterialPlugin<T> {
47+
fn build(&self, app: &mut App) {
48+
app.add_plugins((
49+
ExtractComponentPlugin::<T>::default(),
50+
UniformComponentPlugin::<T>::default(),
51+
));
52+
53+
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
54+
return;
55+
};
56+
render_app.add_systems(RenderStartup, init_pipeline::<T>);
57+
58+
// TODO make this configurable so it's not hardcoded to 3d
59+
render_app
60+
.add_render_graph_node::<ViewNodeRunner<FullscreenMaterialNode<T>>>(
61+
Core3d,
62+
PostProcessLabel,
63+
)
64+
.add_render_graph_edges(
65+
Core3d,
66+
(
67+
Node3d::Tonemapping,
68+
// TODO make this configurable
69+
PostProcessLabel,
70+
Node3d::EndMainPassPostProcessing,
71+
),
72+
);
73+
}
74+
}
75+
76+
pub trait FullscreenMaterial:
77+
Component + ExtractComponent + Clone + Copy + ShaderType + WriteInto + Default
78+
{
79+
fn fragment_shader() -> ShaderRef;
80+
}
81+
82+
#[derive(Resource)]
83+
struct FullscreenMaterialPipeline {
84+
layout: BindGroupLayout,
85+
sampler: Sampler,
86+
pipeline_id: CachedRenderPipelineId,
87+
}
88+
89+
fn init_pipeline<T: FullscreenMaterial>(
90+
mut commands: Commands,
91+
render_device: Res<RenderDevice>,
92+
asset_server: Res<AssetServer>,
93+
fullscreen_shader: Res<FullscreenShader>,
94+
pipeline_cache: Res<PipelineCache>,
95+
) {
96+
// We need to define the bind group layout used for our pipeline
97+
let layout = render_device.create_bind_group_layout(
98+
"post_process_bind_group_layout",
99+
&BindGroupLayoutEntries::sequential(
100+
// The layout entries will only be visible in the fragment stage
101+
ShaderStages::FRAGMENT,
102+
(
103+
// The screen texture
104+
texture_2d(TextureSampleType::Float { filterable: true }),
105+
// The sampler that will be used to sample the screen texture
106+
sampler(SamplerBindingType::Filtering),
107+
// The settings uniform that will control the effect
108+
uniform_buffer::<T>(true),
109+
),
110+
),
111+
);
112+
// We can create the sampler here since it won't change at runtime and doesn't depend on the view
113+
let sampler = render_device.create_sampler(&SamplerDescriptor::default());
114+
let shader = match T::fragment_shader() {
115+
ShaderRef::Default => {
116+
unimplemented!("No default fallback for FullscreenMaterial shader")
117+
}
118+
ShaderRef::Handle(handle) => handle,
119+
ShaderRef::Path(path) => asset_server.load(path),
120+
};
121+
// This will setup a fullscreen triangle for the vertex state.
122+
let vertex_state = fullscreen_shader.to_vertex_state();
123+
let pipeline_id = pipeline_cache.queue_render_pipeline(RenderPipelineDescriptor {
124+
label: Some("post_process_pipeline".into()),
125+
layout: vec![layout.clone()],
126+
vertex: vertex_state,
127+
fragment: Some(FragmentState {
128+
shader,
129+
// TODO handle HDR
130+
targets: vec![Some(ColorTargetState {
131+
format: TextureFormat::bevy_default(),
132+
blend: None,
133+
write_mask: ColorWrites::ALL,
134+
})],
135+
..default()
136+
}),
137+
..default()
138+
});
139+
commands.insert_resource(FullscreenMaterialPipeline {
140+
layout,
141+
sampler,
142+
pipeline_id,
143+
});
144+
}
145+
146+
#[derive(Default)]
147+
struct FullscreenMaterialNode<T: FullscreenMaterial> {
148+
_marker: PhantomData<T>,
149+
}
150+
151+
impl<T: FullscreenMaterial> ViewNode for FullscreenMaterialNode<T> {
152+
// The node needs a query to gather data from the ECS in order to do its rendering,
153+
// but it's not a normal system so we need to define it manually.
154+
//
155+
// This query will only run on the view entity
156+
type ViewQuery = (
157+
&'static ViewTarget,
158+
// This makes sure the node only runs on cameras with the PostProcessSettings component
159+
&'static T,
160+
// As there could be multiple post processing components sent to the GPU (one per camera),
161+
// we need to get the index of the one that is associated with the current view.
162+
&'static DynamicUniformIndex<T>,
163+
);
164+
165+
fn run<'w>(
166+
&self,
167+
_graph: &mut RenderGraphContext,
168+
render_context: &mut RenderContext,
169+
(view_target, _post_process_settings, settings_index): QueryItem<Self::ViewQuery>,
170+
world: &World,
171+
) -> Result<(), NodeRunError> {
172+
let post_process_pipeline = world.resource::<FullscreenMaterialPipeline>();
173+
174+
let pipeline_cache = world.resource::<PipelineCache>();
175+
176+
let Some(pipeline) = pipeline_cache.get_render_pipeline(post_process_pipeline.pipeline_id)
177+
else {
178+
return Ok(());
179+
};
180+
181+
let settings_uniforms = world.resource::<ComponentUniforms<T>>();
182+
let Some(settings_binding) = settings_uniforms.uniforms().binding() else {
183+
return Ok(());
184+
};
185+
186+
let post_process = view_target.post_process_write();
187+
188+
let bind_group = render_context.render_device().create_bind_group(
189+
"post_process_bind_group",
190+
&post_process_pipeline.layout,
191+
// It's important for this to match the BindGroupLayout defined in the PostProcessPipeline
192+
&BindGroupEntries::sequential((
193+
// Make sure to use the source view
194+
post_process.source,
195+
// Use the sampler created for the pipeline
196+
&post_process_pipeline.sampler,
197+
// Set the settings binding
198+
settings_binding.clone(),
199+
)),
200+
);
201+
202+
// Begin the render pass
203+
let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor {
204+
label: Some("post_process_pass"),
205+
color_attachments: &[Some(RenderPassColorAttachment {
206+
// We need to specify the post process destination view here
207+
// to make sure we write to the appropriate texture.
208+
view: post_process.destination,
209+
depth_slice: None,
210+
resolve_target: None,
211+
ops: Operations::default(),
212+
})],
213+
depth_stencil_attachment: None,
214+
timestamp_writes: None,
215+
occlusion_query_set: None,
216+
});
217+
218+
// This is mostly just wgpu boilerplate for drawing a fullscreen triangle,
219+
// using the pipeline/bind_group created above
220+
render_pass.set_render_pipeline(pipeline);
221+
// By passing in the index of the post process settings on this view, we ensure
222+
// that in the event that multiple settings were sent to the GPU (as would be the
223+
// case with multiple cameras), we use the correct one.
224+
render_pass.set_bind_group(0, &bind_group, &[settings_index.index()]);
225+
render_pass.draw(0..3, 0..1);
226+
227+
Ok(())
228+
}
229+
}

crates/bevy_pbr/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ pub mod decal;
3131
pub mod deferred;
3232
mod extended_material;
3333
mod fog;
34+
pub mod fullscreen_material;
3435
mod light_probe;
3536
mod lightmap;
3637
mod material;

0 commit comments

Comments
 (0)