1use dreamwell_engine::material::TonemapOperator;
7
8use crate::formats::DEPTH_FORMAT;
9use crate::shader;
10
11pub const HDR_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba16Float;
12
13const BLOOM_SHADER_SRC: &str = include_str!("../shaders/bloom.wgsl");
14const TONEMAP_SHADER_SRC: &str = include_str!("../shaders/tonemap.wgsl");
15
16#[repr(C)]
21#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
22struct BloomUniforms {
23 threshold: f32,
24 intensity: f32,
25 texel_size: [f32; 2],
26}
27
28#[repr(C)]
29#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
30struct TonemapUniforms {
31 exposure: f32,
32 bloom_intensity: f32,
33 tonemap_operator: u32,
34 dof_enabled: u32,
35 fog_enabled: u32,
36 _pad_align: [f32; 3], _pad: [f32; 3], _pad_tail: f32, }
42#[derive(Debug, Clone)]
50pub struct PostProcessConfig {
51 pub bloom_enabled: bool,
52 pub bloom_threshold: f32,
53 pub bloom_intensity: f32,
54 pub tonemap_enabled: bool,
55 pub tonemap_operator: TonemapOperator,
56 pub exposure: f32,
57}
58
59impl Default for PostProcessConfig {
60 fn default() -> Self {
61 Self {
62 bloom_enabled: false,
63 bloom_threshold: 1.0,
64 bloom_intensity: 0.5,
65 tonemap_enabled: false,
66 tonemap_operator: TonemapOperator::AcesFilmic,
67 exposure: 1.0,
68 }
69 }
70}
71
72impl PostProcessConfig {
73 pub fn for_pbr() -> Self {
76 Self {
77 bloom_enabled: true,
78 bloom_threshold: 1.0,
79 bloom_intensity: 0.04,
80 tonemap_enabled: true,
81 tonemap_operator: TonemapOperator::AcesFilmic,
82 exposure: 1.0,
83 }
84 }
85
86 pub fn for_demo() -> Self {
89 Self {
90 bloom_enabled: true,
91 bloom_threshold: 1.5,
92 bloom_intensity: 0.02,
93 tonemap_enabled: true,
94 tonemap_operator: TonemapOperator::AcesFilmic,
95 exposure: 0.8,
96 }
97 }
98}
99
100struct BloomMip {
105 _texture: wgpu::Texture,
107 view: wgpu::TextureView,
108 width: u32,
109 height: u32,
110}
111
112pub struct PostProcessStack {
118 config: PostProcessConfig,
119 hdr_texture: Option<wgpu::Texture>,
121 hdr_view: Option<wgpu::TextureView>,
122 hdr_depth_texture: Option<wgpu::Texture>,
123 hdr_depth_view: Option<wgpu::TextureView>,
124 width: u32,
125 height: u32,
126 bloom_mips: [Option<BloomMip>; 2],
128 bloom_downsample_pipeline: Option<wgpu::RenderPipeline>,
129 bloom_upsample_pipeline: Option<wgpu::RenderPipeline>,
130 bloom_bgl: Option<wgpu::BindGroupLayout>,
131 bloom_uniform_buf: Option<wgpu::Buffer>,
132 tonemap_pipeline: Option<wgpu::RenderPipeline>,
134 tonemap_bgl: Option<wgpu::BindGroupLayout>,
135 tonemap_uniform_buf: Option<wgpu::Buffer>,
136 sampler: Option<wgpu::Sampler>,
138 black_texture: Option<(wgpu::Texture, wgpu::TextureView)>,
139 surface_format: wgpu::TextureFormat,
140 initialized: bool,
141 cached_tonemap_bg: Option<wgpu::BindGroup>,
143 cached_bloom_bgs: [Option<wgpu::BindGroup>; 3],
144}
145
146impl PostProcessStack {
147 pub fn new(surface_format: wgpu::TextureFormat) -> Self {
149 Self {
150 config: PostProcessConfig::default(),
151 hdr_texture: None,
152 hdr_view: None,
153 hdr_depth_texture: None,
154 hdr_depth_view: None,
155 width: 0,
156 height: 0,
157 bloom_mips: [None, None],
158 bloom_downsample_pipeline: None,
159 bloom_upsample_pipeline: None,
160 bloom_bgl: None,
161 bloom_uniform_buf: None,
162 tonemap_pipeline: None,
163 tonemap_bgl: None,
164 tonemap_uniform_buf: None,
165 sampler: None,
166 black_texture: None,
167 surface_format,
168 initialized: false,
169 cached_tonemap_bg: None,
170 cached_bloom_bgs: [None, None, None],
171 }
172 }
173
174 pub fn config(&self) -> &PostProcessConfig {
176 &self.config
177 }
178
179 pub fn set_config(&mut self, config: PostProcessConfig) {
181 self.config = config;
182 }
183
184 pub fn is_enabled(&self) -> bool {
186 self.config.bloom_enabled || self.config.tonemap_enabled
187 }
188
189 pub fn resize(&mut self, device: &wgpu::Device, width: u32, height: u32) {
195 if width == 0 || height == 0 {
196 return;
197 }
198 if self.initialized && self.width == width && self.height == height {
199 return;
200 }
201
202 self.cached_tonemap_bg = None;
204 self.cached_bloom_bgs = [None, None, None];
205
206 self.hdr_texture = None;
208 self.hdr_view = None;
209 self.hdr_depth_texture = None;
210 self.hdr_depth_view = None;
211 self.bloom_mips = [None, None];
212
213 self.width = width;
214 self.height = height;
215
216 let hdr_tex = device.create_texture(&wgpu::TextureDescriptor {
218 label: Some("post_hdr_color"),
219 size: wgpu::Extent3d {
220 width,
221 height,
222 depth_or_array_layers: 1,
223 },
224 mip_level_count: 1,
225 sample_count: 1,
226 dimension: wgpu::TextureDimension::D2,
227 format: HDR_FORMAT,
228 usage: wgpu::TextureUsages::RENDER_ATTACHMENT
230 | wgpu::TextureUsages::TEXTURE_BINDING
231 | wgpu::TextureUsages::COPY_DST,
232 view_formats: &[],
233 });
234 self.hdr_view = Some(hdr_tex.create_view(&wgpu::TextureViewDescriptor::default()));
235 self.hdr_texture = Some(hdr_tex);
236
237 let depth_tex = device.create_texture(&wgpu::TextureDescriptor {
246 label: Some("post_hdr_depth"),
247 size: wgpu::Extent3d {
248 width,
249 height,
250 depth_or_array_layers: 1,
251 },
252 mip_level_count: 1,
253 sample_count: 1,
254 dimension: wgpu::TextureDimension::D2,
255 format: DEPTH_FORMAT,
256 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
257 view_formats: &[],
258 });
259 self.hdr_depth_view = Some(depth_tex.create_view(&wgpu::TextureViewDescriptor::default()));
260 self.hdr_depth_texture = Some(depth_tex);
261
262 let mip_sizes = [
265 ((width / 2).max(1), (height / 2).max(1)),
266 ((width / 4).max(1), (height / 4).max(1)),
267 ];
268 for (i, &(mw, mh)) in mip_sizes.iter().enumerate() {
269 let tex = device.create_texture(&wgpu::TextureDescriptor {
270 label: Some(&format!("post_bloom_mip{i}")),
271 size: wgpu::Extent3d {
272 width: mw,
273 height: mh,
274 depth_or_array_layers: 1,
275 },
276 mip_level_count: 1,
277 sample_count: 1,
278 dimension: wgpu::TextureDimension::D2,
279 format: HDR_FORMAT,
280 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
281 view_formats: &[],
282 });
283 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
284 self.bloom_mips[i] = Some(BloomMip {
285 _texture: tex,
286 view,
287 width: mw,
288 height: mh,
289 });
290 }
291
292 if self.sampler.is_none() {
294 self.sampler = Some(device.create_sampler(&wgpu::SamplerDescriptor {
295 label: Some("post_sampler"),
296 address_mode_u: wgpu::AddressMode::ClampToEdge,
297 address_mode_v: wgpu::AddressMode::ClampToEdge,
298 address_mode_w: wgpu::AddressMode::ClampToEdge,
299 mag_filter: wgpu::FilterMode::Linear,
300 min_filter: wgpu::FilterMode::Linear,
301 mipmap_filter: wgpu::MipmapFilterMode::Linear,
302 ..Default::default()
303 }));
304 }
305
306 if self.black_texture.is_none() {
308 let tex = device.create_texture(&wgpu::TextureDescriptor {
309 label: Some("post_black_1x1"),
310 size: wgpu::Extent3d {
311 width: 1,
312 height: 1,
313 depth_or_array_layers: 1,
314 },
315 mip_level_count: 1,
316 sample_count: 1,
317 dimension: wgpu::TextureDimension::D2,
318 format: HDR_FORMAT,
319 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
320 view_formats: &[],
321 });
322 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
323 self.black_texture = Some((tex, view));
324 }
325
326 self.initialized = true;
327 }
328
329 pub fn ensure_pipelines(&mut self, device: &wgpu::Device) {
335 if self.bloom_downsample_pipeline.is_some() {
336 return;
337 }
338
339 let bloom_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
341 label: Some("post_bloom_bgl"),
342 entries: &[
343 wgpu::BindGroupLayoutEntry {
344 binding: 0,
345 visibility: wgpu::ShaderStages::FRAGMENT,
346 ty: wgpu::BindingType::Texture {
347 sample_type: wgpu::TextureSampleType::Float { filterable: true },
348 view_dimension: wgpu::TextureViewDimension::D2,
349 multisampled: false,
350 },
351 count: None,
352 },
353 wgpu::BindGroupLayoutEntry {
354 binding: 1,
355 visibility: wgpu::ShaderStages::FRAGMENT,
356 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
357 count: None,
358 },
359 wgpu::BindGroupLayoutEntry {
360 binding: 2,
361 visibility: wgpu::ShaderStages::FRAGMENT,
362 ty: wgpu::BindingType::Buffer {
363 ty: wgpu::BufferBindingType::Uniform,
364 has_dynamic_offset: false,
365 min_binding_size: None,
366 },
367 count: None,
368 },
369 ],
370 });
371
372 let bloom_shader = shader::load_wgsl(device, "bloom", BLOOM_SHADER_SRC).0;
373
374 let bloom_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
375 label: Some("post_bloom_pipeline_layout"),
376 bind_group_layouts: &[Some(&bloom_bgl)],
377 immediate_size: 0,
378 });
379
380 self.bloom_downsample_pipeline = Some(device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
382 label: Some("post_bloom_downsample"),
383 layout: Some(&bloom_pipeline_layout),
384 vertex: wgpu::VertexState {
385 module: &bloom_shader,
386 entry_point: Some("vs_main"),
387 buffers: &[],
388 compilation_options: Default::default(),
389 },
390 fragment: Some(wgpu::FragmentState {
391 module: &bloom_shader,
392 entry_point: Some("fs_downsample"),
393 targets: &[Some(wgpu::ColorTargetState {
394 format: HDR_FORMAT,
395 blend: None,
396 write_mask: wgpu::ColorWrites::ALL,
397 })],
398 compilation_options: Default::default(),
399 }),
400 primitive: wgpu::PrimitiveState {
401 topology: wgpu::PrimitiveTopology::TriangleList,
402 ..Default::default()
403 },
404 depth_stencil: None,
405 multisample: wgpu::MultisampleState::default(),
406 multiview_mask: None,
407 cache: None,
408 }));
409
410 self.bloom_upsample_pipeline = Some(device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
412 label: Some("post_bloom_upsample"),
413 layout: Some(&bloom_pipeline_layout),
414 vertex: wgpu::VertexState {
415 module: &bloom_shader,
416 entry_point: Some("vs_main"),
417 buffers: &[],
418 compilation_options: Default::default(),
419 },
420 fragment: Some(wgpu::FragmentState {
421 module: &bloom_shader,
422 entry_point: Some("fs_upsample"),
423 targets: &[Some(wgpu::ColorTargetState {
424 format: HDR_FORMAT,
425 blend: Some(wgpu::BlendState {
426 color: wgpu::BlendComponent {
427 src_factor: wgpu::BlendFactor::One,
428 dst_factor: wgpu::BlendFactor::One,
429 operation: wgpu::BlendOperation::Add,
430 },
431 alpha: wgpu::BlendComponent {
432 src_factor: wgpu::BlendFactor::One,
433 dst_factor: wgpu::BlendFactor::One,
434 operation: wgpu::BlendOperation::Add,
435 },
436 }),
437 write_mask: wgpu::ColorWrites::ALL,
438 })],
439 compilation_options: Default::default(),
440 }),
441 primitive: wgpu::PrimitiveState {
442 topology: wgpu::PrimitiveTopology::TriangleList,
443 ..Default::default()
444 },
445 depth_stencil: None,
446 multisample: wgpu::MultisampleState::default(),
447 multiview_mask: None,
448 cache: None,
449 }));
450
451 self.bloom_uniform_buf = Some(shader::create_uniform_buffer(
453 device,
454 "post_bloom_uniforms",
455 &BloomUniforms {
456 threshold: self.config.bloom_threshold,
457 intensity: self.config.bloom_intensity,
458 texel_size: [0.0, 0.0],
459 },
460 ));
461
462 self.bloom_bgl = Some(bloom_bgl);
463
464 let tonemap_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
466 label: Some("post_tonemap_bgl"),
467 entries: &[
468 wgpu::BindGroupLayoutEntry {
469 binding: 0,
470 visibility: wgpu::ShaderStages::FRAGMENT,
471 ty: wgpu::BindingType::Texture {
472 sample_type: wgpu::TextureSampleType::Float { filterable: true },
473 view_dimension: wgpu::TextureViewDimension::D2,
474 multisampled: false,
475 },
476 count: None,
477 },
478 wgpu::BindGroupLayoutEntry {
479 binding: 1,
480 visibility: wgpu::ShaderStages::FRAGMENT,
481 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
482 count: None,
483 },
484 wgpu::BindGroupLayoutEntry {
485 binding: 2,
486 visibility: wgpu::ShaderStages::FRAGMENT,
487 ty: wgpu::BindingType::Buffer {
488 ty: wgpu::BufferBindingType::Uniform,
489 has_dynamic_offset: false,
490 min_binding_size: None,
491 },
492 count: None,
493 },
494 wgpu::BindGroupLayoutEntry {
495 binding: 3,
496 visibility: wgpu::ShaderStages::FRAGMENT,
497 ty: wgpu::BindingType::Texture {
498 sample_type: wgpu::TextureSampleType::Float { filterable: true },
499 view_dimension: wgpu::TextureViewDimension::D2,
500 multisampled: false,
501 },
502 count: None,
503 },
504 wgpu::BindGroupLayoutEntry {
506 binding: 4,
507 visibility: wgpu::ShaderStages::FRAGMENT,
508 ty: wgpu::BindingType::Texture {
509 sample_type: wgpu::TextureSampleType::Float { filterable: true },
510 view_dimension: wgpu::TextureViewDimension::D2,
511 multisampled: false,
512 },
513 count: None,
514 },
515 wgpu::BindGroupLayoutEntry {
517 binding: 5,
518 visibility: wgpu::ShaderStages::FRAGMENT,
519 ty: wgpu::BindingType::Texture {
520 sample_type: wgpu::TextureSampleType::Float { filterable: true },
521 view_dimension: wgpu::TextureViewDimension::D2,
522 multisampled: false,
523 },
524 count: None,
525 },
526 ],
527 });
528
529 let tonemap_shader = shader::load_wgsl(device, "tonemap", TONEMAP_SHADER_SRC).0;
530
531 let tonemap_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
532 label: Some("post_tonemap_pipeline_layout"),
533 bind_group_layouts: &[Some(&tonemap_bgl)],
534 immediate_size: 0,
535 });
536
537 self.tonemap_pipeline = Some(device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
538 label: Some("post_tonemap"),
539 layout: Some(&tonemap_pipeline_layout),
540 vertex: wgpu::VertexState {
541 module: &tonemap_shader,
542 entry_point: Some("vs_main"),
543 buffers: &[],
544 compilation_options: Default::default(),
545 },
546 fragment: Some(wgpu::FragmentState {
547 module: &tonemap_shader,
548 entry_point: Some("fs_main"),
549 targets: &[Some(wgpu::ColorTargetState {
550 format: self.surface_format,
551 blend: None,
552 write_mask: wgpu::ColorWrites::ALL,
553 })],
554 compilation_options: Default::default(),
555 }),
556 primitive: wgpu::PrimitiveState {
557 topology: wgpu::PrimitiveTopology::TriangleList,
558 ..Default::default()
559 },
560 depth_stencil: None,
561 multisample: wgpu::MultisampleState::default(),
562 multiview_mask: None,
563 cache: None,
564 }));
565
566 self.tonemap_uniform_buf = Some(shader::create_uniform_buffer(
568 device,
569 "post_tonemap_uniforms",
570 &TonemapUniforms {
571 exposure: self.config.exposure,
572 bloom_intensity: self.config.bloom_intensity,
573 tonemap_operator: tonemap_operator_to_u32(self.config.tonemap_operator),
574 dof_enabled: 0,
575 fog_enabled: 0,
576 _pad_align: [0.0; 3],
577 _pad: [0.0; 3],
578 _pad_tail: 0.0,
579 },
580 ));
581
582 self.tonemap_bgl = Some(tonemap_bgl);
583 }
584
585 pub fn hdr_view(&self) -> Option<&wgpu::TextureView> {
591 self.hdr_view.as_ref()
592 }
593
594 pub fn hdr_texture(&self) -> Option<&wgpu::Texture> {
596 self.hdr_texture.as_ref()
597 }
598
599 pub fn hdr_depth_view(&self) -> Option<&wgpu::TextureView> {
601 self.hdr_depth_view.as_ref()
602 }
603
604 pub fn run_bloom(&self, device: &wgpu::Device, queue: &wgpu::Queue, encoder: &mut wgpu::CommandEncoder) {
612 let (Some(bloom_bgl), Some(sampler), Some(bloom_buf)) =
613 (&self.bloom_bgl, &self.sampler, &self.bloom_uniform_buf)
614 else {
615 return;
616 };
617 let (Some(downsample_pipe), Some(upsample_pipe)) =
618 (&self.bloom_downsample_pipeline, &self.bloom_upsample_pipeline)
619 else {
620 return;
621 };
622 let (Some(hdr_view), Some(mip0), Some(mip1)) = (&self.hdr_view, &self.bloom_mips[0], &self.bloom_mips[1])
623 else {
624 return;
625 };
626
627 let texel_size_0 = [1.0 / mip0.width as f32, 1.0 / mip0.height as f32];
629 shader::update_uniform_buffer(
630 queue,
631 bloom_buf,
632 &BloomUniforms {
633 threshold: self.config.bloom_threshold,
634 intensity: self.config.bloom_intensity,
635 texel_size: texel_size_0,
636 },
637 );
638
639 let bg_down0 = create_bloom_bind_group(device, bloom_bgl, hdr_view, sampler, bloom_buf);
640 {
641 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
642 label: Some("bloom_downsample_0"),
643 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
644 view: &mip0.view,
645 resolve_target: None,
646 ops: wgpu::Operations {
647 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
648 store: wgpu::StoreOp::Store,
649 },
650 depth_slice: None,
651 })],
652 depth_stencil_attachment: None,
653 timestamp_writes: None,
654 occlusion_query_set: None,
655 multiview_mask: None,
656 });
657 pass.set_pipeline(downsample_pipe);
658 pass.set_bind_group(0, &bg_down0, &[]);
659 pass.draw(0..3, 0..1);
660 }
661
662 let texel_size_1 = [1.0 / mip1.width as f32, 1.0 / mip1.height as f32];
664 shader::update_uniform_buffer(
665 queue,
666 bloom_buf,
667 &BloomUniforms {
668 threshold: 0.0, intensity: self.config.bloom_intensity,
670 texel_size: texel_size_1,
671 },
672 );
673
674 let bg_down1 = create_bloom_bind_group(device, bloom_bgl, &mip0.view, sampler, bloom_buf);
675 {
676 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
677 label: Some("bloom_downsample_1"),
678 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
679 view: &mip1.view,
680 resolve_target: None,
681 ops: wgpu::Operations {
682 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
683 store: wgpu::StoreOp::Store,
684 },
685 depth_slice: None,
686 })],
687 depth_stencil_attachment: None,
688 timestamp_writes: None,
689 occlusion_query_set: None,
690 multiview_mask: None,
691 });
692 pass.set_pipeline(downsample_pipe);
693 pass.set_bind_group(0, &bg_down1, &[]);
694 pass.draw(0..3, 0..1);
695 }
696
697 shader::update_uniform_buffer(
699 queue,
700 bloom_buf,
701 &BloomUniforms {
702 threshold: 0.0,
703 intensity: self.config.bloom_intensity,
704 texel_size: texel_size_0,
705 },
706 );
707
708 let bg_up = create_bloom_bind_group(device, bloom_bgl, &mip1.view, sampler, bloom_buf);
709 {
710 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
711 label: Some("bloom_upsample"),
712 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
713 view: &mip0.view,
714 resolve_target: None,
715 ops: wgpu::Operations {
716 load: wgpu::LoadOp::Load,
717 store: wgpu::StoreOp::Store,
718 },
719 depth_slice: None,
720 })],
721 depth_stencil_attachment: None,
722 timestamp_writes: None,
723 occlusion_query_set: None,
724 multiview_mask: None,
725 });
726 pass.set_pipeline(upsample_pipe);
727 pass.set_bind_group(0, &bg_up, &[]);
728 pass.draw(0..3, 0..1);
729 }
730 }
731
732 pub fn run_tonemap(
740 &self,
741 device: &wgpu::Device,
742 queue: &wgpu::Queue,
743 encoder: &mut wgpu::CommandEncoder,
744 surface_view: &wgpu::TextureView,
745 ) {
746 let (Some(tonemap_bgl), Some(sampler), Some(tonemap_buf)) =
747 (&self.tonemap_bgl, &self.sampler, &self.tonemap_uniform_buf)
748 else {
749 return;
750 };
751 let (Some(tonemap_pipe), Some(hdr_view)) = (&self.tonemap_pipeline, &self.hdr_view) else {
752 return;
753 };
754
755 shader::update_uniform_buffer(
757 queue,
758 tonemap_buf,
759 &TonemapUniforms {
760 exposure: self.config.exposure,
761 bloom_intensity: if self.config.bloom_enabled {
762 self.config.bloom_intensity
763 } else {
764 0.0
765 },
766 tonemap_operator: tonemap_operator_to_u32(self.config.tonemap_operator),
767 dof_enabled: 0, fog_enabled: 0, _pad_align: [0.0; 3],
770 _pad: [0.0; 3],
771 _pad_tail: 0.0,
772 },
773 );
774
775 let bloom_view = if self.config.bloom_enabled {
777 self.bloom_mips[0].as_ref().map(|m| &m.view)
778 } else {
779 None
780 };
781 let bloom_view = bloom_view
782 .or_else(|| self.black_texture.as_ref().map(|(_, v)| v))
783 .expect("black_texture should be initialized after resize");
784
785 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
786 label: Some("post_tonemap_bg"),
787 layout: tonemap_bgl,
788 entries: &[
789 wgpu::BindGroupEntry {
790 binding: 0,
791 resource: wgpu::BindingResource::TextureView(hdr_view),
792 },
793 wgpu::BindGroupEntry {
794 binding: 1,
795 resource: wgpu::BindingResource::Sampler(sampler),
796 },
797 wgpu::BindGroupEntry {
798 binding: 2,
799 resource: tonemap_buf.as_entire_binding(),
800 },
801 wgpu::BindGroupEntry {
802 binding: 3,
803 resource: wgpu::BindingResource::TextureView(bloom_view),
804 },
805 wgpu::BindGroupEntry {
807 binding: 4,
808 resource: wgpu::BindingResource::TextureView(
809 self.black_texture
810 .as_ref()
811 .map(|(_, v)| v)
812 .expect("black_texture should be initialized after resize"),
813 ),
814 },
815 wgpu::BindGroupEntry {
817 binding: 5,
818 resource: wgpu::BindingResource::TextureView(
819 self.black_texture
820 .as_ref()
821 .map(|(_, v)| v)
822 .expect("black_texture should be initialized after resize"),
823 ),
824 },
825 ],
826 });
827
828 {
829 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
830 label: Some("tonemap"),
831 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
832 view: surface_view,
833 resolve_target: None,
834 ops: wgpu::Operations {
835 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
836 store: wgpu::StoreOp::Store,
837 },
838 depth_slice: None,
839 })],
840 depth_stencil_attachment: None,
841 timestamp_writes: None,
842 occlusion_query_set: None,
843 multiview_mask: None,
844 });
845 pass.set_pipeline(tonemap_pipe);
846 pass.set_bind_group(0, &bind_group, &[]);
847 pass.draw(0..3, 0..1);
848 }
849 }
850
851 pub fn destroy(&mut self) {
857 self.hdr_texture = None;
858 self.hdr_view = None;
859 self.hdr_depth_texture = None;
860 self.hdr_depth_view = None;
861 self.bloom_mips = [None, None];
862 self.bloom_downsample_pipeline = None;
863 self.bloom_upsample_pipeline = None;
864 self.bloom_bgl = None;
865 self.bloom_uniform_buf = None;
866 self.tonemap_pipeline = None;
867 self.tonemap_bgl = None;
868 self.tonemap_uniform_buf = None;
869 self.sampler = None;
870 self.black_texture = None;
871 self.initialized = false;
872 self.width = 0;
873 self.height = 0;
874 }
875}
876
877fn create_bloom_bind_group(
882 device: &wgpu::Device,
883 layout: &wgpu::BindGroupLayout,
884 src_view: &wgpu::TextureView,
885 sampler: &wgpu::Sampler,
886 uniform_buf: &wgpu::Buffer,
887) -> wgpu::BindGroup {
888 device.create_bind_group(&wgpu::BindGroupDescriptor {
889 label: Some("post_bloom_bg"),
890 layout,
891 entries: &[
892 wgpu::BindGroupEntry {
893 binding: 0,
894 resource: wgpu::BindingResource::TextureView(src_view),
895 },
896 wgpu::BindGroupEntry {
897 binding: 1,
898 resource: wgpu::BindingResource::Sampler(sampler),
899 },
900 wgpu::BindGroupEntry {
901 binding: 2,
902 resource: uniform_buf.as_entire_binding(),
903 },
904 ],
905 })
906}
907
908fn tonemap_operator_to_u32(op: TonemapOperator) -> u32 {
909 match op {
910 TonemapOperator::AcesFilmic => 0,
911 TonemapOperator::Uncharted2 => 1,
912 TonemapOperator::Reinhard => 2,
913 TonemapOperator::None => 3,
914 }
915}
916
917#[cfg(test)]
922mod tests {
923 use super::*;
924
925 #[test]
926 fn default_config_disabled() {
927 let config = PostProcessConfig::default();
928 assert!(!config.bloom_enabled);
929 assert!(!config.tonemap_enabled);
930 assert_eq!(config.bloom_threshold, 1.0);
931 assert_eq!(config.bloom_intensity, 0.5);
932 assert_eq!(config.exposure, 1.0);
933 }
934
935 #[test]
936 fn config_with_bloom_enabled() {
937 let mut stack = PostProcessStack::new(wgpu::TextureFormat::Bgra8UnormSrgb);
938 stack.set_config(PostProcessConfig {
939 bloom_enabled: true,
940 ..Default::default()
941 });
942 assert!(stack.is_enabled());
943 }
944
945 #[test]
946 fn config_with_tonemap_enabled() {
947 let mut stack = PostProcessStack::new(wgpu::TextureFormat::Bgra8UnormSrgb);
948 stack.set_config(PostProcessConfig {
949 tonemap_enabled: true,
950 ..Default::default()
951 });
952 assert!(stack.is_enabled());
953 }
954
955 #[test]
956 fn hdr_format_is_rgba16float() {
957 assert_eq!(HDR_FORMAT, wgpu::TextureFormat::Rgba16Float);
958 }
959
960 #[test]
961 fn for_pbr_config() {
962 let config = PostProcessConfig::for_pbr();
963 assert!(config.bloom_enabled);
964 assert!(config.tonemap_enabled);
965 assert_eq!(config.bloom_threshold, 1.0);
966 assert_eq!(config.bloom_intensity, 0.04);
967 assert_eq!(config.exposure, 1.0);
968 }
969
970 #[test]
971 fn new_not_initialized() {
972 let stack = PostProcessStack::new(wgpu::TextureFormat::Bgra8UnormSrgb);
973 assert!(!stack.initialized);
974 assert!(!stack.is_enabled());
975 assert!(stack.hdr_view().is_none());
976 assert!(stack.hdr_depth_view().is_none());
977 }
978}