1use crate::gpu_types::PostProcessUniforms;
2use wgpu::util::DeviceExt;
3
4pub struct PostProcessState {
6 pub post_bind_group_layout: wgpu::BindGroupLayout,
7 pub blur_params_bind_group_layout: wgpu::BindGroupLayout,
8 pub composite_bloom_bind_group_layout: wgpu::BindGroupLayout,
9 pub post_params_buffer: wgpu::Buffer,
10 pub post_params_bind_group_layout: wgpu::BindGroupLayout,
11 pub post_params_bind_group: wgpu::BindGroup,
12 pub bloom_extract_pipeline: wgpu::RenderPipeline,
13 pub bloom_blur_pipeline: wgpu::RenderPipeline,
14 pub composite_pipeline: wgpu::RenderPipeline,
15 pub hdr_texture: wgpu::Texture,
16 pub hdr_texture_view: wgpu::TextureView,
17 pub hdr_bind_group: wgpu::BindGroup,
18 pub bloom_extract_texture_view: wgpu::TextureView,
19 pub bloom_extract_bind_group: wgpu::BindGroup,
20 pub bloom_blur_texture_view: wgpu::TextureView,
21 pub bloom_blur_bind_group: wgpu::BindGroup,
22 pub composite_bloom_bind_group: wgpu::BindGroup,
23 pub blur_params_buffer: wgpu::Buffer,
24 pub blur_h_bind_group: wgpu::BindGroup,
25 pub blur_v_bind_group: wgpu::BindGroup,
26}
27
28pub fn build_post_process_resources(
29 device: &wgpu::Device,
30 surface_format: wgpu::TextureFormat,
31 width: u32,
32 height: u32,
33 depth_view: &wgpu::TextureView,
34) -> PostProcessState {
35 let post_shader = {
36 #[cfg(not(target_arch = "wasm32"))]
37 let source = std::fs::read_to_string("demo/assets/shaders/post_process.wgsl")
38 .unwrap_or_else(|_| include_str!("shaders/post_process.wgsl").to_string());
39
40 #[cfg(target_arch = "wasm32")]
41 let source = include_str!("shaders/post_process_wasm.wgsl").to_string();
42
43 device.create_shader_module(wgpu::ShaderModuleDescriptor {
44 label: Some("Post-Processing Shader"),
45 source: wgpu::ShaderSource::Wgsl(source.into()),
46 })
47 };
48
49 let post_bind_group_layout =
50 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
51 label: Some("post_bind_group_layout"),
52 entries: &[
53 wgpu::BindGroupLayoutEntry {
54 binding: 0,
55 visibility: wgpu::ShaderStages::FRAGMENT,
56 ty: wgpu::BindingType::Texture {
57 multisampled: false,
58 view_dimension: wgpu::TextureViewDimension::D2,
59 sample_type: wgpu::TextureSampleType::Float { filterable: true },
60 },
61 count: None,
62 },
63 wgpu::BindGroupLayoutEntry {
64 binding: 1,
65 visibility: wgpu::ShaderStages::FRAGMENT,
66 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
67 count: None,
68 },
69 ],
70 });
71
72 let blur_params_bind_group_layout =
73 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
74 label: Some("blur_params_bind_group_layout"),
75 entries: &[wgpu::BindGroupLayoutEntry {
76 binding: 0,
77 visibility: wgpu::ShaderStages::FRAGMENT,
78 ty: wgpu::BindingType::Buffer {
79 ty: wgpu::BufferBindingType::Uniform,
80 has_dynamic_offset: false,
81 min_binding_size: None,
82 },
83 count: None,
84 }],
85 });
86
87 #[cfg(not(target_arch = "wasm32"))]
90 let composite_bloom_bind_group_layout =
91 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
92 label: Some("composite_bloom_bind_group_layout"),
93 entries: &[
94 wgpu::BindGroupLayoutEntry {
95 binding: 0,
96 visibility: wgpu::ShaderStages::FRAGMENT,
97 ty: wgpu::BindingType::Texture {
98 multisampled: false,
99 view_dimension: wgpu::TextureViewDimension::D2,
100 sample_type: wgpu::TextureSampleType::Float { filterable: true },
101 },
102 count: None,
103 },
104 wgpu::BindGroupLayoutEntry {
105 binding: 1,
106 visibility: wgpu::ShaderStages::FRAGMENT,
107 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
108 count: None,
109 },
110 wgpu::BindGroupLayoutEntry {
111 binding: 2,
112 visibility: wgpu::ShaderStages::FRAGMENT,
113 ty: wgpu::BindingType::Texture {
114 multisampled: false,
115 view_dimension: wgpu::TextureViewDimension::D2,
116 sample_type: wgpu::TextureSampleType::Depth,
117 },
118 count: None,
119 },
120 ],
121 });
122 #[cfg(target_arch = "wasm32")]
123 let composite_bloom_bind_group_layout =
124 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
125 label: Some("composite_bloom_bind_group_layout"),
126 entries: &[
127 wgpu::BindGroupLayoutEntry {
128 binding: 0,
129 visibility: wgpu::ShaderStages::FRAGMENT,
130 ty: wgpu::BindingType::Texture {
131 multisampled: false,
132 view_dimension: wgpu::TextureViewDimension::D2,
133 sample_type: wgpu::TextureSampleType::Float { filterable: true },
134 },
135 count: None,
136 },
137 wgpu::BindGroupLayoutEntry {
138 binding: 1,
139 visibility: wgpu::ShaderStages::FRAGMENT,
140 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
141 count: None,
142 },
143 ],
144 });
145
146 let post_params_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
147 label: Some("Post Process Params Buffer"),
148 contents: bytemuck::cast_slice(&[PostProcessUniforms {
149 bloom_intensity: 0.8, bloom_threshold: 0.85, exposure: 1.15, chromatic_aberration: 0.0, vignette_intensity: 0.25, film_grain_intensity: 0.03, dof_focus_dist: 15.0,
156 dof_focus_range: 25.0,
157 dof_blur_size: 0.0, _padding: [0.0; 3],
159 }]),
160 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
161 });
162
163 let post_params_bind_group_layout =
164 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
165 label: Some("post_params_bind_group_layout"),
166 entries: &[wgpu::BindGroupLayoutEntry {
167 binding: 0,
168 visibility: wgpu::ShaderStages::FRAGMENT,
169 ty: wgpu::BindingType::Buffer {
170 ty: wgpu::BufferBindingType::Uniform,
171 has_dynamic_offset: false,
172 min_binding_size: None,
173 },
174 count: None,
175 }],
176 });
177
178 let post_params_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
179 layout: &post_params_bind_group_layout,
180 entries: &[wgpu::BindGroupEntry {
181 binding: 0,
182 resource: post_params_buffer.as_entire_binding(),
183 }],
184 label: Some("post_params_bind_group"),
185 });
186
187 let (bloom_extract_pipeline, bloom_blur_pipeline, composite_pipeline) = build_post_pipelines(
188 device,
189 &post_shader,
190 &post_bind_group_layout,
191 &blur_params_bind_group_layout,
192 &composite_bloom_bind_group_layout,
193 &post_params_bind_group_layout,
194 surface_format,
195 );
196
197 let post_sampler = create_post_sampler(device);
198 let (
199 hdr_texture,
200 hdr_texture_view,
201 hdr_bind_group,
202 bloom_extract_texture_view,
203 bloom_extract_bind_group,
204 bloom_blur_texture_view,
205 bloom_blur_bind_group,
206 composite_bloom_bind_group,
207 ) = create_post_textures(
208 device,
209 &post_bind_group_layout,
210 &composite_bloom_bind_group_layout,
211 &post_sampler,
212 width,
213 height,
214 depth_view,
215 );
216
217 let (blur_params_buffer, blur_h_bind_group, blur_v_bind_group) =
218 create_blur_buffers(device, &blur_params_bind_group_layout, width, height);
219
220 PostProcessState {
221 post_bind_group_layout,
222 blur_params_bind_group_layout,
223 composite_bloom_bind_group_layout,
224 post_params_buffer,
225 post_params_bind_group_layout,
226 post_params_bind_group,
227 bloom_extract_pipeline,
228 bloom_blur_pipeline,
229 composite_pipeline,
230 hdr_texture,
231 hdr_texture_view,
232 hdr_bind_group,
233 bloom_extract_texture_view,
234 bloom_extract_bind_group,
235 bloom_blur_texture_view,
236 bloom_blur_bind_group,
237 composite_bloom_bind_group,
238 blur_params_buffer,
239 blur_h_bind_group,
240 blur_v_bind_group,
241 }
242}
243
244fn build_post_pipelines(
245 device: &wgpu::Device,
246 post_shader: &wgpu::ShaderModule,
247 post_bgl: &wgpu::BindGroupLayout,
248 blur_bgl: &wgpu::BindGroupLayout,
249 composite_bloom_bgl: &wgpu::BindGroupLayout,
250 post_params_bgl: &wgpu::BindGroupLayout,
251 surface_format: wgpu::TextureFormat,
252) -> (
253 wgpu::RenderPipeline,
254 wgpu::RenderPipeline,
255 wgpu::RenderPipeline,
256) {
257 let extract_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
258 label: Some("Bloom Extract Pipeline Layout"),
259 bind_group_layouts: &[post_bgl, blur_bgl, post_params_bgl],
260 push_constant_ranges: &[],
261 });
262 let bloom_extract_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
263 label: Some("Bloom Extract Pipeline"),
264 layout: Some(&extract_layout),
265 vertex: wgpu::VertexState {
266 module: post_shader,
267 entry_point: "vs_fullscreen",
268 compilation_options: Default::default(),
269 buffers: &[],
270 },
271 fragment: Some(wgpu::FragmentState {
272 module: post_shader,
273 entry_point: "fs_bright_extract",
274 compilation_options: Default::default(),
275 targets: &[Some(wgpu::ColorTargetState {
276 format: wgpu::TextureFormat::Rgba16Float,
277 blend: Some(wgpu::BlendState::REPLACE),
278 write_mask: wgpu::ColorWrites::ALL,
279 })],
280 }),
281 primitive: wgpu::PrimitiveState {
282 topology: wgpu::PrimitiveTopology::TriangleList,
283 ..Default::default()
284 },
285 depth_stencil: None,
286 multisample: wgpu::MultisampleState::default(),
287 multiview: None,
288 });
289
290 let blur_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
291 label: Some("Bloom Blur Pipeline Layout"),
292 bind_group_layouts: &[post_bgl, blur_bgl],
293 push_constant_ranges: &[],
294 });
295 let bloom_blur_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
296 label: Some("Bloom Blur Pipeline"),
297 layout: Some(&blur_layout),
298 vertex: wgpu::VertexState {
299 module: post_shader,
300 entry_point: "vs_fullscreen",
301 compilation_options: Default::default(),
302 buffers: &[],
303 },
304 fragment: Some(wgpu::FragmentState {
305 module: post_shader,
306 entry_point: "fs_blur",
307 compilation_options: Default::default(),
308 targets: &[Some(wgpu::ColorTargetState {
309 format: wgpu::TextureFormat::Rgba16Float,
310 blend: Some(wgpu::BlendState::REPLACE),
311 write_mask: wgpu::ColorWrites::ALL,
312 })],
313 }),
314 primitive: wgpu::PrimitiveState {
315 topology: wgpu::PrimitiveTopology::TriangleList,
316 ..Default::default()
317 },
318 depth_stencil: None,
319 multisample: wgpu::MultisampleState::default(),
320 multiview: None,
321 });
322
323 let composite_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
324 label: Some("Composite Pipeline Layout"),
325 bind_group_layouts: &[post_bgl, composite_bloom_bgl, post_params_bgl],
326 push_constant_ranges: &[],
327 });
328 let composite_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
329 label: Some("Composite Pipeline"),
330 layout: Some(&composite_layout),
331 vertex: wgpu::VertexState {
332 module: post_shader,
333 entry_point: "vs_fullscreen",
334 compilation_options: Default::default(),
335 buffers: &[],
336 },
337 fragment: Some(wgpu::FragmentState {
338 module: post_shader,
339 entry_point: "fs_composite",
340 compilation_options: Default::default(),
341 targets: &[Some(wgpu::ColorTargetState {
342 format: surface_format,
343 blend: Some(wgpu::BlendState::REPLACE),
344 write_mask: wgpu::ColorWrites::ALL,
345 })],
346 }),
347 primitive: wgpu::PrimitiveState {
348 topology: wgpu::PrimitiveTopology::TriangleList,
349 ..Default::default()
350 },
351 depth_stencil: None,
352 multisample: wgpu::MultisampleState::default(),
353 multiview: None,
354 });
355
356 (
357 bloom_extract_pipeline,
358 bloom_blur_pipeline,
359 composite_pipeline,
360 )
361}
362
363pub fn rebuild_post_pipelines(renderer: &mut crate::Renderer, post_shader: &wgpu::ShaderModule) {
364 let (e, b, c) = build_post_pipelines(
365 &renderer.device,
366 post_shader,
367 &renderer.post.post_bind_group_layout,
368 &renderer.post.blur_params_bind_group_layout,
369 &renderer.post.composite_bloom_bind_group_layout,
370 &renderer.post.post_params_bind_group_layout,
371 renderer.config.format,
372 );
373 renderer.post.bloom_extract_pipeline = e;
374 renderer.post.bloom_blur_pipeline = b;
375 renderer.post.composite_pipeline = c;
376}
377
378fn create_post_sampler(device: &wgpu::Device) -> wgpu::Sampler {
379 device.create_sampler(&wgpu::SamplerDescriptor {
380 address_mode_u: wgpu::AddressMode::ClampToEdge,
381 address_mode_v: wgpu::AddressMode::ClampToEdge,
382 mag_filter: wgpu::FilterMode::Linear,
383 min_filter: wgpu::FilterMode::Linear,
384 ..Default::default()
385 })
386}
387
388pub fn create_post_textures(
389 device: &wgpu::Device,
390 post_bgl: &wgpu::BindGroupLayout,
391 composite_bloom_bgl: &wgpu::BindGroupLayout,
392 sampler: &wgpu::Sampler,
393 width: u32,
394 height: u32,
395 depth_view: &wgpu::TextureView,
396) -> (
397 wgpu::Texture,
398 wgpu::TextureView,
399 wgpu::BindGroup,
400 wgpu::TextureView,
401 wgpu::BindGroup,
402 wgpu::TextureView,
403 wgpu::BindGroup,
404 wgpu::BindGroup,
405) {
406 let make = |label: &str| -> (wgpu::Texture, wgpu::TextureView, wgpu::BindGroup) {
407 let tex = device.create_texture(&wgpu::TextureDescriptor {
408 label: Some(label),
409 size: wgpu::Extent3d {
410 width,
411 height,
412 depth_or_array_layers: 1,
413 },
414 mip_level_count: 1,
415 sample_count: 1,
416 dimension: wgpu::TextureDimension::D2,
417 format: wgpu::TextureFormat::Rgba16Float,
418 usage: wgpu::TextureUsages::RENDER_ATTACHMENT
419 | wgpu::TextureUsages::TEXTURE_BINDING
420 | wgpu::TextureUsages::COPY_SRC,
421 view_formats: &[],
422 });
423 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
424 let bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
425 layout: post_bgl,
426 entries: &[
427 wgpu::BindGroupEntry {
428 binding: 0,
429 resource: wgpu::BindingResource::TextureView(&view),
430 },
431 wgpu::BindGroupEntry {
432 binding: 1,
433 resource: wgpu::BindingResource::Sampler(sampler),
434 },
435 ],
436 label: Some(&format!("{}_bind_group", label)),
437 });
438 (tex, view, bg)
439 };
440
441 let (hdr_t, hdr_v, hdr_bg) = make("HDR Texture");
442 let (_be_t, be_v, be_bg) = make("Bloom Extract Texture");
443 let (_bb_t, bb_v, bb_bg) = make("Bloom Blur Texture");
444
445 #[cfg(not(target_arch = "wasm32"))]
446 let cb_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
447 layout: composite_bloom_bgl,
448 entries: &[
449 wgpu::BindGroupEntry {
450 binding: 0,
451 resource: wgpu::BindingResource::TextureView(&be_v),
452 },
453 wgpu::BindGroupEntry {
454 binding: 1,
455 resource: wgpu::BindingResource::Sampler(sampler),
456 },
457 wgpu::BindGroupEntry {
458 binding: 2,
459 resource: wgpu::BindingResource::TextureView(depth_view),
460 },
461 ],
462 label: Some("composite_bloom_bind_group"),
463 });
464 #[cfg(target_arch = "wasm32")]
465 let cb_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
466 layout: composite_bloom_bgl,
467 entries: &[
468 wgpu::BindGroupEntry {
469 binding: 0,
470 resource: wgpu::BindingResource::TextureView(&be_v),
471 },
472 wgpu::BindGroupEntry {
473 binding: 1,
474 resource: wgpu::BindingResource::Sampler(sampler),
475 },
476 ],
477 label: Some("composite_bloom_bind_group"),
478 });
479
480 (hdr_t, hdr_v, hdr_bg, be_v, be_bg, bb_v, bb_bg, cb_bg)
481}
482
483pub fn create_blur_buffers(
484 device: &wgpu::Device,
485 bgl: &wgpu::BindGroupLayout,
486 width: u32,
487 height: u32,
488) -> (wgpu::Buffer, wgpu::BindGroup, wgpu::BindGroup) {
489 let h_data: [f32; 4] = [1.0 / width as f32, 0.0, 0.0, 0.0];
490 let v_data: [f32; 4] = [0.0, 1.0 / height as f32, 0.0, 0.0];
491
492 let h_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
493 label: Some("Blur H Params"),
494 contents: bytemuck::cast_slice(&h_data),
495 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
496 });
497 let v_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
498 label: Some("Blur V Params"),
499 contents: bytemuck::cast_slice(&v_data),
500 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
501 });
502 let h_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
503 layout: bgl,
504 entries: &[wgpu::BindGroupEntry {
505 binding: 0,
506 resource: h_buf.as_entire_binding(),
507 }],
508 label: Some("blur_h_bind_group"),
509 });
510 let v_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
511 layout: bgl,
512 entries: &[wgpu::BindGroupEntry {
513 binding: 0,
514 resource: v_buf.as_entire_binding(),
515 }],
516 label: Some("blur_v_bind_group"),
517 });
518 (h_buf, h_bg, v_bg)
519}
520
521pub fn run_post_processing(
523 renderer: &crate::Renderer,
524 encoder: &mut wgpu::CommandEncoder,
525 output_view: &wgpu::TextureView,
526) {
527 #[cfg(not(target_arch = "wasm32"))]
529 {
530 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
531 label: Some("Bloom Extract Pass"),
532 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
533 view: &renderer.post.bloom_extract_texture_view,
534 resolve_target: None,
535 ops: wgpu::Operations {
536 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
537 store: wgpu::StoreOp::Store,
538 },
539 })],
540 depth_stencil_attachment: None,
541 ..Default::default()
542 });
543 pass.set_pipeline(&renderer.post.bloom_extract_pipeline);
544 pass.set_bind_group(0, &renderer.post.hdr_bind_group, &[]);
545 pass.set_bind_group(1, &renderer.post.blur_h_bind_group, &[]);
546 pass.set_bind_group(2, &renderer.post.post_params_bind_group, &[]);
547 pass.draw(0..3, 0..1);
548 }
549 #[cfg(not(target_arch = "wasm32"))]
551 {
552 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
553 label: Some("Bloom Blur Horizontal"),
554 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
555 view: &renderer.post.bloom_blur_texture_view,
556 resolve_target: None,
557 ops: wgpu::Operations {
558 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
559 store: wgpu::StoreOp::Store,
560 },
561 })],
562 depth_stencil_attachment: None,
563 ..Default::default()
564 });
565 pass.set_pipeline(&renderer.post.bloom_blur_pipeline);
566 pass.set_bind_group(0, &renderer.post.bloom_extract_bind_group, &[]);
567 pass.set_bind_group(1, &renderer.post.blur_h_bind_group, &[]);
568 pass.draw(0..3, 0..1);
569 }
570 #[cfg(not(target_arch = "wasm32"))]
572 {
573 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
574 label: Some("Bloom Blur Vertical"),
575 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
576 view: &renderer.post.bloom_extract_texture_view,
577 resolve_target: None,
578 ops: wgpu::Operations {
579 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
580 store: wgpu::StoreOp::Store,
581 },
582 })],
583 depth_stencil_attachment: None,
584 ..Default::default()
585 });
586 pass.set_pipeline(&renderer.post.bloom_blur_pipeline);
587 pass.set_bind_group(0, &renderer.post.bloom_blur_bind_group, &[]);
588 pass.set_bind_group(1, &renderer.post.blur_v_bind_group, &[]);
589 pass.draw(0..3, 0..1);
590 }
591 {
593 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
594 label: Some("Composite + Tone Mapping Pass"),
595 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
596 view: output_view,
597 resolve_target: None,
598 ops: wgpu::Operations {
599 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
600 store: wgpu::StoreOp::Store,
601 },
602 })],
603 depth_stencil_attachment: None,
604 ..Default::default()
605 });
606 pass.set_pipeline(&renderer.post.composite_pipeline);
607 pass.set_bind_group(0, &renderer.post.hdr_bind_group, &[]);
608 pass.set_bind_group(1, &renderer.post.composite_bloom_bind_group, &[]);
609 pass.set_bind_group(2, &renderer.post.post_params_bind_group, &[]);
610 pass.draw(0..3, 0..1);
611 }
612}