From 2d83ef1c0e87d83d70b345930f86aa26ef2b7109 Mon Sep 17 00:00:00 2001 From: Imbris Date: Wed, 21 Jul 2021 04:58:03 -0400 Subject: [PATCH] Initial bloom impl --- .../voxygen/shaders/duel-downsample-frag.glsl | 31 +++ .../voxygen/shaders/duel-upsample-frag.glsl | 33 +++ assets/voxygen/shaders/postprocess-frag.glsl | 6 + voxygen/src/lib.rs | 2 + voxygen/src/render/pipelines/bloom.rs | 197 ++++++++++++++++++ voxygen/src/render/pipelines/mod.rs | 1 + voxygen/src/render/pipelines/postprocess.rs | 25 ++- voxygen/src/render/renderer.rs | 85 ++++++-- voxygen/src/render/renderer/drawer.rs | 55 ++++- voxygen/src/render/renderer/locals.rs | 40 +++- .../src/render/renderer/pipeline_creation.rs | 35 +++- voxygen/src/session/mod.rs | 6 + 12 files changed, 478 insertions(+), 38 deletions(-) create mode 100644 assets/voxygen/shaders/duel-downsample-frag.glsl create mode 100644 assets/voxygen/shaders/duel-upsample-frag.glsl create mode 100644 voxygen/src/render/pipelines/bloom.rs diff --git a/assets/voxygen/shaders/duel-downsample-frag.glsl b/assets/voxygen/shaders/duel-downsample-frag.glsl new file mode 100644 index 0000000000..51e9f3129f --- /dev/null +++ b/assets/voxygen/shaders/duel-downsample-frag.glsl @@ -0,0 +1,31 @@ +#version 420 core + +layout(set = 0, binding = 0) +uniform texture2D t_src_color; +layout(set = 0, binding = 1) +uniform sampler s_src_color; +layout(set = 0, binding = 2) +uniform vec2 halfpixel; + +layout(location = 0) in vec2 uv; + +layout(location = 0) out vec4 tgt_color; + +vec4 simplesample(vec2 uv) { + return textureLod(sampler2D(t_src_color, s_src_color), uv, 0); +} + +// From: https://community.arm.com/cfs-file/__key/communityserver-blogs-components-weblogfiles/00-00-00-20-66/siggraph2015_2D00_mmg_2D00_marius_2D00_notes.pdf +vec4 downsample(vec2 uv, vec2 halfpixel) { + vec4 sum = simplesample(uv) * 4.0; + sum += simplesample(uv - halfpixel.xy); + sum += simplesample(uv + halfpixel.xy); + sum += simplesample(uv + vec2(halfpixel.x, -halfpixel.y)); + sum += simplesample(uv - vec2(halfpixel.x, -halfpixel.y)); + + return sum / 8.0; +} + +void main() { + tgt_color = downsample(uv, halfpixel); +} diff --git a/assets/voxygen/shaders/duel-upsample-frag.glsl b/assets/voxygen/shaders/duel-upsample-frag.glsl new file mode 100644 index 0000000000..6224f29927 --- /dev/null +++ b/assets/voxygen/shaders/duel-upsample-frag.glsl @@ -0,0 +1,33 @@ +#version 420 core + +layout(set = 0, binding = 0) +uniform texture2D t_src_color; +layout(set = 0, binding = 1) +uniform sampler s_src_color; +layout(set = 0, binding = 2) +uniform vec2 halfpixel; + +layout(location = 0) in vec2 uv; + +layout(location = 0) out vec4 tgt_color; + +vec4 simplesample(vec2 uv) { + return textureLod(sampler2D(t_src_color, s_src_color), uv, 0); +} + +// From: https://community.arm.com/cfs-file/__key/communityserver-blogs-components-weblogfiles/00-00-00-20-66/siggraph2015_2D00_mmg_2D00_marius_2D00_notes.pdf +vec4 upsample(vec2 uv, vec2 halfpixel) { + vec4 sum = simplesample(uv + vec2(-halfpixel.x * 2.0, 0.0)); + sum += simplesample(uv + vec2(-halfpixel.x, halfpixel.y)) * 2.0; + sum += simplesample(uv + vec2(0.0, halfpixel.y * 2.0)); + sum += simplesample(uv + vec2(halfpixel.x, halfpixel.y)) * 2.0; + sum += simplesample(uv + vec2(halfpixel.x * 2.0, 0.0)); + sum += simplesample(uv + vec2(halfpixel.x, -halfpixel.y)) * 2.0; + sum += simplesample(uv + vec2(0.0, -halfpixel.y * 2.0)); + sum += simplesample(uv + vec2(-halfpixel.x, -halfpixel.y)) * 2.0; + return sum / 12.0; +} + +void main() { + tgt_color = upsample(uv, halfpixel); +} diff --git a/assets/voxygen/shaders/postprocess-frag.glsl b/assets/voxygen/shaders/postprocess-frag.glsl index aeba45f350..947019bc06 100644 --- a/assets/voxygen/shaders/postprocess-frag.glsl +++ b/assets/voxygen/shaders/postprocess-frag.glsl @@ -25,6 +25,8 @@ layout(set = 1, binding = 0) uniform texture2D t_src_color; layout(set = 1, binding = 1) +uniform texture2D t_src_bloom; +layout(set = 1, binding = 2) uniform sampler s_src_color; @@ -182,6 +184,10 @@ void main() { vec4 aa_color = aa_apply(t_src_color, s_src_color, uv * screen_res.xy, screen_res.xy); + // Bloom + vec4 bloom = textureLod(sampler2D(t_src_bloom, s_src_color), uv, 0) * 0.05; + aa_color += bloom; + // Tonemapping float exposure_offset = 1.0; // Adding an in-code offset to gamma and exposure let us have more precise control over the game's look diff --git a/voxygen/src/lib.rs b/voxygen/src/lib.rs index cf4d673534..6404738e50 100644 --- a/voxygen/src/lib.rs +++ b/voxygen/src/lib.rs @@ -4,6 +4,8 @@ #![deny(clippy::clone_on_ref_ptr)] #![feature( array_map, + array_methods, + array_zip, bool_to_option, const_generics, drain_filter, diff --git a/voxygen/src/render/pipelines/bloom.rs b/voxygen/src/render/pipelines/bloom.rs new file mode 100644 index 0000000000..56afdf1838 --- /dev/null +++ b/voxygen/src/render/pipelines/bloom.rs @@ -0,0 +1,197 @@ +use super::super::Consts; +use bytemuck::{Pod, Zeroable}; +use vek::*; + +/// Each level is a multiple of 2 smaller in both dimensions. +/// For a total of 8 passes from the largest to the smallest to the largest +/// again. +pub const NUM_SIZES: usize = 5; + +pub struct BindGroup { + pub(in super::super) bind_group: wgpu::BindGroup, +} + +#[repr(C)] +#[derive(Copy, Clone, Debug, Zeroable, Pod)] +pub struct HalfPixel([f32; 2]); + +impl HalfPixel { + pub fn new(source_texture_resolution: Vec2) -> Self { + Self(source_texture_resolution.map(|e| 0.5 / e).into_array()) + } +} + +pub struct BloomLayout { + pub layout: wgpu::BindGroupLayout, +} + +impl BloomLayout { + pub fn new(device: &wgpu::Device) -> Self { + Self { + layout: device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: None, + entries: &[ + // Color source + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Sampler { + filtering: true, + comparison: false, + }, + count: None, + }, + // halfpixel + wgpu::BindGroupLayoutEntry { + binding: 2, + visibility: wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + ], + }), + } + } + + pub fn bind( + &self, + device: &wgpu::Device, + src_color: &wgpu::TextureView, + sampler: &wgpu::Sampler, + half_pixel: Consts, + ) -> BindGroup { + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &self.layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(src_color), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(sampler), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: half_pixel.buf().as_entire_binding(), + }, + ], + }); + + BindGroup { bind_group } + } +} + +pub struct BloomPipelines { + //pub downsample_filtered: wgpu::RenderPipeline, + pub downsample: wgpu::RenderPipeline, + pub upsample: wgpu::RenderPipeline, +} + +impl BloomPipelines { + pub fn new( + device: &wgpu::Device, + vs_module: &wgpu::ShaderModule, + //downsample_filtered_fs_module: &wgpu::ShaderModule, + downsample_fs_module: &wgpu::ShaderModule, + upsample_fs_module: &wgpu::ShaderModule, + target_format: wgpu::TextureFormat, + layout: &BloomLayout, + ) -> Self { + common_base::span!(_guard, "BloomPipelines::new"); + let render_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Bloom pipelines layout"), + push_constant_ranges: &[], + bind_group_layouts: &[&layout.layout], + }); + + let downsample_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Bloom downsample pipeline"), + layout: Some(&render_pipeline_layout), + vertex: wgpu::VertexState { + module: vs_module, + entry_point: "main", + buffers: &[], + }, + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: None, + clamp_depth: false, + polygon_mode: wgpu::PolygonMode::Fill, + conservative: false, + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + fragment: Some(wgpu::FragmentState { + module: downsample_fs_module, + entry_point: "main", + targets: &[wgpu::ColorTargetState { + format: target_format, + blend: None, + write_mask: wgpu::ColorWrite::ALL, + }], + }), + }); + + let upsample_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Bloom upsample pipeline"), + layout: Some(&render_pipeline_layout), + vertex: wgpu::VertexState { + module: vs_module, + entry_point: "main", + buffers: &[], + }, + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: None, + clamp_depth: false, + polygon_mode: wgpu::PolygonMode::Fill, + conservative: false, + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + fragment: Some(wgpu::FragmentState { + module: upsample_fs_module, + entry_point: "main", + targets: &[wgpu::ColorTargetState { + format: target_format, + blend: None, + write_mask: wgpu::ColorWrite::ALL, + }], + }), + }); + + Self { + downsample: downsample_pipeline, + upsample: upsample_pipeline, + } + } +} diff --git a/voxygen/src/render/pipelines/mod.rs b/voxygen/src/render/pipelines/mod.rs index 592d1da64d..d4a0ee3c46 100644 --- a/voxygen/src/render/pipelines/mod.rs +++ b/voxygen/src/render/pipelines/mod.rs @@ -1,4 +1,5 @@ pub mod blit; +pub mod bloom; pub mod clouds; pub mod debug; pub mod figure; diff --git a/voxygen/src/render/pipelines/postprocess.rs b/voxygen/src/render/pipelines/postprocess.rs index 18c982f50c..f990f81dcb 100644 --- a/voxygen/src/render/pipelines/postprocess.rs +++ b/voxygen/src/render/pipelines/postprocess.rs @@ -39,7 +39,19 @@ impl PostProcessLayout { // src color wgpu::BindGroupLayoutEntry { binding: 0, - visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, + visibility: wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + // TODO: make optional + // src bloom + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStage::FRAGMENT, ty: wgpu::BindingType::Texture { sample_type: wgpu::TextureSampleType::Float { filterable: true }, view_dimension: wgpu::TextureViewDimension::D2, @@ -49,7 +61,7 @@ impl PostProcessLayout { }, wgpu::BindGroupLayoutEntry { binding: 1, - visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, + visibility: wgpu::ShaderStage::FRAGMENT, ty: wgpu::BindingType::Sampler { filtering: true, comparison: false, @@ -76,6 +88,7 @@ impl PostProcessLayout { &self, device: &wgpu::Device, src_color: &wgpu::TextureView, + src_bloom: &wgpu::TextureView, sampler: &wgpu::Sampler, locals: &Consts, ) -> BindGroup { @@ -87,6 +100,14 @@ impl PostProcessLayout { binding: 0, resource: wgpu::BindingResource::TextureView(src_color), }, + // TODO: might be cheaper to premix bloom at lower resolution if we are doing + // extensive upscaling + // TODO: if there is no upscaling we can do the last bloom upsampling in post + // process to save a pass and the need for the final full size bloom render target + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(src_bloom), + }, wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(sampler), diff --git a/voxygen/src/render/renderer.rs b/voxygen/src/render/renderer.rs index 4b58ca095f..27f411ac41 100644 --- a/voxygen/src/render/renderer.rs +++ b/voxygen/src/render/renderer.rs @@ -21,8 +21,8 @@ use super::{ mesh::Mesh, model::{DynamicModel, Model}, pipelines::{ - blit, clouds, debug, figure, postprocess, shadow, sprite, terrain, ui, GlobalsBindGroup, - GlobalsLayouts, ShadowTexturesBindGroup, + blit, bloom, clouds, debug, figure, postprocess, shadow, sprite, terrain, ui, + GlobalsBindGroup, GlobalsLayouts, ShadowTexturesBindGroup, }, texture::Texture, AaMode, AddressMode, FilterMode, RenderError, RenderMode, ShadowMapMode, ShadowMode, Vertex, @@ -49,13 +49,14 @@ const QUAD_INDEX_BUFFER_U32_START_VERT_LEN: u32 = 3000; struct Layouts { global: GlobalsLayouts, - clouds: clouds::CloudsLayout, debug: debug::DebugLayout, figure: figure::FigureLayout, - postprocess: postprocess::PostProcessLayout, shadow: shadow::ShadowLayout, sprite: sprite::SpriteLayout, terrain: terrain::TerrainLayout, + clouds: clouds::CloudsLayout, + bloom: bloom::BloomLayout, + postprocess: postprocess::PostProcessLayout, ui: ui::UiLayout, blit: blit::BlitLayout, } @@ -67,6 +68,8 @@ struct Views { tgt_color: wgpu::TextureView, tgt_depth: wgpu::TextureView, + + bloom_tgts: [wgpu::TextureView; bloom::NUM_SIZES], // TODO: rename tgt_color_pp: wgpu::TextureView, } @@ -305,26 +308,28 @@ impl Renderer { let layouts = { let global = GlobalsLayouts::new(&device); - let clouds = clouds::CloudsLayout::new(&device); let debug = debug::DebugLayout::new(&device); let figure = figure::FigureLayout::new(&device); - let postprocess = postprocess::PostProcessLayout::new(&device); let shadow = shadow::ShadowLayout::new(&device); let sprite = sprite::SpriteLayout::new(&device); let terrain = terrain::TerrainLayout::new(&device); + let clouds = clouds::CloudsLayout::new(&device); + let bloom = bloom::BloomLayout::new(&device); + let postprocess = postprocess::PostProcessLayout::new(&device); let ui = ui::UiLayout::new(&device); let blit = blit::BlitLayout::new(&device); Layouts { global, - clouds, debug, figure, - postprocess, shadow, sprite, terrain, + clouds, + bloom, + postprocess, ui, blit, } @@ -350,7 +355,8 @@ impl Renderer { creating, }; - let views = Self::create_rt_views(&device, (dims.width, dims.height), &mode)?; + let (views, bloom_sizes) = + Self::create_rt_views(&device, (dims.width, dims.height), &mode)?; let create_sampler = |filter| { device.create_sampler(&wgpu::SamplerDescriptor { @@ -379,6 +385,8 @@ impl Renderer { let clouds_locals = Self::create_consts_inner(&device, &queue, &[clouds::Locals::default()]); + let bloom_locals = bloom_sizes + .map(|size| Self::create_consts_inner(&device, &queue, &[bloom::HalfPixel::new(size)])); let postprocess_locals = Self::create_consts_inner(&device, &queue, &[postprocess::Locals::default()]); @@ -386,9 +394,18 @@ impl Renderer { &device, &layouts, clouds_locals, + bloom_locals, postprocess_locals, &views.tgt_color, &views.tgt_depth, + [ + &views.tgt_color, + &views.bloom_tgts[1], + &views.bloom_tgts[2], + &views.bloom_tgts[3], + &views.bloom_tgts[4], + ], + &views.bloom_tgts[0], &views.tgt_color_pp, &sampler, &depth_sampler, @@ -546,13 +563,26 @@ impl Renderer { self.swap_chain = self.device.create_swap_chain(&self.surface, &self.sc_desc); // Resize other render targets - self.views = Self::create_rt_views(&self.device, (dims.x, dims.y), &self.mode)?; + let (views, bloom_sizes) = + Self::create_rt_views(&self.device, (dims.x, dims.y), &self.mode)?; + self.views = views; + let bloom_locals = + bloom_sizes.map(|size| self.create_consts(&[bloom::HalfPixel::new(size)])); // Rebind views to clouds/postprocess bind groups self.locals.rebind( &self.device, &self.layouts, + bloom_locals, &self.views.tgt_color, &self.views.tgt_depth, + [ + &self.views.tgt_color, + &self.views.bloom_tgts[1], + &self.views.bloom_tgts[2], + &self.views.bloom_tgts[3], + &self.views.bloom_tgts[4], + ], + &self.views.bloom_tgts[0], &self.views.tgt_color_pp, &self.sampler, &self.depth_sampler, @@ -625,7 +655,7 @@ impl Renderer { device: &wgpu::Device, size: (u32, u32), mode: &RenderMode, - ) -> Result { + ) -> Result<(Views, [Vec2; bloom::NUM_SIZES]), RenderError> { let upscaled = Vec2::::from(size) .map(|e| (e as f32 * mode.upscale_mode.factor) as u32) .into_tuple(); @@ -637,7 +667,7 @@ impl Renderer { }; let levels = 1; - let color_view = || { + let color_view = |width, height| { let tex = device.create_texture(&wgpu::TextureDescriptor { label: None, size: wgpu::Extent3d { @@ -665,8 +695,19 @@ impl Renderer { }) }; - let tgt_color_view = color_view(); - let tgt_color_pp_view = color_view(); + let tgt_color_view = color_view(width, height); + let tgt_color_pp_view = color_view(width, height); + + let mut size_shift = 0; + // TODO: skip creating bloom stuff when it is disabled + let bloom_sizes = [(); bloom::NUM_SIZES].map(|()| { + // .max(1) to ensure we don't create zero sized textures + let size = Vec2::new(width, height).map(|e| (e >> size_shift).max(1)); + size_shift += 1; + size + }); + + let bloom_tgt_views = bloom_sizes.map(|size| color_view(size.x, size.y)); let tgt_depth_tex = device.create_texture(&wgpu::TextureDescriptor { label: None, @@ -717,12 +758,16 @@ impl Renderer { array_layer_count: None, }); - Ok(Views { - tgt_color: tgt_color_view, - tgt_depth: tgt_depth_view, - tgt_color_pp: tgt_color_pp_view, - _win_depth: win_depth_view, - }) + Ok(( + Views { + tgt_color: tgt_color_view, + tgt_depth: tgt_depth_view, + bloom_tgts: bloom_tgt_views, + tgt_color_pp: tgt_color_pp_view, + _win_depth: win_depth_view, + }, + bloom_sizes.map(|s| s.map(|e| e as f32)), + )) } /// Get the resolution of the render target. diff --git a/voxygen/src/render/renderer/drawer.rs b/voxygen/src/render/renderer/drawer.rs index 8034a251b5..a7816a95b6 100644 --- a/voxygen/src/render/renderer/drawer.rs +++ b/voxygen/src/render/renderer/drawer.rs @@ -4,8 +4,8 @@ use super::{ instances::Instances, model::{DynamicModel, Model, SubModel}, pipelines::{ - blit, clouds, debug, figure, fluid, lod_terrain, particle, shadow, skybox, sprite, - terrain, ui, ColLights, GlobalsBindGroup, ShadowTexturesBindGroup, + blit, bloom, clouds, debug, figure, fluid, lod_terrain, particle, shadow, skybox, + sprite, terrain, ui, ColLights, GlobalsBindGroup, ShadowTexturesBindGroup, }, }, Renderer, ShadowMap, ShadowMapRenderer, @@ -241,6 +241,57 @@ impl<'frame> Drawer<'frame> { }) } + /// To be ran between the second pass and the third pass + /// does nothing if the ingame pipelines are not yet ready + pub fn run_bloom_passes(&mut self) { + let pipelines = match self.borrow.pipelines.all() { + Some(p) => p, + None => return, + }; + + let locals = &self.borrow.locals; + let views = &self.borrow.views; + + let encoder = self.encoder.as_mut().unwrap(); + let device = self.borrow.device; + + let mut run_bloom_pass = |bind, view, label: String, pipeline| { + let mut render_pass = + encoder.scoped_render_pass(&label, device, &wgpu::RenderPassDescriptor { + label: Some(&label), + color_attachments: &[wgpu::RenderPassColorAttachment { + resolve_target: None, + view, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), + store: true, + }, + }], + depth_stencil_attachment: None, + }); + + render_pass.set_bind_group(0, bind, &[]); + render_pass.set_pipeline(pipeline); + render_pass.draw(0..3, 0..1); + }; + + // Downsample filter passes + (0..bloom::NUM_SIZES - 1).for_each(|index| { + let bind = &locals.bloom_binds[index].bind_group; + let view = &views.bloom_tgts[index + 1]; + let label = format!("bloom downsample pass {}", index + 1); + run_bloom_pass(bind, view, label, &pipelines.bloom.downsample); + }); + + // Upsample filter passes + (0..bloom::NUM_SIZES - 1).for_each(|index| { + let bind = &locals.bloom_binds[bloom::NUM_SIZES - 1 - index].bind_group; + let view = &views.bloom_tgts[bloom::NUM_SIZES - 2 - index]; + let label = format!("bloom upsample pass {}", index + 1); + run_bloom_pass(bind, view, label, &pipelines.bloom.upsample); + }); + } + pub fn third_pass(&mut self) -> ThirdPassDrawer { let encoder = self.encoder.as_mut().unwrap(); let device = self.borrow.device; diff --git a/voxygen/src/render/renderer/locals.rs b/voxygen/src/render/renderer/locals.rs index b7c94c5396..8a3c549604 100644 --- a/voxygen/src/render/renderer/locals.rs +++ b/voxygen/src/render/renderer/locals.rs @@ -1,7 +1,7 @@ use super::{ super::{ consts::Consts, - pipelines::{clouds, postprocess}, + pipelines::{bloom, clouds, postprocess}, }, Layouts, }; @@ -10,6 +10,8 @@ pub struct Locals { pub clouds: Consts, pub clouds_bind: clouds::BindGroup, + pub bloom_binds: [bloom::BindGroup; bloom::NUM_SIZES], + pub postprocess: Consts, pub postprocess_bind: postprocess::BindGroup, } @@ -19,9 +21,12 @@ impl Locals { device: &wgpu::Device, layouts: &Layouts, clouds_locals: Consts, + bloom_locals: [Consts; bloom::NUM_SIZES], postprocess_locals: Consts, tgt_color_view: &wgpu::TextureView, tgt_depth_view: &wgpu::TextureView, + bloom_src_views: [&wgpu::TextureView; bloom::NUM_SIZES], + bloom_final_tgt_view: &wgpu::TextureView, tgt_color_pp_view: &wgpu::TextureView, sampler: &wgpu::Sampler, depth_sampler: &wgpu::Sampler, @@ -34,14 +39,22 @@ impl Locals { depth_sampler, &clouds_locals, ); - let postprocess_bind = - layouts - .postprocess - .bind(device, tgt_color_pp_view, sampler, &postprocess_locals); + let bloom_binds = bloom_src_views + .zip(bloom_locals) + .map(|(view, locals)| layouts.bloom.bind(device, view, sampler, locals)); + + let postprocess_bind = layouts.postprocess.bind( + device, + tgt_color_pp_view, + bloom_final_tgt_view, + sampler, + &postprocess_locals, + ); Self { clouds: clouds_locals, clouds_bind, + bloom_binds, postprocess: postprocess_locals, postprocess_bind, } @@ -53,8 +66,11 @@ impl Locals { layouts: &Layouts, // Call when these are recreated and need to be rebound // e.g. resizing + bloom_locals: [Consts; bloom::NUM_SIZES], tgt_color_view: &wgpu::TextureView, tgt_depth_view: &wgpu::TextureView, + bloom_src_views: [&wgpu::TextureView; bloom::NUM_SIZES], + bloom_final_tgt_view: &wgpu::TextureView, tgt_color_pp_view: &wgpu::TextureView, sampler: &wgpu::Sampler, depth_sampler: &wgpu::Sampler, @@ -67,9 +83,15 @@ impl Locals { depth_sampler, &self.clouds, ); - self.postprocess_bind = - layouts - .postprocess - .bind(device, tgt_color_pp_view, sampler, &self.postprocess); + self.bloom_binds = bloom_src_views + .zip(bloom_locals) + .map(|(view, locals)| layouts.bloom.bind(device, view, sampler, locals)); + self.postprocess_bind = layouts.postprocess.bind( + device, + tgt_color_pp_view, + bloom_final_tgt_view, + sampler, + &self.postprocess, + ); } } diff --git a/voxygen/src/render/renderer/pipeline_creation.rs b/voxygen/src/render/renderer/pipeline_creation.rs index 1aca7f43f1..36f55b6a60 100644 --- a/voxygen/src/render/renderer/pipeline_creation.rs +++ b/voxygen/src/render/renderer/pipeline_creation.rs @@ -1,8 +1,8 @@ use super::{ super::{ pipelines::{ - blit, clouds, debug, figure, fluid, lod_terrain, particle, postprocess, shadow, skybox, - sprite, terrain, ui, + blit, bloom, clouds, debug, figure, fluid, lod_terrain, particle, postprocess, shadow, + skybox, sprite, terrain, ui, }, AaMode, CloudMode, FluidMode, LightingMode, RenderError, RenderMode, ShadowMode, }, @@ -20,6 +20,7 @@ pub struct Pipelines { pub lod_terrain: lod_terrain::LodTerrainPipeline, pub particle: particle::ParticlePipeline, pub clouds: clouds::CloudsPipeline, + pub bloom: bloom::BloomPipelines, pub postprocess: postprocess::PostProcessPipeline, // Consider reenabling at some time // player_shadow: figure::FigurePipeline, @@ -39,6 +40,7 @@ pub struct IngamePipelines { lod_terrain: lod_terrain::LodTerrainPipeline, particle: particle::ParticlePipeline, clouds: clouds::CloudsPipeline, + bloom: bloom::BloomPipelines, postprocess: postprocess::PostProcessPipeline, // Consider reenabling at some time // player_shadow: figure::FigurePipeline, @@ -74,6 +76,7 @@ impl Pipelines { lod_terrain: ingame.lod_terrain, particle: ingame.particle, clouds: ingame.clouds, + bloom: ingame.bloom, postprocess: ingame.postprocess, //player_shadow: ingame.player_shadow, skybox: ingame.skybox, @@ -107,6 +110,8 @@ struct ShaderModules { lod_terrain_frag: wgpu::ShaderModule, clouds_vert: wgpu::ShaderModule, clouds_frag: wgpu::ShaderModule, + duel_downsample_frag: wgpu::ShaderModule, + duel_upsample_frag: wgpu::ShaderModule, postprocess_vert: wgpu::ShaderModule, postprocess_frag: wgpu::ShaderModule, blit_vert: wgpu::ShaderModule, @@ -255,6 +260,8 @@ impl ShaderModules { lod_terrain_frag: create_shader("lod-terrain-frag", ShaderKind::Fragment)?, clouds_vert: create_shader("clouds-vert", ShaderKind::Vertex)?, clouds_frag: create_shader("clouds-frag", ShaderKind::Fragment)?, + duel_downsample_frag: create_shader("duel-downsample-frag", ShaderKind::Fragment)?, + duel_upsample_frag: create_shader("duel-upsample-frag", ShaderKind::Fragment)?, postprocess_vert: create_shader("postprocess-vert", ShaderKind::Vertex)?, postprocess_frag: create_shader("postprocess-frag", ShaderKind::Fragment)?, blit_vert: create_shader("blit-vert", ShaderKind::Vertex)?, @@ -360,7 +367,7 @@ fn create_interface_pipelines( fn create_ingame_and_shadow_pipelines( needs: PipelineNeeds, pool: &rayon::ThreadPool, - tasks: [Task; 13], + tasks: [Task; 14], ) -> IngameAndShadowPipelines { prof_span!(_guard, "create_ingame_and_shadow_pipelines"); @@ -382,6 +389,7 @@ fn create_ingame_and_shadow_pipelines( particle_task, lod_terrain_task, clouds_task, + bloom_task, postprocess_task, // TODO: if these are ever actually optionally done, counting them // as tasks to do beforehand seems kind of iffy since they will just @@ -535,6 +543,22 @@ fn create_ingame_and_shadow_pipelines( "clouds pipeline creation", ) }; + // Pipelines for rendering our bloom + let create_bloom = || { + bloom_task.run( + || { + bloom::BloomPipelines::new( + device, + &shaders.blit_vert, + &shaders.duel_downsample_frag, + &shaders.duel_upsample_frag, + wgpu::TextureFormat::Rgba16Float, + &layouts.bloom, + ) + }, + "bloom pipelines creation", + ) + }; // Pipeline for rendering our post-processing let create_postprocess = || { postprocess_task.run( @@ -622,7 +646,7 @@ fn create_ingame_and_shadow_pipelines( }; let j1 = || pool.join(create_debug, || pool.join(create_skybox, create_figure)); - let j2 = || pool.join(create_terrain, create_fluid); + let j2 = || pool.join(create_terrain, || pool.join(create_fluid, create_bloom)); let j3 = || pool.join(create_sprite, create_particle); let j4 = || pool.join(create_lod_terrain, create_clouds); let j5 = || pool.join(create_postprocess, create_point_shadow); @@ -636,7 +660,7 @@ fn create_ingame_and_shadow_pipelines( // Ignore this let ( ( - ((debug, (skybox, figure)), (terrain, fluid)), + ((debug, (skybox, figure)), (terrain, (fluid, bloom))), ((sprite, particle), (lod_terrain, clouds)), ), ((postprocess, point_shadow), (terrain_directed_shadow, figure_directed_shadow)), @@ -653,6 +677,7 @@ fn create_ingame_and_shadow_pipelines( lod_terrain, particle, clouds, + bloom, postprocess, skybox, sprite, diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs index 3e7bda0b77..eaf2469d95 100644 --- a/voxygen/src/session/mod.rs +++ b/voxygen/src/session/mod.rs @@ -1483,6 +1483,12 @@ impl PlayState for SessionState { second_pass.draw_clouds(); } } + // Bloom + // TODO: make optional + { + prof_span!("bloom"); + drawer.run_bloom_passes() + } // PostProcess and UI { prof_span!("post-process and ui");