1use std::num::NonZeroU64;
4use wgpu::util::DeviceExt;
5
6#[repr(C)]
8#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
9pub struct ToneMapUniforms {
10 pub exposure: f32,
11 pub white_level: f32,
12 pub gamma: f32,
13 pub ssao_enabled: u32, #[allow(clippy::pub_underscore_fields)]
16 pub _padding: [f32; 4],
17}
18
19impl Default for ToneMapUniforms {
20 fn default() -> Self {
21 Self {
22 exposure: 1.0,
23 white_level: 1.0,
24 gamma: 2.2,
25 ssao_enabled: 0,
26 _padding: [0.0; 4],
27 }
28 }
29}
30
31pub struct ToneMapPass {
33 pipeline: wgpu::RenderPipeline,
34 bind_group_layout: wgpu::BindGroupLayout,
35 uniform_buffer: wgpu::Buffer,
36 sampler: wgpu::Sampler,
37}
38
39impl ToneMapPass {
40 #[must_use]
42 pub fn new(device: &wgpu::Device, output_format: wgpu::TextureFormat) -> Self {
43 let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
45 label: Some("Tone Map Bind Group Layout"),
46 entries: &[
47 wgpu::BindGroupLayoutEntry {
49 binding: 0,
50 visibility: wgpu::ShaderStages::FRAGMENT,
51 ty: wgpu::BindingType::Texture {
52 sample_type: wgpu::TextureSampleType::Float { filterable: true },
53 view_dimension: wgpu::TextureViewDimension::D2,
54 multisampled: false,
55 },
56 count: None,
57 },
58 wgpu::BindGroupLayoutEntry {
60 binding: 1,
61 visibility: wgpu::ShaderStages::FRAGMENT,
62 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
63 count: None,
64 },
65 wgpu::BindGroupLayoutEntry {
67 binding: 2,
68 visibility: wgpu::ShaderStages::FRAGMENT,
69 ty: wgpu::BindingType::Buffer {
70 ty: wgpu::BufferBindingType::Uniform,
71 has_dynamic_offset: false,
72 min_binding_size: NonZeroU64::new(32),
73 },
74 count: None,
75 },
76 wgpu::BindGroupLayoutEntry {
78 binding: 3,
79 visibility: wgpu::ShaderStages::FRAGMENT,
80 ty: wgpu::BindingType::Texture {
81 sample_type: wgpu::TextureSampleType::Float { filterable: true },
82 view_dimension: wgpu::TextureViewDimension::D2,
83 multisampled: false,
84 },
85 count: None,
86 },
87 ],
88 });
89
90 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
92 label: Some("Tone Map Shader"),
93 source: wgpu::ShaderSource::Wgsl(include_str!("shaders/tone_map.wgsl").into()),
94 });
95
96 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
98 label: Some("Tone Map Pipeline Layout"),
99 bind_group_layouts: &[&bind_group_layout],
100 push_constant_ranges: &[],
101 });
102
103 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
105 label: Some("Tone Map Pipeline"),
106 layout: Some(&pipeline_layout),
107 vertex: wgpu::VertexState {
108 module: &shader,
109 entry_point: Some("vs_main"),
110 buffers: &[],
111 compilation_options: wgpu::PipelineCompilationOptions::default(),
112 },
113 fragment: Some(wgpu::FragmentState {
114 module: &shader,
115 entry_point: Some("fs_main"),
116 targets: &[Some(wgpu::ColorTargetState {
117 format: output_format,
118 blend: None,
119 write_mask: wgpu::ColorWrites::ALL,
120 })],
121 compilation_options: wgpu::PipelineCompilationOptions::default(),
122 }),
123 primitive: wgpu::PrimitiveState {
124 topology: wgpu::PrimitiveTopology::TriangleList,
125 ..Default::default()
126 },
127 depth_stencil: None,
128 multisample: wgpu::MultisampleState::default(),
129 multiview: None,
130 cache: None,
131 });
132
133 let uniforms = ToneMapUniforms::default();
135 let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
136 label: Some("Tone Map Uniform Buffer"),
137 contents: bytemuck::cast_slice(&[uniforms]),
138 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
139 });
140
141 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
143 label: Some("Tone Map Sampler"),
144 mag_filter: wgpu::FilterMode::Linear,
145 min_filter: wgpu::FilterMode::Linear,
146 ..Default::default()
147 });
148
149 Self {
150 pipeline,
151 bind_group_layout,
152 uniform_buffer,
153 sampler,
154 }
155 }
156
157 pub fn update_uniforms(
159 &self,
160 queue: &wgpu::Queue,
161 exposure: f32,
162 white_level: f32,
163 gamma: f32,
164 ssao_enabled: bool,
165 ) {
166 let uniforms = ToneMapUniforms {
167 exposure,
168 white_level,
169 gamma,
170 ssao_enabled: u32::from(ssao_enabled),
171 _padding: [0.0; 4],
172 };
173 queue.write_buffer(&self.uniform_buffer, 0, bytemuck::cast_slice(&[uniforms]));
174 }
175
176 #[must_use]
178 pub fn create_bind_group(
179 &self,
180 device: &wgpu::Device,
181 input_view: &wgpu::TextureView,
182 ssao_view: &wgpu::TextureView,
183 ) -> wgpu::BindGroup {
184 device.create_bind_group(&wgpu::BindGroupDescriptor {
185 label: Some("Tone Map Bind Group"),
186 layout: &self.bind_group_layout,
187 entries: &[
188 wgpu::BindGroupEntry {
189 binding: 0,
190 resource: wgpu::BindingResource::TextureView(input_view),
191 },
192 wgpu::BindGroupEntry {
193 binding: 1,
194 resource: wgpu::BindingResource::Sampler(&self.sampler),
195 },
196 wgpu::BindGroupEntry {
197 binding: 2,
198 resource: self.uniform_buffer.as_entire_binding(),
199 },
200 wgpu::BindGroupEntry {
201 binding: 3,
202 resource: wgpu::BindingResource::TextureView(ssao_view),
203 },
204 ],
205 })
206 }
207
208 pub fn render(
210 &self,
211 encoder: &mut wgpu::CommandEncoder,
212 output_view: &wgpu::TextureView,
213 bind_group: &wgpu::BindGroup,
214 ) {
215 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
216 label: Some("Tone Map Pass"),
217 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
218 view: output_view,
219 resolve_target: None,
220 ops: wgpu::Operations {
221 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
222 store: wgpu::StoreOp::Store,
223 },
224 depth_slice: None,
225 })],
226 depth_stencil_attachment: None,
227 ..Default::default()
228 });
229
230 render_pass.set_pipeline(&self.pipeline);
231 render_pass.set_bind_group(0, bind_group, &[]);
232 render_pass.draw(0..3, 0..1); }
234
235 pub fn render_to_target(
238 &self,
239 device: &wgpu::Device,
240 encoder: &mut wgpu::CommandEncoder,
241 input_view: &wgpu::TextureView,
242 ssao_view: &wgpu::TextureView,
243 output_view: &wgpu::TextureView,
244 ) {
245 let bind_group = self.create_bind_group(device, input_view, ssao_view);
246 self.render(encoder, output_view, &bind_group);
247 }
248}