renderling/tonemapping/
cpu.rs

1//! Tonemapping.
2use core::ops::Deref;
3use craballoc::{
4    prelude::{Hybrid, SlabAllocator},
5    runtime::WgpuRuntime,
6};
7use std::sync::{Arc, RwLock};
8
9use crate::texture::Texture;
10
11use super::TonemapConstants;
12
13pub fn bindgroup_layout(device: &wgpu::Device, label: Option<&str>) -> wgpu::BindGroupLayout {
14    device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
15        label,
16        entries: &[
17            // slab
18            wgpu::BindGroupLayoutEntry {
19                binding: 0,
20                visibility: wgpu::ShaderStages::FRAGMENT,
21                ty: wgpu::BindingType::Buffer {
22                    ty: wgpu::BufferBindingType::Storage { read_only: true },
23                    has_dynamic_offset: false,
24                    min_binding_size: None,
25                },
26                count: None,
27            },
28            // hdr texture
29            wgpu::BindGroupLayoutEntry {
30                binding: 1,
31                visibility: wgpu::ShaderStages::FRAGMENT,
32                ty: wgpu::BindingType::Texture {
33                    sample_type: wgpu::TextureSampleType::Float { filterable: true },
34                    view_dimension: wgpu::TextureViewDimension::D2,
35                    multisampled: false,
36                },
37                count: None,
38            },
39            // hdr sampler
40            wgpu::BindGroupLayoutEntry {
41                binding: 2,
42                visibility: wgpu::ShaderStages::FRAGMENT,
43                ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
44                count: None,
45            },
46        ],
47    })
48}
49
50pub fn create_bindgroup(
51    device: &wgpu::Device,
52    label: Option<&str>,
53    hdr_texture: &Texture,
54    slab_buffer: &wgpu::Buffer,
55) -> wgpu::BindGroup {
56    device.create_bind_group(&wgpu::BindGroupDescriptor {
57        label,
58        layout: &bindgroup_layout(device, label),
59        entries: &[
60            wgpu::BindGroupEntry {
61                binding: 0,
62                resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
63                    buffer: slab_buffer,
64                    offset: 0,
65                    size: None,
66                }),
67            },
68            wgpu::BindGroupEntry {
69                binding: 1,
70                resource: wgpu::BindingResource::TextureView(&hdr_texture.view),
71            },
72            wgpu::BindGroupEntry {
73                binding: 2,
74                resource: wgpu::BindingResource::Sampler(&hdr_texture.sampler),
75            },
76        ],
77    })
78}
79
80/// Conducts HDR tone mapping.
81///
82/// Writes the HDR surface texture to the (most likely) sRGB window surface.
83///
84/// Clones of [`Tonemapping`] all reference the same internal data.
85///
86/// ## Note
87/// Only available on CPU. Not Available in shaders.
88#[derive(Clone)]
89pub struct Tonemapping {
90    slab: SlabAllocator<WgpuRuntime>,
91    config: Hybrid<TonemapConstants>,
92    hdr_texture: Arc<RwLock<Texture>>,
93    bindgroup: Arc<RwLock<wgpu::BindGroup>>,
94    pipeline: Arc<wgpu::RenderPipeline>,
95}
96
97impl Tonemapping {
98    pub fn new(
99        runtime: &WgpuRuntime,
100        frame_texture_format: wgpu::TextureFormat,
101        hdr_texture: &Texture,
102    ) -> Self {
103        let slab = SlabAllocator::new(runtime, "tonemapping-slab", wgpu::BufferUsages::empty());
104        let config = slab.new_value(TonemapConstants::default());
105
106        let label = Some("tonemapping");
107        let slab_buffer = slab.commit();
108        let bindgroup = Arc::new(RwLock::new(create_bindgroup(
109            &runtime.device,
110            label,
111            hdr_texture,
112            &slab_buffer,
113        )));
114
115        let device = &runtime.device;
116        let vertex_linkage = crate::linkage::tonemapping_vertex::linkage(device);
117        let fragment_linkage = crate::linkage::tonemapping_fragment::linkage(device);
118        let hdr_layout = bindgroup_layout(device, label);
119        let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
120            label,
121            bind_group_layouts: &[&hdr_layout],
122            push_constant_ranges: &[],
123        });
124        let pipeline = Arc::new(
125            device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
126                label,
127                layout: Some(&layout),
128                vertex: wgpu::VertexState {
129                    module: &vertex_linkage.module,
130                    entry_point: Some(vertex_linkage.entry_point),
131                    buffers: &[],
132                    compilation_options: Default::default(),
133                },
134                primitive: wgpu::PrimitiveState {
135                    topology: wgpu::PrimitiveTopology::TriangleList,
136                    strip_index_format: None,
137                    front_face: wgpu::FrontFace::Ccw,
138                    cull_mode: Some(wgpu::Face::Back),
139                    unclipped_depth: false,
140                    polygon_mode: wgpu::PolygonMode::Fill,
141                    conservative: false,
142                },
143                depth_stencil: None,
144                fragment: Some(wgpu::FragmentState {
145                    module: &fragment_linkage.module,
146                    entry_point: Some(fragment_linkage.entry_point),
147                    targets: &[Some(wgpu::ColorTargetState {
148                        format: frame_texture_format,
149                        blend: Some(wgpu::BlendState::ALPHA_BLENDING),
150                        write_mask: wgpu::ColorWrites::ALL,
151                    })],
152                    compilation_options: Default::default(),
153                }),
154                multisample: wgpu::MultisampleState::default(),
155                multiview: None,
156                cache: None,
157            }),
158        );
159        Self {
160            slab,
161            config,
162            hdr_texture: Arc::new(RwLock::new(hdr_texture.clone())),
163            bindgroup,
164            pipeline,
165        }
166    }
167
168    pub(crate) fn slab_allocator(&self) -> &SlabAllocator<WgpuRuntime> {
169        &self.slab
170    }
171
172    pub fn set_hdr_texture(&self, device: &wgpu::Device, hdr_texture: &Texture) {
173        // UNWRAP: safe because the buffer is created in `Self::new` and guaranteed to
174        // exist
175        let slab_buffer = self.slab.get_buffer().unwrap();
176        let bindgroup = create_bindgroup(device, Some("tonemapping"), hdr_texture, &slab_buffer);
177        // UNWRAP: not safe but we want to panic
178        *self.bindgroup.write().unwrap() = bindgroup;
179        *self.hdr_texture.write().unwrap() = hdr_texture.clone();
180    }
181
182    pub fn get_tonemapping_config(&self) -> TonemapConstants {
183        self.config.get()
184    }
185
186    pub fn set_tonemapping_config(&self, config: TonemapConstants) {
187        self.config.set(config);
188    }
189
190    pub fn render(&self, device: &wgpu::Device, queue: &wgpu::Queue, view: &wgpu::TextureView) {
191        let label = Some("tonemapping render");
192        assert!(!self.slab.commit().is_new_this_commit());
193
194        // UNWRAP: not safe but we want to panic
195        let bindgroup = self.bindgroup.read().unwrap();
196        let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label });
197        let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
198            label,
199            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
200                view,
201                resolve_target: None,
202                ops: wgpu::Operations {
203                    load: wgpu::LoadOp::Load,
204                    store: wgpu::StoreOp::Store,
205                },
206                depth_slice: None,
207            })],
208            depth_stencil_attachment: None,
209            ..Default::default()
210        });
211        render_pass.set_pipeline(&self.pipeline);
212        render_pass.set_bind_group(0, Some(bindgroup.deref()), &[]);
213        let id = self.config.id().into();
214        render_pass.draw(0..6, id..id + 1);
215        drop(render_pass);
216
217        queue.submit(std::iter::once(encoder.finish()));
218    }
219}