veloren_voxygen/render/pipelines/
bloom.rs

1//! Based on: https://community.arm.com/cfs-file/__key/communityserver-blogs-components-weblogfiles/00-00-00-20-66/siggraph2015_2D00_mmg_2D00_marius_2D00_notes.pdf
2//!
3//! See additional details in the [NUM_SIZES] docs
4
5use super::super::{BloomConfig, Consts};
6use bytemuck::{Pod, Zeroable};
7use vek::*;
8
9// TODO: auto-tune the number of passes to maintain roughly constant blur per
10// unit of FOV so changing resolution / FOV doesn't change the blur appearance
11// significantly.
12//
13/// Blurring is performed while downsampling to the smaller sizes in steps and
14/// then upsampling back up to the original resolution. Each level is half the
15/// size in both dimensions from the previous. For instance with 5 distinct
16/// sizes there is a total of 8 passes going from the largest to the smallest to
17/// the largest again:
18///
19/// 1 -> 1/2 -> 1/4 -> 1/8 -> 1/16 -> 1/8 -> 1/4 -> 1/2 -> 1
20///                           ~~~~
21///     [downsampling]      smallest      [upsampling]
22///
23/// The textures used for downsampling are re-used when upsampling.
24///
25/// Additionally, instead of clearing them the colors are added together in an
26/// attempt to obtain a more concentrated bloom near bright areas rather than
27/// a uniform blur. In the example above, the added layers would include 1/8,
28/// 1/4, and 1/2. The smallest size is not upsampled to and the original full
29/// resolution has no blurring and we are already combining the bloom into the
30/// full resolution image in a later step, so they are not included here. The 3
31/// extra layers added in mean the total luminosity of the final blurred bloom
32/// image will be 4 times more than the input image. To account for this, we
33/// divide the bloom intensity by 4 before applying it.
34///
35/// Nevertheless, we have not fully evaluated how this visually compares to the
36/// bloom obtained without adding with the previous layers so there is the
37/// potential for further artistic investigation here.
38///
39/// NOTE: This constant includes the full resolution size and it is
40/// assumed that there will be at least one smaller image to downsample to and
41/// upsample back from (otherwise no blurring would be done). Thus, the minimum
42/// valid value is 2 and panicking indexing operations we perform assume this
43/// will be at least 2.
44pub const NUM_SIZES: usize = 5;
45
46pub struct BindGroup {
47    pub(in super::super) bind_group: wgpu::BindGroup,
48}
49
50#[repr(C)]
51#[derive(Copy, Clone, Debug, Zeroable, Pod)]
52pub struct Locals {
53    halfpixel: [f32; 2],
54}
55
56impl Locals {
57    pub fn new(source_texture_resolution: Vec2<f32>) -> Self {
58        Self {
59            halfpixel: source_texture_resolution.map(|e| 0.5 / e).into_array(),
60        }
61    }
62}
63
64pub struct BloomLayout {
65    pub layout: wgpu::BindGroupLayout,
66}
67
68impl BloomLayout {
69    pub fn new(device: &wgpu::Device) -> Self {
70        Self {
71            layout: device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
72                label: None,
73                entries: &[
74                    // Color source
75                    wgpu::BindGroupLayoutEntry {
76                        binding: 0,
77                        visibility: wgpu::ShaderStages::FRAGMENT,
78                        ty: wgpu::BindingType::Texture {
79                            sample_type: wgpu::TextureSampleType::Float { filterable: true },
80                            view_dimension: wgpu::TextureViewDimension::D2,
81                            multisampled: false,
82                        },
83                        count: None,
84                    },
85                    wgpu::BindGroupLayoutEntry {
86                        binding: 1,
87                        visibility: wgpu::ShaderStages::FRAGMENT,
88                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
89                        count: None,
90                    },
91                    // halfpixel
92                    wgpu::BindGroupLayoutEntry {
93                        binding: 2,
94                        visibility: wgpu::ShaderStages::FRAGMENT,
95                        ty: wgpu::BindingType::Buffer {
96                            ty: wgpu::BufferBindingType::Uniform,
97                            has_dynamic_offset: false,
98                            min_binding_size: None,
99                        },
100                        count: None,
101                    },
102                ],
103            }),
104        }
105    }
106
107    pub fn bind(
108        &self,
109        device: &wgpu::Device,
110        src_color: &wgpu::TextureView,
111        sampler: &wgpu::Sampler,
112        half_pixel: Consts<Locals>,
113    ) -> BindGroup {
114        let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
115            label: None,
116            layout: &self.layout,
117            entries: &[
118                wgpu::BindGroupEntry {
119                    binding: 0,
120                    resource: wgpu::BindingResource::TextureView(src_color),
121                },
122                wgpu::BindGroupEntry {
123                    binding: 1,
124                    resource: wgpu::BindingResource::Sampler(sampler),
125                },
126                wgpu::BindGroupEntry {
127                    binding: 2,
128                    resource: half_pixel.buf().as_entire_binding(),
129                },
130            ],
131        });
132
133        BindGroup { bind_group }
134    }
135}
136
137pub struct BloomPipelines {
138    pub downsample_filtered: wgpu::RenderPipeline,
139    pub downsample: wgpu::RenderPipeline,
140    pub upsample: wgpu::RenderPipeline,
141}
142
143impl BloomPipelines {
144    pub fn new(
145        device: &wgpu::Device,
146        vs_module: &wgpu::ShaderModule,
147        downsample_filtered_fs_module: &wgpu::ShaderModule,
148        downsample_fs_module: &wgpu::ShaderModule,
149        upsample_fs_module: &wgpu::ShaderModule,
150        target_format: wgpu::TextureFormat,
151        layout: &BloomLayout,
152        bloom_config: &BloomConfig,
153    ) -> Self {
154        common_base::span!(_guard, "BloomPipelines::new");
155        let render_pipeline_layout =
156            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
157                label: Some("Bloom pipelines layout"),
158                push_constant_ranges: &[],
159                bind_group_layouts: &[&layout.layout],
160            });
161
162        let create_pipeline = |label, fs_module, blend| {
163            device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
164                label: Some(label),
165                layout: Some(&render_pipeline_layout),
166                vertex: wgpu::VertexState {
167                    module: vs_module,
168                    entry_point: "main",
169                    buffers: &[],
170                },
171                primitive: wgpu::PrimitiveState {
172                    topology: wgpu::PrimitiveTopology::TriangleList,
173                    strip_index_format: None,
174                    front_face: wgpu::FrontFace::Ccw,
175                    cull_mode: None,
176                    unclipped_depth: false,
177                    polygon_mode: wgpu::PolygonMode::Fill,
178                    conservative: false,
179                },
180                depth_stencil: None,
181                multisample: wgpu::MultisampleState {
182                    count: 1,
183                    mask: !0,
184                    alpha_to_coverage_enabled: false,
185                },
186                fragment: Some(wgpu::FragmentState {
187                    module: fs_module,
188                    entry_point: "main",
189                    targets: &[Some(wgpu::ColorTargetState {
190                        format: target_format,
191                        blend,
192                        write_mask: wgpu::ColorWrites::ALL,
193                    })],
194                }),
195                multiview: None,
196            })
197        };
198
199        let downsample_filtered_pipeline = create_pipeline(
200            "Bloom downsample filtered pipeline",
201            downsample_filtered_fs_module,
202            None,
203        );
204        let downsample_pipeline =
205            create_pipeline("Bloom downsample pipeline", downsample_fs_module, None);
206        let upsample_pipeline = create_pipeline(
207            "Bloom upsample pipeline",
208            upsample_fs_module,
209            (!bloom_config.uniform_blur).then_some(wgpu::BlendState {
210                color: wgpu::BlendComponent {
211                    src_factor: wgpu::BlendFactor::One,
212                    dst_factor: wgpu::BlendFactor::One,
213                    operation: wgpu::BlendOperation::Add,
214                },
215                // We don't really use this but we need something here..
216                alpha: wgpu::BlendComponent::REPLACE,
217            }),
218        );
219
220        Self {
221            downsample_filtered: downsample_filtered_pipeline,
222            downsample: downsample_pipeline,
223            upsample: upsample_pipeline,
224        }
225    }
226}