Skip to main content

gizmo_renderer/
post_process.rs

1use crate::gpu_types::PostProcessUniforms;
2use wgpu::util::DeviceExt;
3
4/// Post-Processing durumu — HDR, Bloom, Blur, Composite pipeline'ları ve kaynakları
5pub 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    // WASM: depth texture'ı composite layout'tan kaldır (textureLoad kullanılıyor, sampler gerekmez)
88    // Native: depth texture sampler ile kalır
89    #[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,       // Daha belirgin ve hacimli parlama
150            bloom_threshold: 0.85,      // Daha çok highlight yakalamak için eşik düşürüldü
151            exposure: 1.15, // ACES Tone mapping'in renkleri daha canlı sunması için pozlama artırıldı
152            chromatic_aberration: 0.0, // Dövüş oyununda rahatsız edici ghosting yapmaması için kapatıldı
153            vignette_intensity: 0.25,  // Köşelerde dramatik kararma (Vignette)
154            film_grain_intensity: 0.03, // Film greni (Realistic noise)
155            dof_focus_dist: 15.0,
156            dof_focus_range: 25.0,
157            dof_blur_size: 0.0, // Disable focus depth blur to prevent thin gizmo lines from washing out
158            _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
521/// Post-processing render geçişlerini sırayla çalıştırır.
522pub fn run_post_processing(
523    renderer: &crate::Renderer,
524    encoder: &mut wgpu::CommandEncoder,
525    output_view: &wgpu::TextureView,
526) {
527    // Pass 1: Bright Extract (WASM'da KAPALI)
528    #[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    // Pass 2a: Yatay Blur (WASM'da KAPALI)
550    #[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    // Pass 2b: Dikey Blur (ping-pong) (WASM'da KAPALI)
571    #[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    // Pass 3: Composite + Tone Mapping
592    {
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}