From df171671d8fb0c50577d9286db9525c4184079f4 Mon Sep 17 00:00:00 2001 From: Imbris Date: Wed, 21 Jul 2021 04:58:03 -0400 Subject: [PATCH 1/8] 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"); From 1de1f4858d466307c53b812fe10ae1cb9f4cb3a3 Mon Sep 17 00:00:00 2001 From: Imbris Date: Wed, 21 Jul 2021 05:39:30 -0400 Subject: [PATCH 2/8] Fixes and extra bloom --- assets/voxygen/shaders/duel-downsample-frag.glsl | 5 ++++- assets/voxygen/shaders/duel-upsample-frag.glsl | 4 +++- assets/voxygen/shaders/postprocess-frag.glsl | 4 ++-- voxygen/src/render/pipelines/postprocess.rs | 12 ++++++------ voxygen/src/render/renderer/shaders.rs | 3 +++ 5 files changed, 18 insertions(+), 10 deletions(-) diff --git a/assets/voxygen/shaders/duel-downsample-frag.glsl b/assets/voxygen/shaders/duel-downsample-frag.glsl index 51e9f3129f..fb8cfdf9dc 100644 --- a/assets/voxygen/shaders/duel-downsample-frag.glsl +++ b/assets/voxygen/shaders/duel-downsample-frag.glsl @@ -5,7 +5,10 @@ uniform texture2D t_src_color; layout(set = 0, binding = 1) uniform sampler s_src_color; layout(set = 0, binding = 2) -uniform vec2 halfpixel; +// TODO: refactor in rust +uniform u_locals { + vec2 halfpixel; +}; layout(location = 0) in vec2 uv; diff --git a/assets/voxygen/shaders/duel-upsample-frag.glsl b/assets/voxygen/shaders/duel-upsample-frag.glsl index 6224f29927..10d1b1fdc6 100644 --- a/assets/voxygen/shaders/duel-upsample-frag.glsl +++ b/assets/voxygen/shaders/duel-upsample-frag.glsl @@ -5,7 +5,9 @@ uniform texture2D t_src_color; layout(set = 0, binding = 1) uniform sampler s_src_color; layout(set = 0, binding = 2) -uniform vec2 halfpixel; +uniform u_locals { + vec2 halfpixel; +}; layout(location = 0) in vec2 uv; diff --git a/assets/voxygen/shaders/postprocess-frag.glsl b/assets/voxygen/shaders/postprocess-frag.glsl index 947019bc06..8bcc1f6acd 100644 --- a/assets/voxygen/shaders/postprocess-frag.glsl +++ b/assets/voxygen/shaders/postprocess-frag.glsl @@ -32,7 +32,7 @@ uniform sampler s_src_color; layout(location = 0) in vec2 uv; -layout (std140, set = 1, binding = 2) +layout (std140, set = 1, binding = 3) uniform u_locals { mat4 proj_mat_inv; mat4 view_mat_inv; @@ -185,7 +185,7 @@ 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; + vec4 bloom = textureLod(sampler2D(t_src_bloom, s_src_color), uv, 0) * 0.75; aa_color += bloom; // Tonemapping diff --git a/voxygen/src/render/pipelines/postprocess.rs b/voxygen/src/render/pipelines/postprocess.rs index f990f81dcb..4efac906f1 100644 --- a/voxygen/src/render/pipelines/postprocess.rs +++ b/voxygen/src/render/pipelines/postprocess.rs @@ -50,7 +50,7 @@ impl PostProcessLayout { // TODO: make optional // src bloom wgpu::BindGroupLayoutEntry { - binding: 0, + binding: 1, visibility: wgpu::ShaderStage::FRAGMENT, ty: wgpu::BindingType::Texture { sample_type: wgpu::TextureSampleType::Float { filterable: true }, @@ -60,7 +60,7 @@ impl PostProcessLayout { count: None, }, wgpu::BindGroupLayoutEntry { - binding: 1, + binding: 2, visibility: wgpu::ShaderStage::FRAGMENT, ty: wgpu::BindingType::Sampler { filtering: true, @@ -70,7 +70,7 @@ impl PostProcessLayout { }, // Locals wgpu::BindGroupLayoutEntry { - binding: 2, + binding: 3, visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, @@ -105,15 +105,15 @@ impl PostProcessLayout { // 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, + binding: 1, resource: wgpu::BindingResource::TextureView(src_bloom), }, wgpu::BindGroupEntry { - binding: 1, + binding: 2, resource: wgpu::BindingResource::Sampler(sampler), }, wgpu::BindGroupEntry { - binding: 2, + binding: 3, resource: locals.buf().as_entire_binding(), }, ], diff --git a/voxygen/src/render/renderer/shaders.rs b/voxygen/src/render/renderer/shaders.rs index eb3a148a89..21ac844627 100644 --- a/voxygen/src/render/renderer/shaders.rs +++ b/voxygen/src/render/renderer/shaders.rs @@ -68,6 +68,9 @@ impl assets::Compound for Shaders { "lod-terrain-frag", "clouds-vert", "clouds-frag", + "duel-downsample-frag", + "duel-upsample-frag", + "clouds-frag", "postprocess-vert", "postprocess-frag", "blit-vert", From c85b5e13cb69d62543e753a3c2d0c0658b76f87f Mon Sep 17 00:00:00 2001 From: Imbris Date: Sat, 24 Jul 2021 00:48:26 -0400 Subject: [PATCH 3/8] Add bloom passes in character select screen, use proper source image for bloom after clouds are applied, add blurred and less blurred stages of bloom together (experimental could remove), add filtering downsample pass that is not yet used --- .../dual-downsample-filtered-frag.glsl | 73 ++++++++++ ...le-frag.glsl => dual-downsample-frag.glsl} | 0 ...mple-frag.glsl => dual-upsample-frag.glsl} | 0 assets/voxygen/shaders/postprocess-frag.glsl | 6 +- voxygen/src/menu/char_selection/mod.rs | 5 + voxygen/src/render/pipelines/bloom.rs | 133 +++++++++--------- voxygen/src/render/renderer.rs | 4 +- voxygen/src/render/renderer/drawer.rs | 39 +++-- .../src/render/renderer/pipeline_creation.rs | 18 ++- voxygen/src/render/renderer/shaders.rs | 5 +- 10 files changed, 193 insertions(+), 90 deletions(-) create mode 100644 assets/voxygen/shaders/dual-downsample-filtered-frag.glsl rename assets/voxygen/shaders/{duel-downsample-frag.glsl => dual-downsample-frag.glsl} (100%) rename assets/voxygen/shaders/{duel-upsample-frag.glsl => dual-upsample-frag.glsl} (100%) diff --git a/assets/voxygen/shaders/dual-downsample-filtered-frag.glsl b/assets/voxygen/shaders/dual-downsample-filtered-frag.glsl new file mode 100644 index 0000000000..01235bf798 --- /dev/null +++ b/assets/voxygen/shaders/dual-downsample-filtered-frag.glsl @@ -0,0 +1,73 @@ +#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) +// TODO: refactor in rust +uniform u_locals { + 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); +} + +// check whether the texel color is higher than threshold, if so output as brightness color +vec4 filterDim(vec4 color) { + // TODO: note where this constant came from if we keep it + float brightness = dot(color.rgb, vec3(0.2126, 0.7152, 0.0722)); + if(brightness > 1.0) + return vec4(color.rgb, 1.0); + else + return vec4(0.0, 0.0, 0.0, 1.0); +} + +vec4 filteredFetch(ivec2 uv) { + return filterDim(simplesample(uv)); +} + +// Derived 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 filteredDownsample(vec2 uv, vec2 halfpixel) { + vec2 tex_res = 2.0 / halfpixel; + // coordinate of the top left texel + // _ _ _ _ + // |x|_|_|_| + // |_|_|_|_| + // |_|_|_|_| + // |_|_|_|_| + // + ivec2 tl_coord = ivec2(uv * tex_res + vec2(-1.5, 1.5)); + + // Fetch inner square + vec4 sum = filteredFetch(tl_coord + ivec2(1, 1)); + sum += filteredFetch(tl_coord + ivec2(2, 1)); + sum += filteredFetch(tl_coord + ivec2(1, 2)); + sum += filteredFetch(tl_coord + ivec2(2, 2)); + // Weight inner square + sum *= 5.0; + // Fetch border + sum += filteredFetch(tl_coord + ivec2(0, 0)); + sum += filteredFetch(tl_coord + ivec2(1, 0)); + sum += filteredFetch(tl_coord + ivec2(2, 0)); + sum += filteredFetch(tl_coord + ivec2(3, 0)); + sum += filteredFetch(tl_coord + ivec2(0, 1)); + sum += filteredFetch(tl_coord + ivec2(3, 1)); + sum += filteredFetch(tl_coord + ivec2(0, 2)); + sum += filteredFetch(tl_coord + ivec2(3, 2)); + sum += filteredFetch(tl_coord + ivec2(0, 3)); + sum += filteredFetch(tl_coord + ivec2(1, 3)); + sum += filteredFetch(tl_coord + ivec2(2, 3)); + sum += filteredFetch(tl_coord + ivec2(3, 3)); + + return sum / 32.0; +} + +void main() { + tgt_color = filteredDownsample(uv, halfpixel); +} diff --git a/assets/voxygen/shaders/duel-downsample-frag.glsl b/assets/voxygen/shaders/dual-downsample-frag.glsl similarity index 100% rename from assets/voxygen/shaders/duel-downsample-frag.glsl rename to assets/voxygen/shaders/dual-downsample-frag.glsl diff --git a/assets/voxygen/shaders/duel-upsample-frag.glsl b/assets/voxygen/shaders/dual-upsample-frag.glsl similarity index 100% rename from assets/voxygen/shaders/duel-upsample-frag.glsl rename to assets/voxygen/shaders/dual-upsample-frag.glsl diff --git a/assets/voxygen/shaders/postprocess-frag.glsl b/assets/voxygen/shaders/postprocess-frag.glsl index 8bcc1f6acd..6308d73d16 100644 --- a/assets/voxygen/shaders/postprocess-frag.glsl +++ b/assets/voxygen/shaders/postprocess-frag.glsl @@ -185,8 +185,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.75; - aa_color += bloom; + // divide by 4.0 to account for adding blurred layers together + vec4 bloom = textureLod(sampler2D(t_src_bloom, s_src_color), uv, 0) / 4.0; + float bloom_factor = 0.10; + aa_color = aa_color * (1.0 - bloom_factor) + bloom * bloom_factor; // Tonemapping float exposure_offset = 1.0; diff --git a/voxygen/src/menu/char_selection/mod.rs b/voxygen/src/menu/char_selection/mod.rs index 8941cfc8bf..688d53f465 100644 --- a/voxygen/src/menu/char_selection/mod.rs +++ b/voxygen/src/menu/char_selection/mod.rs @@ -257,6 +257,11 @@ impl PlayState for CharSelectionState { if let Some(mut second_pass) = drawer.second_pass() { second_pass.draw_clouds(); } + // Bloom + // TODO: make optional + { + drawer.run_bloom_passes() + } // PostProcess and UI let mut third_pass = drawer.third_pass(); third_pass.draw_postprocess(); diff --git a/voxygen/src/render/pipelines/bloom.rs b/voxygen/src/render/pipelines/bloom.rs index 56afdf1838..061ef0871f 100644 --- a/voxygen/src/render/pipelines/bloom.rs +++ b/voxygen/src/render/pipelines/bloom.rs @@ -1,7 +1,12 @@ +//! 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 + use super::super::Consts; use bytemuck::{Pod, Zeroable}; use vek::*; +// TODO: auto-tune the number of passes to maintain roughly constant blur per +// unit of FOV so changing resolution / FOV doesn't change the blur appearance +// significantly /// 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. @@ -98,7 +103,7 @@ impl BloomLayout { } pub struct BloomPipelines { - //pub downsample_filtered: wgpu::RenderPipeline, + pub downsample_filtered: wgpu::RenderPipeline, pub downsample: wgpu::RenderPipeline, pub upsample: wgpu::RenderPipeline, } @@ -107,7 +112,7 @@ impl BloomPipelines { pub fn new( device: &wgpu::Device, vs_module: &wgpu::ShaderModule, - //downsample_filtered_fs_module: &wgpu::ShaderModule, + downsample_filtered_fs_module: &wgpu::ShaderModule, downsample_fs_module: &wgpu::ShaderModule, upsample_fs_module: &wgpu::ShaderModule, target_format: wgpu::TextureFormat, @@ -121,75 +126,69 @@ impl BloomPipelines { 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 create_pipeline = |label, fs_module, blend| { + device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some(label), + 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: fs_module, + entry_point: "main", + targets: &[wgpu::ColorTargetState { + format: target_format, + blend, + 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, - }], + let downsample_filtered_pipeline = create_pipeline( + "Bloom downsample filtered pipeline", + downsample_filtered_fs_module, + None, + ); + let downsample_pipeline = + create_pipeline("Bloom downsample pipeline", downsample_fs_module, None); + let upsample_pipeline = create_pipeline( + "Bloom upsample pipeline", + upsample_fs_module, + Some(wgpu::BlendState { + color: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::One, + dst_factor: wgpu::BlendFactor::One, + operation: wgpu::BlendOperation::Add, + }, + // we don't really use this, but... we need something here + alpha: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::One, + dst_factor: wgpu::BlendFactor::One, + operation: wgpu::BlendOperation::Add, + }, }), - }); + ); Self { + downsample_filtered: downsample_filtered_pipeline, downsample: downsample_pipeline, upsample: upsample_pipeline, } diff --git a/voxygen/src/render/renderer.rs b/voxygen/src/render/renderer.rs index 27f411ac41..9e923adf3d 100644 --- a/voxygen/src/render/renderer.rs +++ b/voxygen/src/render/renderer.rs @@ -399,7 +399,7 @@ impl Renderer { &views.tgt_color, &views.tgt_depth, [ - &views.tgt_color, + &views.tgt_color_pp, &views.bloom_tgts[1], &views.bloom_tgts[2], &views.bloom_tgts[3], @@ -576,7 +576,7 @@ impl Renderer { &self.views.tgt_color, &self.views.tgt_depth, [ - &self.views.tgt_color, + &self.views.tgt_color_pp, &self.views.bloom_tgts[1], &self.views.bloom_tgts[2], &self.views.bloom_tgts[3], diff --git a/voxygen/src/render/renderer/drawer.rs b/voxygen/src/render/renderer/drawer.rs index a7816a95b6..f0e2a301bd 100644 --- a/voxygen/src/render/renderer/drawer.rs +++ b/voxygen/src/render/renderer/drawer.rs @@ -252,20 +252,18 @@ impl<'frame> Drawer<'frame> { let locals = &self.borrow.locals; let views = &self.borrow.views; - let encoder = self.encoder.as_mut().unwrap(); let device = self.borrow.device; + let mut encoder = self.encoder.as_mut().unwrap().scope("bloom", device); - let mut run_bloom_pass = |bind, view, label: String, pipeline| { + let mut run_bloom_pass = |bind, view, label: String, pipeline, load| { + let pass_label = format!("bloom {} pass", label); let mut render_pass = encoder.scoped_render_pass(&label, device, &wgpu::RenderPassDescriptor { - label: Some(&label), + label: Some(&pass_label), color_attachments: &[wgpu::RenderPassColorAttachment { resolve_target: None, view, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), - store: true, - }, + ops: wgpu::Operations { store: true, load }, }], depth_stencil_attachment: None, }); @@ -279,16 +277,35 @@ impl<'frame> Drawer<'frame> { (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); + let label = format!("downsample {}", index + 1); + run_bloom_pass( + bind, + view, + label, + &pipelines.bloom.downsample, + wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), + ); }); // 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); + let label = format!("upsample {}", index + 1); + run_bloom_pass( + bind, + view, + label, + &pipelines.bloom.upsample, + if index + 2 == bloom::NUM_SIZES { + // Clear for the final image since that is just stuff from the pervious frame. + wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT) + } else { + // Add to less blurred images to get gradient of blur instead of a smudge> + // https://catlikecoding.com/unity/tutorials/advanced-rendering/bloom/ + wgpu::LoadOp::Load + }, + ); }); } diff --git a/voxygen/src/render/renderer/pipeline_creation.rs b/voxygen/src/render/renderer/pipeline_creation.rs index 36f55b6a60..10d02ba298 100644 --- a/voxygen/src/render/renderer/pipeline_creation.rs +++ b/voxygen/src/render/renderer/pipeline_creation.rs @@ -110,8 +110,9 @@ 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, + dual_downsample_filtered_frag: wgpu::ShaderModule, + dual_downsample_frag: wgpu::ShaderModule, + dual_upsample_frag: wgpu::ShaderModule, postprocess_vert: wgpu::ShaderModule, postprocess_frag: wgpu::ShaderModule, blit_vert: wgpu::ShaderModule, @@ -260,8 +261,12 @@ 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)?, + dual_downsample_filtered_frag: create_shader( + "dual-downsample-filtered-frag", + ShaderKind::Fragment, + )?, + dual_downsample_frag: create_shader("dual-downsample-frag", ShaderKind::Fragment)?, + dual_upsample_frag: create_shader("dual-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)?, @@ -550,8 +555,9 @@ fn create_ingame_and_shadow_pipelines( bloom::BloomPipelines::new( device, &shaders.blit_vert, - &shaders.duel_downsample_frag, - &shaders.duel_upsample_frag, + &shaders.dual_downsample_filtered_frag, + &shaders.dual_downsample_frag, + &shaders.dual_upsample_frag, wgpu::TextureFormat::Rgba16Float, &layouts.bloom, ) diff --git a/voxygen/src/render/renderer/shaders.rs b/voxygen/src/render/renderer/shaders.rs index 21ac844627..2d8f32556c 100644 --- a/voxygen/src/render/renderer/shaders.rs +++ b/voxygen/src/render/renderer/shaders.rs @@ -68,8 +68,9 @@ impl assets::Compound for Shaders { "lod-terrain-frag", "clouds-vert", "clouds-frag", - "duel-downsample-frag", - "duel-upsample-frag", + "dual-downsample-filtered-frag", + "dual-downsample-frag", + "dual-upsample-frag", "clouds-frag", "postprocess-vert", "postprocess-frag", From 088d2077a1bc523d63d817cc1e4488023d7b43b3 Mon Sep 17 00:00:00 2001 From: Imbris Date: Sat, 24 Jul 2021 01:10:29 -0400 Subject: [PATCH 4/8] Add ability to edit shader to enable filtering dim values for experimentation --- .../dual-downsample-filtered-frag.glsl | 31 ++++++++++++++----- voxygen/src/render/renderer/drawer.rs | 15 +++++++-- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/assets/voxygen/shaders/dual-downsample-filtered-frag.glsl b/assets/voxygen/shaders/dual-downsample-filtered-frag.glsl index 01235bf798..b4a54ec5c6 100644 --- a/assets/voxygen/shaders/dual-downsample-filtered-frag.glsl +++ b/assets/voxygen/shaders/dual-downsample-filtered-frag.glsl @@ -14,27 +14,27 @@ 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); +vec4 simplefetch(ivec2 uv) { + return texelFetch(sampler2D(t_src_color, s_src_color), uv, 0); } // check whether the texel color is higher than threshold, if so output as brightness color vec4 filterDim(vec4 color) { // TODO: note where this constant came from if we keep it float brightness = dot(color.rgb, vec3(0.2126, 0.7152, 0.0722)); - if(brightness > 1.0) + if(brightness > 1.00) return vec4(color.rgb, 1.0); else return vec4(0.0, 0.0, 0.0, 1.0); } vec4 filteredFetch(ivec2 uv) { - return filterDim(simplesample(uv)); + return filterDim(simplefetch(uv)); } // Derived 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 filteredDownsample(vec2 uv, vec2 halfpixel) { - vec2 tex_res = 2.0 / halfpixel; + vec2 tex_res = 0.5 / halfpixel; // coordinate of the top left texel // _ _ _ _ // |x|_|_|_| @@ -68,6 +68,23 @@ vec4 filteredDownsample(vec2 uv, vec2 halfpixel) { return sum / 32.0; } -void main() { - tgt_color = filteredDownsample(uv, halfpixel); +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() { + // Uncomment to experiment with filtering out dim pixels + //tgt_color = filteredDownsample(uv, halfpixel); + tgt_color = downsample(uv, halfpixel); } diff --git a/voxygen/src/render/renderer/drawer.rs b/voxygen/src/render/renderer/drawer.rs index f0e2a301bd..4c5bc22887 100644 --- a/voxygen/src/render/renderer/drawer.rs +++ b/voxygen/src/render/renderer/drawer.rs @@ -277,12 +277,23 @@ impl<'frame> Drawer<'frame> { (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!("downsample {}", index + 1); + // Do filtering out of non-bright things during the first downsample + let (label, pipeline) = if index == 0 { + ( + format!("downsample filtered {}", index + 1), + &pipelines.bloom.downsample_filtered, + ) + } else { + ( + format!("downsample {}", index + 1), + &pipelines.bloom.downsample, + ) + }; run_bloom_pass( bind, view, label, - &pipelines.bloom.downsample, + pipeline, wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), ); }); From d8c080afde79e49f07c3a1291a33852fbbc05ddf Mon Sep 17 00:00:00 2001 From: Imbris Date: Sat, 24 Jul 2021 01:40:17 -0400 Subject: [PATCH 5/8] Add some of the changes from zesterer's patch including decreasing figure glow power, increasing glow of some particles, and fixing water normal normalization --- assets/voxygen/shaders/dual-downsample-filtered-frag.glsl | 4 ++-- assets/voxygen/shaders/figure-frag.glsl | 2 +- assets/voxygen/shaders/fluid-frag/shiny.glsl | 2 +- assets/voxygen/shaders/particle-vert.glsl | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/assets/voxygen/shaders/dual-downsample-filtered-frag.glsl b/assets/voxygen/shaders/dual-downsample-filtered-frag.glsl index b4a54ec5c6..1c381e166b 100644 --- a/assets/voxygen/shaders/dual-downsample-filtered-frag.glsl +++ b/assets/voxygen/shaders/dual-downsample-filtered-frag.glsl @@ -18,9 +18,9 @@ vec4 simplefetch(ivec2 uv) { return texelFetch(sampler2D(t_src_color, s_src_color), uv, 0); } -// check whether the texel color is higher than threshold, if so output as brightness color +// Check whether the texel color is higher than threshold, if so output as brightness color vec4 filterDim(vec4 color) { - // TODO: note where this constant came from if we keep it + // constants from: https://learnopengl.com/Advanced-Lighting/Bloom float brightness = dot(color.rgb, vec3(0.2126, 0.7152, 0.0722)); if(brightness > 1.00) return vec4(color.rgb, 1.0); diff --git a/assets/voxygen/shaders/figure-frag.glsl b/assets/voxygen/shaders/figure-frag.glsl index 3b084366da..ebd636fea7 100644 --- a/assets/voxygen/shaders/figure-frag.glsl +++ b/assets/voxygen/shaders/figure-frag.glsl @@ -198,7 +198,7 @@ void main() { // For now, just make glowing material light be the same colour as the surface // TODO: Add a way to control this better outside the shaders if ((material & (1u << 0u)) > 0u) { - emitted_light += 1000 * surf_color; + emitted_light += 10 * surf_color; } float glow_mag = length(model_glow.xyz); diff --git a/assets/voxygen/shaders/fluid-frag/shiny.glsl b/assets/voxygen/shaders/fluid-frag/shiny.glsl index 019e2b0618..67aae23766 100644 --- a/assets/voxygen/shaders/fluid-frag/shiny.glsl +++ b/assets/voxygen/shaders/fluid-frag/shiny.glsl @@ -145,7 +145,7 @@ void main() { nmap = mix(f_norm, normalize(nmap), min(1.0 / pow(frag_dist, 0.75), 1)); //float suppress_waves = max(dot(), 0); - vec3 norm = vec3(0, 0, 1) * nmap.z + b_norm * nmap.x + c_norm * nmap.y; + vec3 norm = normalize(vec3(0, 0, 1) * nmap.z + b_norm * nmap.x + c_norm * nmap.y); // vec3 norm = f_norm; vec3 water_color = (1.0 - MU_WATER) * MU_SCATTER; diff --git a/assets/voxygen/shaders/particle-vert.glsl b/assets/voxygen/shaders/particle-vert.glsl index 8f2a494079..1e29ffb9e4 100644 --- a/assets/voxygen/shaders/particle-vert.glsl +++ b/assets/voxygen/shaders/particle-vert.glsl @@ -355,7 +355,7 @@ void main() { sin(lifetime * 2.0 + rand2) + sin(lifetime * 9.0 + rand5) * 0.3 ), vec3(raise), - vec4(vec3(5, 5, 1.1), 1), + vec4(vec3(10.3, 9, 1.5), 1), spin_in_axis(vec3(rand6, rand7, rand8), rand9 * 3 + lifetime * 5) ); break; @@ -386,7 +386,7 @@ void main() { attr = Attr( spiral_motion(vec3(0, 0, rand3 + 1), spiral_radius, lifetime, abs(rand0), rand1 * 2 * PI) + vec3(0, 0, rand2), vec3(6 * abs(rand4) * (1 - slow_start(2)) * pow(spiral_radius / length(inst_dir), 0.5)), - vec4(vec3(0, 1.7, 0.7), 1), + vec4(vec3(0, 1.7, 0.7) * 3, 1), spin_in_axis(vec3(rand6, rand7, rand8), rand9 * 3) ); break; @@ -397,7 +397,7 @@ void main() { attr = Attr( spiral_motion(inst_dir, 0.3 * (floor(2 * rand0 + 0.5) - 0.5) * min(linear_scale(10), 1), lifetime / inst_lifespan, 10.0, inst_time), vec3((1.7 - 0.7 * abs(floor(2 * rand0 - 0.5) + 0.5)) * (1.5 + 0.5 * sin(tick.x * 10 - lifetime * 4))), - vec4(vec3(purple_col, green_col, 0.75 * purple_col), 1), + vec4(vec3(purple_col, green_col, 0.75 * purple_col) * 3, 1), spin_in_axis(inst_dir, tick.z) ); break; From 47d8b718394cf60e89af6e8f13b3e662e0509a14 Mon Sep 17 00:00:00 2001 From: Imbris Date: Thu, 29 Jul 2021 02:06:56 -0400 Subject: [PATCH 6/8] Make bloom optional with a config option that is not exposed in the UI (to give artists time to refine bloom before exposing the option) --- .../dual-downsample-filtered-frag.glsl | 2 +- .../voxygen/shaders/dual-downsample-frag.glsl | 2 +- assets/voxygen/shaders/figure-frag.glsl | 2 +- assets/voxygen/shaders/include/constants.glsl | 3 + assets/voxygen/shaders/postprocess-frag.glsl | 14 +- voxygen/src/menu/char_selection/mod.rs | 7 +- voxygen/src/render/mod.rs | 42 +++ voxygen/src/render/pipelines/bloom.rs | 12 +- voxygen/src/render/pipelines/postprocess.rs | 145 +++++---- voxygen/src/render/renderer.rs | 290 +++++++++++------- voxygen/src/render/renderer/drawer.rs | 45 +-- voxygen/src/render/renderer/locals.rs | 39 ++- .../src/render/renderer/pipeline_creation.rs | 140 +++++---- voxygen/src/scene/figure/mod.rs | 2 +- voxygen/src/scene/mod.rs | 4 +- voxygen/src/scene/terrain.rs | 2 +- voxygen/src/session/mod.rs | 3 +- voxygen/src/settings/graphics.rs | 2 + voxygen/src/window.rs | 3 +- 19 files changed, 480 insertions(+), 279 deletions(-) diff --git a/assets/voxygen/shaders/dual-downsample-filtered-frag.glsl b/assets/voxygen/shaders/dual-downsample-filtered-frag.glsl index 1c381e166b..9ce06098ae 100644 --- a/assets/voxygen/shaders/dual-downsample-filtered-frag.glsl +++ b/assets/voxygen/shaders/dual-downsample-filtered-frag.glsl @@ -5,7 +5,7 @@ uniform texture2D t_src_color; layout(set = 0, binding = 1) uniform sampler s_src_color; layout(set = 0, binding = 2) -// TODO: refactor in rust + uniform u_locals { vec2 halfpixel; }; diff --git a/assets/voxygen/shaders/dual-downsample-frag.glsl b/assets/voxygen/shaders/dual-downsample-frag.glsl index fb8cfdf9dc..af9fa46c09 100644 --- a/assets/voxygen/shaders/dual-downsample-frag.glsl +++ b/assets/voxygen/shaders/dual-downsample-frag.glsl @@ -5,7 +5,7 @@ uniform texture2D t_src_color; layout(set = 0, binding = 1) uniform sampler s_src_color; layout(set = 0, binding = 2) -// TODO: refactor in rust + uniform u_locals { vec2 halfpixel; }; diff --git a/assets/voxygen/shaders/figure-frag.glsl b/assets/voxygen/shaders/figure-frag.glsl index ebd636fea7..0c1948023c 100644 --- a/assets/voxygen/shaders/figure-frag.glsl +++ b/assets/voxygen/shaders/figure-frag.glsl @@ -198,7 +198,7 @@ void main() { // For now, just make glowing material light be the same colour as the surface // TODO: Add a way to control this better outside the shaders if ((material & (1u << 0u)) > 0u) { - emitted_light += 10 * surf_color; + emitted_light += 20 * surf_color; } float glow_mag = length(model_glow.xyz); diff --git a/assets/voxygen/shaders/include/constants.glsl b/assets/voxygen/shaders/include/constants.glsl index f1bc2a8aa9..8cd90ca072 100644 --- a/assets/voxygen/shaders/include/constants.glsl +++ b/assets/voxygen/shaders/include/constants.glsl @@ -24,6 +24,9 @@ #define SHADOW_MODE_CHEAP 1 #define SHADOW_MODE_MAP 2 +#define BLOOM_DISABLED 0 +#define BLOOM_ENABLED 1 + /* Unlike the other flags (for now anyway), these are bitmask values */ #define LIGHTING_TYPE_REFLECTION 0x01 #define LIGHTING_TYPE_TRANSMISSION 0x02 diff --git a/assets/voxygen/shaders/postprocess-frag.glsl b/assets/voxygen/shaders/postprocess-frag.glsl index 6308d73d16..57df871073 100644 --- a/assets/voxygen/shaders/postprocess-frag.glsl +++ b/assets/voxygen/shaders/postprocess-frag.glsl @@ -25,19 +25,21 @@ 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; - layout(location = 0) in vec2 uv; -layout (std140, set = 1, binding = 3) +layout (std140, set = 1, binding = 2) uniform u_locals { mat4 proj_mat_inv; mat4 view_mat_inv; }; +#if (BLOOM == BLOOM_ENABLED) +layout(set = 1, binding = 3) +uniform texture2D t_src_bloom; +#endif + layout(location = 0) out vec4 tgt_color; vec3 rgb2hsv(vec3 c) { @@ -185,10 +187,12 @@ void main() { vec4 aa_color = aa_apply(t_src_color, s_src_color, uv * screen_res.xy, screen_res.xy); // Bloom + #if (BLOOM == BLOOM_ENABLED) // divide by 4.0 to account for adding blurred layers together vec4 bloom = textureLod(sampler2D(t_src_bloom, s_src_color), uv, 0) / 4.0; float bloom_factor = 0.10; - aa_color = aa_color * (1.0 - bloom_factor) + bloom * bloom_factor; + aa_color = mix(aa_color, bloom, bloom_factor); + #endif // Tonemapping float exposure_offset = 1.0; diff --git a/voxygen/src/menu/char_selection/mod.rs b/voxygen/src/menu/char_selection/mod.rs index 688d53f465..a5e96f3c20 100644 --- a/voxygen/src/menu/char_selection/mod.rs +++ b/voxygen/src/menu/char_selection/mod.rs @@ -257,11 +257,8 @@ impl PlayState for CharSelectionState { if let Some(mut second_pass) = drawer.second_pass() { second_pass.draw_clouds(); } - // Bloom - // TODO: make optional - { - drawer.run_bloom_passes() - } + // Bloom (does nothing if bloom is disabled) + drawer.run_bloom_passes(); // PostProcess and UI let mut third_pass = drawer.third_pass(); third_pass.draw_postprocess(); diff --git a/voxygen/src/render/mod.rs b/voxygen/src/render/mod.rs index d7598d5508..a2a8786c01 100644 --- a/voxygen/src/render/mod.rs +++ b/voxygen/src/render/mod.rs @@ -274,7 +274,49 @@ pub struct RenderMode { pub fluid: FluidMode, pub lighting: LightingMode, pub shadow: ShadowMode, + pub bloom: bool, + pub upscale_mode: UpscaleMode, pub present_mode: PresentMode, pub profiler_enabled: bool, } + +impl RenderMode { + fn split(self) -> (PipelineModes, OtherModes) { + ( + PipelineModes { + aa: self.aa, + cloud: self.cloud, + fluid: self.fluid, + lighting: self.lighting, + shadow: self.shadow, + bloom: self.bloom, + }, + OtherModes { + upscale_mode: self.upscale_mode, + present_mode: self.present_mode, + profiler_enabled: self.profiler_enabled, + }, + ) + } +} + +/// Render modes that require pipeline recreation (e.g. shader recompilation) +/// when changed +#[derive(PartialEq, Clone, Debug)] +pub struct PipelineModes { + aa: AaMode, + cloud: CloudMode, + fluid: FluidMode, + lighting: LightingMode, + pub shadow: ShadowMode, + bloom: bool, +} + +/// Other render modes that don't effect pipelines +#[derive(PartialEq, Clone, Debug)] +struct OtherModes { + upscale_mode: UpscaleMode, + present_mode: PresentMode, + profiler_enabled: bool, +} diff --git a/voxygen/src/render/pipelines/bloom.rs b/voxygen/src/render/pipelines/bloom.rs index 061ef0871f..64463278e2 100644 --- a/voxygen/src/render/pipelines/bloom.rs +++ b/voxygen/src/render/pipelines/bloom.rs @@ -18,11 +18,15 @@ pub struct BindGroup { #[repr(C)] #[derive(Copy, Clone, Debug, Zeroable, Pod)] -pub struct HalfPixel([f32; 2]); +pub struct Locals { + halfpixel: [f32; 2], +} -impl HalfPixel { +impl Locals { pub fn new(source_texture_resolution: Vec2) -> Self { - Self(source_texture_resolution.map(|e| 0.5 / e).into_array()) + Self { + halfpixel: source_texture_resolution.map(|e| 0.5 / e).into_array(), + } } } @@ -77,7 +81,7 @@ impl BloomLayout { device: &wgpu::Device, src_color: &wgpu::TextureView, sampler: &wgpu::Sampler, - half_pixel: Consts, + half_pixel: Consts, ) -> BindGroup { let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { label: None, diff --git a/voxygen/src/render/pipelines/postprocess.rs b/voxygen/src/render/pipelines/postprocess.rs index 4efac906f1..ccb83b52c1 100644 --- a/voxygen/src/render/pipelines/postprocess.rs +++ b/voxygen/src/render/pipelines/postprocess.rs @@ -1,4 +1,4 @@ -use super::super::{Consts, GlobalsLayouts}; +use super::super::{Consts, GlobalsLayouts, PipelineModes}; use bytemuck::{Pod, Zeroable}; use vek::*; @@ -31,55 +31,61 @@ pub struct PostProcessLayout { } impl PostProcessLayout { - pub fn new(device: &wgpu::Device) -> Self { + pub fn new(device: &wgpu::Device, pipeline_modes: &PipelineModes) -> Self { + let mut bind_entries = vec![ + // src color + 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, + }, + // Locals + wgpu::BindGroupLayoutEntry { + binding: 2, + visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + ]; + + if pipeline_modes.bloom { + bind_entries.push( + // src bloom + wgpu::BindGroupLayoutEntry { + binding: 3, + visibility: wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + ); + } + Self { layout: device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: None, - entries: &[ - // src color - 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, - }, - // TODO: make optional - // src bloom - wgpu::BindGroupLayoutEntry { - binding: 1, - 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: 2, - visibility: wgpu::ShaderStage::FRAGMENT, - ty: wgpu::BindingType::Sampler { - filtering: true, - comparison: false, - }, - count: None, - }, - // Locals - wgpu::BindGroupLayoutEntry { - binding: 3, - visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }, - ], + entries: &bind_entries, }), } } @@ -88,35 +94,42 @@ impl PostProcessLayout { &self, device: &wgpu::Device, src_color: &wgpu::TextureView, - src_bloom: &wgpu::TextureView, + src_bloom: Option<&wgpu::TextureView>, sampler: &wgpu::Sampler, locals: &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), - }, + let mut entries = vec![ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(src_color), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(sampler), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: locals.buf().as_entire_binding(), + }, + ]; + // Optional bloom source + if let Some(src_bloom) = src_bloom { + entries.push( // 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: 1, + binding: 3, resource: wgpu::BindingResource::TextureView(src_bloom), }, - wgpu::BindGroupEntry { - binding: 2, - resource: wgpu::BindingResource::Sampler(sampler), - }, - wgpu::BindGroupEntry { - binding: 3, - resource: locals.buf().as_entire_binding(), - }, - ], + ); + } + + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &self.layout, + entries: &entries, }); BindGroup { bind_group } diff --git a/voxygen/src/render/renderer.rs b/voxygen/src/render/renderer.rs index 9e923adf3d..a7f2f78d48 100644 --- a/voxygen/src/render/renderer.rs +++ b/voxygen/src/render/renderer.rs @@ -25,7 +25,8 @@ use super::{ GlobalsBindGroup, GlobalsLayouts, ShadowTexturesBindGroup, }, texture::Texture, - AaMode, AddressMode, FilterMode, RenderError, RenderMode, ShadowMapMode, ShadowMode, Vertex, + AaMode, AddressMode, FilterMode, OtherModes, PipelineModes, RenderError, RenderMode, + ShadowMapMode, ShadowMode, Vertex, }; use common::assets::{self, AssetExt, AssetHandle}; use common_base::span; @@ -45,8 +46,9 @@ pub type ColLightInfo = (Vec<[u8; 4]>, Vec2); const QUAD_INDEX_BUFFER_U16_START_VERT_LEN: u16 = 3000; const QUAD_INDEX_BUFFER_U32_START_VERT_LEN: u32 = 3000; -/// A type that stores all the layouts associated with this renderer. -struct Layouts { +/// A type that stores all the layouts associated with this renderer that never +/// change when the RenderMode is modified. +struct ImmutableLayouts { global: GlobalsLayouts, debug: debug::DebugLayout, @@ -56,11 +58,23 @@ struct Layouts { terrain: terrain::TerrainLayout, clouds: clouds::CloudsLayout, bloom: bloom::BloomLayout, - postprocess: postprocess::PostProcessLayout, ui: ui::UiLayout, blit: blit::BlitLayout, } +/// A type that stores all the layouts associated with this renderer. +struct Layouts { + immutable: Arc, + + postprocess: Arc, +} + +impl core::ops::Deref for Layouts { + type Target = ImmutableLayouts; + + fn deref(&self) -> &Self::Target { &self.immutable } +} + /// Render target views struct Views { // NOTE: unused for now, maybe... we will want it for something @@ -69,7 +83,7 @@ struct Views { tgt_color: wgpu::TextureView, tgt_depth: wgpu::TextureView, - bloom_tgts: [wgpu::TextureView; bloom::NUM_SIZES], + bloom_tgts: Option<[wgpu::TextureView; bloom::NUM_SIZES]>, // TODO: rename tgt_color_pp: wgpu::TextureView, } @@ -84,6 +98,7 @@ struct Shadow { /// 1. Only interface pipelines created /// 2. All of the pipelines have been created #[allow(clippy::large_enum_variant)] // They are both pretty large +#[allow(clippy::type_complexity)] enum State { // NOTE: this is used as a transient placeholder for moving things out of State temporarily Nothing, @@ -96,7 +111,19 @@ enum State { Complete { pipelines: Pipelines, shadow: Shadow, - recreating: Option>>, + recreating: Option< + PipelineCreation< + Result< + ( + Pipelines, + ShadowPipelines, + PipelineModes, + Arc, + ), + RenderError, + >, + >, + >, }, } @@ -115,11 +142,11 @@ pub struct Renderer { depth_sampler: wgpu::Sampler, state: State, - // true if there is a pending need to recreate the pipelines (e.g. RenderMode change or shader + // Some if there is a pending need to recreate the pipelines (e.g. RenderMode change or shader // hotloading) - recreation_pending: bool, + recreation_pending: Option, - layouts: Arc, + layouts: Layouts, // Note: we keep these here since their bind groups need to be updated if we resize the // color/depth textures locals: Locals, @@ -131,7 +158,8 @@ pub struct Renderer { shaders: AssetHandle, - mode: RenderMode, + pipeline_modes: PipelineModes, + other_modes: OtherModes, resolution: Vec2, // If this is Some then a screenshot will be taken and passed to the handler here @@ -155,7 +183,8 @@ pub struct Renderer { impl Renderer { /// Create a new `Renderer` from a variety of backend-specific components /// and the window targets. - pub fn new(window: &winit::window::Window, mut mode: RenderMode) -> Result { + pub fn new(window: &winit::window::Window, mode: RenderMode) -> Result { + let (pipeline_modes, mut other_modes) = mode.split(); // Enable seamless cubemaps globally, where available--they are essentially a // strict improvement on regular cube maps. // @@ -288,7 +317,7 @@ impl Renderer { format, width: dims.width, height: dims.height, - present_mode: mode.present_mode.into(), + present_mode: other_modes.present_mode.into(), }; let swap_chain = device.create_swap_chain(&surface, &sc_desc); @@ -296,7 +325,7 @@ impl Renderer { let shadow_views = ShadowMap::create_shadow_views( &device, (dims.width, dims.height), - &ShadowMapMode::try_from(mode.shadow).unwrap_or_default(), + &ShadowMapMode::try_from(pipeline_modes.shadow).unwrap_or_default(), ) .map_err(|err| { warn!("Could not create shadow map views: {:?}", err); @@ -315,11 +344,14 @@ impl Renderer { 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 postprocess = Arc::new(postprocess::PostProcessLayout::new( + &device, + &pipeline_modes, + )); let ui = ui::UiLayout::new(&device); let blit = blit::BlitLayout::new(&device); - Layouts { + let immutable = Arc::new(ImmutableLayouts { global, debug, @@ -329,22 +361,27 @@ impl Renderer { terrain, clouds, bloom, - postprocess, ui, blit, + }); + + Layouts { + immutable, + postprocess, } }; - // Arcify the device and layouts + // Arcify the device let device = Arc::new(device); - let layouts = Arc::new(layouts); let (interface_pipelines, creating) = pipeline_creation::initial_create_pipelines( - // TODO: combine Arcs? Arc::clone(&device), - Arc::clone(&layouts), + Layouts { + immutable: Arc::clone(&layouts.immutable), + postprocess: Arc::clone(&layouts.postprocess), + }, shaders.read().clone(), - mode.clone(), + pipeline_modes.clone(), sc_desc.clone(), // Note: cheap clone shadow_views.is_some(), )?; @@ -355,8 +392,12 @@ impl Renderer { creating, }; - let (views, bloom_sizes) = - Self::create_rt_views(&device, (dims.width, dims.height), &mode)?; + let (views, bloom_sizes) = Self::create_rt_views( + &device, + (dims.width, dims.height), + &pipeline_modes, + &other_modes, + ); let create_sampler = |filter| { device.create_sampler(&wgpu::SamplerDescriptor { @@ -385,8 +426,6 @@ 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()]); @@ -394,18 +433,16 @@ impl Renderer { &device, &layouts, clouds_locals, - bloom_locals, postprocess_locals, &views.tgt_color, &views.tgt_depth, - [ - &views.tgt_color_pp, - &views.bloom_tgts[1], - &views.bloom_tgts[2], - &views.bloom_tgts[3], - &views.bloom_tgts[4], - ], - &views.bloom_tgts[0], + views.bloom_tgts.as_ref().map(|tgts| locals::BloomParams { + locals: bloom_sizes.map(|size| { + Self::create_consts_inner(&device, &queue, &[bloom::Locals::new(size)]) + }), + src_views: [&views.tgt_color_pp, &tgts[1], &tgts[2], &tgts[3], &tgts[4]], + final_tgt_view: &tgts[0], + }), &views.tgt_color_pp, &sampler, &depth_sampler, @@ -416,9 +453,9 @@ impl Renderer { let quad_index_buffer_u32 = create_quad_index_buffer_u32(&device, QUAD_INDEX_BUFFER_U32_START_VERT_LEN as usize); let mut profiler = wgpu_profiler::GpuProfiler::new(4, queue.get_timestamp_period()); - mode.profiler_enabled &= profiler_features_enabled; - profiler.enable_timer = mode.profiler_enabled; - profiler.enable_debug_marker = mode.profiler_enabled; + other_modes.profiler_enabled &= profiler_features_enabled; + profiler.enable_timer = other_modes.profiler_enabled; + profiler.enable_debug_marker = other_modes.profiler_enabled; #[cfg(feature = "egui-ui")] let egui_renderpass = @@ -432,7 +469,7 @@ impl Renderer { sc_desc, state, - recreation_pending: false, + recreation_pending: None, layouts, locals, @@ -447,7 +484,8 @@ impl Renderer { shaders, - mode, + pipeline_modes, + other_modes, resolution: Vec2::new(dims.width, dims.height), take_screenshot: None, @@ -492,37 +530,47 @@ impl Renderer { /// Change the render mode. pub fn set_render_mode(&mut self, mode: RenderMode) -> Result<(), RenderError> { - // TODO: are there actually any issues with the current mode not matching the - // pipelines (since we could previously have inconsistencies from - // pipelines failing to build due to shader editing)? - // TODO: FIXME: defer mode changing until pipelines are rebuilt to prevent - // incompatibilities as pipelines are now rebuilt in a deferred mannder in the - // background TODO: consider separating changes that don't require - // rebuilding pipelines - self.mode = mode; - self.sc_desc.present_mode = self.mode.present_mode.into(); + let (pipeline_modes, other_modes) = mode.split(); - // Only enable profiling if the wgpu features are enabled - self.mode.profiler_enabled &= self.profiler_features_enabled; - // Enable/disable profiler - if !self.mode.profiler_enabled { - // Clear the times if disabled - core::mem::take(&mut self.profile_times); + if self.other_modes != other_modes { + self.other_modes = other_modes; + + // Update present mode in swap chain descriptor + self.sc_desc.present_mode = self.other_modes.present_mode.into(); + + // Only enable profiling if the wgpu features are enabled + self.other_modes.profiler_enabled &= self.profiler_features_enabled; + // Enable/disable profiler + if !self.other_modes.profiler_enabled { + // Clear the times if disabled + core::mem::take(&mut self.profile_times); + } + self.profiler.enable_timer = self.other_modes.profiler_enabled; + self.profiler.enable_debug_marker = self.other_modes.profiler_enabled; + + // Recreate render target + self.on_resize(self.resolution); } - self.profiler.enable_timer = self.mode.profiler_enabled; - self.profiler.enable_debug_marker = self.mode.profiler_enabled; - // Recreate render target - self.on_resize(self.resolution)?; - - // Recreate pipelines with the new AA mode - self.recreate_pipelines(); + // We can't cancel the pending recreation even if the new settings are equal + // to the current ones becuase the recreation could be triggered by something + // else like shader hotloading + if dbg!(self.pipeline_modes != pipeline_modes) + || dbg!( + self.recreation_pending + .as_ref() + .map_or(false, |modes| modes != &pipeline_modes) + ) + { + // Recreate pipelines with new modes + self.recreate_pipelines(pipeline_modes); + } Ok(()) } - /// Get the render mode. - pub fn render_mode(&self) -> &RenderMode { &self.mode } + /// Get the pipelines mode. + pub fn pipeline_modes(&self) -> &PipelineModes { &self.pipeline_modes } /// Get the current profiling times /// Nested timings immediately follow their parent @@ -552,7 +600,7 @@ impl Renderer { } /// Resize internal render targets to match window render target dimensions. - pub fn on_resize(&mut self, dims: Vec2) -> Result<(), RenderError> { + pub fn on_resize(&mut self, dims: Vec2) { // Avoid panics when creating texture with w,h of 0,0. if dims.x != 0 && dims.y != 0 { self.is_minimized = false; @@ -563,26 +611,36 @@ impl Renderer { self.swap_chain = self.device.create_swap_chain(&self.surface, &self.sc_desc); // Resize other render targets - let (views, bloom_sizes) = - 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.pipeline_modes, + &self.other_modes, + ); self.views = views; - let bloom_locals = - bloom_sizes.map(|size| self.create_consts(&[bloom::HalfPixel::new(size)])); - // Rebind views to clouds/postprocess bind groups + + // appease borrow check + let device = &self.device; + let queue = &self.queue; + let views = &self.views; + let bloom_params = self + .views + .bloom_tgts + .as_ref() + .map(|tgts| locals::BloomParams { + locals: bloom_sizes.map(|size| { + Self::create_consts_inner(device, queue, &[bloom::Locals::new(size)]) + }), + src_views: [&views.tgt_color_pp, &tgts[1], &tgts[2], &tgts[3], &tgts[4]], + final_tgt_view: &tgts[0], + }); + self.locals.rebind( &self.device, &self.layouts, - bloom_locals, &self.views.tgt_color, &self.views.tgt_depth, - [ - &self.views.tgt_color_pp, - &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], + bloom_params, &self.views.tgt_color_pp, &self.sampler, &self.depth_sampler, @@ -606,7 +664,7 @@ impl Renderer { }; if let (Some((point_depth, directed_depth)), ShadowMode::Map(mode)) = - (shadow_views, self.mode.shadow) + (shadow_views, self.pipeline_modes.shadow) { match ShadowMap::create_shadow_views(&self.device, (dims.x, dims.y), &mode) { Ok((new_point_depth, new_directed_depth)) => { @@ -638,8 +696,6 @@ impl Renderer { } else { self.is_minimized = true; } - - Ok(()) } pub fn maintain(&self) { @@ -654,12 +710,13 @@ impl Renderer { fn create_rt_views( device: &wgpu::Device, size: (u32, u32), - mode: &RenderMode, - ) -> Result<(Views, [Vec2; bloom::NUM_SIZES]), RenderError> { + pipeline_modes: &PipelineModes, + other_modes: &OtherModes, + ) -> (Views, [Vec2; bloom::NUM_SIZES]) { let upscaled = Vec2::::from(size) - .map(|e| (e as f32 * mode.upscale_mode.factor) as u32) + .map(|e| (e as f32 * other_modes.upscale_mode.factor) as u32) .into_tuple(); - let (width, height, sample_count) = match mode.aa { + let (width, height, sample_count) = match pipeline_modes.aa { AaMode::None | AaMode::Fxaa => (upscaled.0, upscaled.1, 1), AaMode::MsaaX4 => (upscaled.0, upscaled.1, 4), AaMode::MsaaX8 => (upscaled.0, upscaled.1, 8), @@ -707,7 +764,9 @@ impl Renderer { size }); - let bloom_tgt_views = bloom_sizes.map(|size| color_view(size.x, size.y)); + let bloom_tgt_views = pipeline_modes + .bloom + .then(|| bloom_sizes.map(|size| color_view(size.x, size.y))); let tgt_depth_tex = device.create_texture(&wgpu::TextureDescriptor { label: None, @@ -758,7 +817,7 @@ impl Renderer { array_layer_count: None, }); - Ok(( + ( Views { tgt_color: tgt_color_view, tgt_depth: tgt_depth_view, @@ -767,7 +826,7 @@ impl Renderer { _win_depth: win_depth_view, }, bloom_sizes.map(|s| s.map(|e| e as f32)), - )) + ) } /// Get the resolution of the render target. @@ -841,7 +900,7 @@ impl Renderer { } // Try to get the latest profiling results - if self.mode.profiler_enabled { + if self.other_modes.profiler_enabled { // Note: this lags a few frames behind if let Some(profile_times) = self.profiler.process_finished_frame() { self.profile_times = profile_times; @@ -851,6 +910,10 @@ impl Renderer { // Handle polling background pipeline creation/recreation // Temporarily set to nothing and then replace in the statement below let state = core::mem::replace(&mut self.state, State::Nothing); + // Indicator for if pipeline recreation finished and we need to recreate bind + // groups / render targets (handling defered so that State will be valid + // when calling Self::on_resize) + let mut trigger_on_resize = false; // If still creating initial pipelines, check if complete self.state = if let State::Interface { pipelines: interface, @@ -906,7 +969,7 @@ impl Renderer { } = state { match recreating.try_complete() { - Ok(Ok((pipelines, shadow_pipelines))) => { + Ok(Ok((pipelines, shadow_pipelines, new_pipeline_modes, postprocess_layout))) => { if let ( Some(point_pipeline), Some(terrain_directed_pipeline), @@ -922,6 +985,14 @@ impl Renderer { shadow_map.terrain_directed_pipeline = terrain_directed_pipeline; shadow_map.figure_directed_pipeline = figure_directed_pipeline; } + + self.pipeline_modes = new_pipeline_modes; + self.layouts.postprocess = postprocess_layout; + // TODO: we have the potential to skip recreating bindings / render targets on + // pipeline recreation trigged by shader reloading (would need to ensure new + // postprocess_layout is not created...) + trigger_on_resize = true; + State::Complete { pipelines, shadow, @@ -947,17 +1018,26 @@ impl Renderer { state }; + // Call on_resize to recreate render targets and their bind groups if the + // pipelines were recreated with a new postprocess layout and or changes in the + // render modes + if trigger_on_resize { + self.on_resize(self.resolution); + } + // If the shaders files were changed attempt to recreate the shaders if self.shaders.reloaded() { - self.recreate_pipelines(); + self.recreate_pipelines(self.pipeline_modes.clone()); } // Or if we have a recreation pending - if self.recreation_pending - && matches!(&self.state, State::Complete { recreating, .. } if recreating.is_none()) - { - self.recreation_pending = false; - self.recreate_pipelines(); + if matches!(&self.state, State::Complete { + recreating: None, + .. + }) { + if let Some(new_pipeline_modes) = self.recreation_pending.take() { + self.recreate_pipelines(new_pipeline_modes); + } } let tex = match self.swap_chain.get_current_frame() { @@ -965,7 +1045,8 @@ impl Renderer { // If lost recreate the swap chain Err(err @ wgpu::SwapChainError::Lost) => { warn!("{}. Recreating swap chain. A frame will be missed", err); - return self.on_resize(self.resolution).map(|()| None); + self.on_resize(self.resolution); + return Ok(None); }, Err(wgpu::SwapChainError::Timeout) => { // This will probably be resolved on the next frame @@ -990,21 +1071,24 @@ impl Renderer { } /// Recreate the pipelines - fn recreate_pipelines(&mut self) { + fn recreate_pipelines(&mut self, pipeline_modes: PipelineModes) { match &mut self.state { State::Complete { recreating, .. } if recreating.is_some() => { // Defer recreation so that we are not building multiple sets of pipelines in // the background at once - self.recreation_pending = true; + self.recreation_pending = Some(pipeline_modes); }, State::Complete { recreating, shadow, .. } => { *recreating = Some(pipeline_creation::recreate_pipelines( Arc::clone(&self.device), - Arc::clone(&self.layouts), + Arc::clone(&self.layouts.immutable), self.shaders.read().clone(), - self.mode.clone(), + self.pipeline_modes.clone(), + // NOTE: if present_mode starts to be used to configure pipelines then it needs + // to become a part of the pipeline modes (note here since the present mode is + // accessible through the swap chain descriptor) self.sc_desc.clone(), // Note: cheap clone shadow.map.is_enabled(), )); @@ -1012,7 +1096,7 @@ impl Renderer { State::Interface { .. } => { // Defer recreation so that we are not building multiple sets of pipelines in // the background at once - self.recreation_pending = true; + self.recreation_pending = Some(pipeline_modes); }, State::Nothing => {}, } @@ -1229,7 +1313,7 @@ impl Renderer { // Queue screenshot self.take_screenshot = Some(Box::new(screenshot_handler)); // Take profiler snapshot - if self.mode.profiler_enabled { + if self.other_modes.profiler_enabled { let file_name = format!( "frame-trace_{}.json", std::time::SystemTime::now() diff --git a/voxygen/src/render/renderer/drawer.rs b/voxygen/src/render/renderer/drawer.rs index 4c5bc22887..16f67b76f2 100644 --- a/voxygen/src/render/renderer/drawer.rs +++ b/voxygen/src/render/renderer/drawer.rs @@ -61,7 +61,7 @@ struct RendererBorrow<'frame> { pipelines: Pipelines<'frame>, locals: &'frame super::locals::Locals, views: &'frame super::Views, - mode: &'frame super::super::RenderMode, + pipeline_modes: &'frame super::super::PipelineModes, quad_index_buffer_u16: &'frame Buffer, quad_index_buffer_u32: &'frame Buffer, #[cfg(feature = "egui-ui")] @@ -112,7 +112,7 @@ impl<'frame> Drawer<'frame> { pipelines, locals: &renderer.locals, views: &renderer.views, - mode: &renderer.mode, + pipeline_modes: &renderer.pipeline_modes, quad_index_buffer_u16: &renderer.quad_index_buffer_u16, quad_index_buffer_u32: &renderer.quad_index_buffer_u32, #[cfg(feature = "egui-ui")] @@ -131,13 +131,13 @@ impl<'frame> Drawer<'frame> { } } - /// Get the render mode. - pub fn render_mode(&self) -> &super::super::RenderMode { self.borrow.mode } + /// Get the pipeline modes. + pub fn pipeline_modes(&self) -> &super::super::PipelineModes { self.borrow.pipeline_modes } /// Returns None if the shadow renderer is not enabled at some level or the /// pipelines are not available yet pub fn shadow_pass(&mut self) -> Option { - if !self.borrow.mode.shadow.is_map() { + if !self.borrow.pipeline_modes.shadow.is_map() { return None; } @@ -243,15 +243,24 @@ 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 + /// does nothing if bloom is disabled 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 bloom_pipelines = match self.borrow.pipelines.all() { + Some(super::Pipelines { bloom: Some(p), .. }) => p, + _ => return, + }; + + // TODO: consider consolidating optional bloom bind groups and optional pipeline + // into a single structure? + let (bloom_tgts, bloom_binds) = + match views.bloom_tgts.as_ref().zip(locals.bloom_binds.as_ref()) { + Some((t, b)) => (t, b), + None => return, + }; + let device = self.borrow.device; let mut encoder = self.encoder.as_mut().unwrap().scope("bloom", device); @@ -275,18 +284,18 @@ impl<'frame> Drawer<'frame> { // 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 bind = &bloom_binds[index].bind_group; + let view = &bloom_tgts[index + 1]; // Do filtering out of non-bright things during the first downsample let (label, pipeline) = if index == 0 { ( format!("downsample filtered {}", index + 1), - &pipelines.bloom.downsample_filtered, + &bloom_pipelines.downsample_filtered, ) } else { ( format!("downsample {}", index + 1), - &pipelines.bloom.downsample, + &bloom_pipelines.downsample, ) }; run_bloom_pass( @@ -300,14 +309,14 @@ impl<'frame> Drawer<'frame> { // 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 bind = &bloom_binds[bloom::NUM_SIZES - 1 - index].bind_group; + let view = &bloom_tgts[bloom::NUM_SIZES - 2 - index]; let label = format!("upsample {}", index + 1); run_bloom_pass( bind, view, label, - &pipelines.bloom.upsample, + &bloom_pipelines.upsample, if index + 2 == bloom::NUM_SIZES { // Clear for the final image since that is just stuff from the pervious frame. wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT) @@ -400,7 +409,7 @@ impl<'frame> Drawer<'frame> { chunks: impl Clone + Iterator, &'data terrain::BoundLocals)>, ) { - if !self.borrow.mode.shadow.is_map() { + if !self.borrow.pipeline_modes.shadow.is_map() { return; } diff --git a/voxygen/src/render/renderer/locals.rs b/voxygen/src/render/renderer/locals.rs index 8a3c549604..fb3b0301ac 100644 --- a/voxygen/src/render/renderer/locals.rs +++ b/voxygen/src/render/renderer/locals.rs @@ -6,11 +6,17 @@ use super::{ Layouts, }; +pub struct BloomParams<'a> { + pub locals: [Consts; bloom::NUM_SIZES], + pub src_views: [&'a wgpu::TextureView; bloom::NUM_SIZES], + pub final_tgt_view: &'a wgpu::TextureView, +} + pub struct Locals { pub clouds: Consts, pub clouds_bind: clouds::BindGroup, - pub bloom_binds: [bloom::BindGroup; bloom::NUM_SIZES], + pub bloom_binds: Option<[bloom::BindGroup; bloom::NUM_SIZES]>, pub postprocess: Consts, pub postprocess_bind: postprocess::BindGroup, @@ -21,12 +27,10 @@ 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, + bloom: Option, tgt_color_pp_view: &wgpu::TextureView, sampler: &wgpu::Sampler, depth_sampler: &wgpu::Sampler, @@ -39,18 +43,22 @@ impl Locals { depth_sampler, &clouds_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, + bloom.as_ref().map(|b| b.final_tgt_view), sampler, &postprocess_locals, ); + let bloom_binds = bloom.map(|bloom| { + bloom + .src_views + .zip(bloom.locals) // zip arrays + .map(|(view, locals)| layouts.bloom.bind(device, view, sampler, locals)) + }); + Self { clouds: clouds_locals, clouds_bind, @@ -66,11 +74,9 @@ 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, + bloom: Option, tgt_color_pp_view: &wgpu::TextureView, sampler: &wgpu::Sampler, depth_sampler: &wgpu::Sampler, @@ -83,15 +89,18 @@ impl Locals { depth_sampler, &self.clouds, ); - 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, + bloom.as_ref().map(|b| b.final_tgt_view), sampler, &self.postprocess, ); + self.bloom_binds = bloom.map(|bloom| { + bloom + .src_views + .zip(bloom.locals) // zip arrays + .map(|(view, locals)| layouts.bloom.bind(device, view, sampler, locals)) + }); } } diff --git a/voxygen/src/render/renderer/pipeline_creation.rs b/voxygen/src/render/renderer/pipeline_creation.rs index 10d02ba298..7331504271 100644 --- a/voxygen/src/render/renderer/pipeline_creation.rs +++ b/voxygen/src/render/renderer/pipeline_creation.rs @@ -4,10 +4,10 @@ use super::{ blit, bloom, clouds, debug, figure, fluid, lod_terrain, particle, postprocess, shadow, skybox, sprite, terrain, ui, }, - AaMode, CloudMode, FluidMode, LightingMode, RenderError, RenderMode, ShadowMode, + AaMode, CloudMode, FluidMode, LightingMode, PipelineModes, RenderError, ShadowMode, }, shaders::Shaders, - Layouts, + ImmutableLayouts, Layouts, }; use common_base::prof_span; use std::sync::Arc; @@ -20,7 +20,7 @@ pub struct Pipelines { pub lod_terrain: lod_terrain::LodTerrainPipeline, pub particle: particle::ParticlePipeline, pub clouds: clouds::CloudsPipeline, - pub bloom: bloom::BloomPipelines, + pub bloom: Option, pub postprocess: postprocess::PostProcessPipeline, // Consider reenabling at some time // player_shadow: figure::FigurePipeline, @@ -40,7 +40,7 @@ pub struct IngamePipelines { lod_terrain: lod_terrain::LodTerrainPipeline, particle: particle::ParticlePipeline, clouds: clouds::CloudsPipeline, - bloom: bloom::BloomPipelines, + pub bloom: Option, postprocess: postprocess::PostProcessPipeline, // Consider reenabling at some time // player_shadow: figure::FigurePipeline, @@ -126,7 +126,7 @@ impl ShaderModules { pub fn new( device: &wgpu::Device, shaders: &Shaders, - mode: &RenderMode, + pipeline_modes: &PipelineModes, has_shadow_views: bool, ) -> Result { prof_span!(_guard, "ShaderModules::new"); @@ -151,16 +151,17 @@ impl ShaderModules { #define CLOUD_MODE {} #define LIGHTING_ALGORITHM {} #define SHADOW_MODE {} +#define BLOOM {} "#, &constants.0, // TODO: Configurable vertex/fragment shader preference. "VOXYGEN_COMPUTATION_PREFERENCE_FRAGMENT", - match mode.fluid { + match pipeline_modes.fluid { FluidMode::Cheap => "FLUID_MODE_CHEAP", FluidMode::Shiny => "FLUID_MODE_SHINY", }, - match mode.cloud { + match pipeline_modes.cloud { CloudMode::None => "CLOUD_MODE_NONE", CloudMode::Minimal => "CLOUD_MODE_MINIMAL", CloudMode::Low => "CLOUD_MODE_LOW", @@ -168,20 +169,25 @@ impl ShaderModules { CloudMode::High => "CLOUD_MODE_HIGH", CloudMode::Ultra => "CLOUD_MODE_ULTRA", }, - match mode.lighting { + match pipeline_modes.lighting { LightingMode::Ashikhmin => "LIGHTING_ALGORITHM_ASHIKHMIN", LightingMode::BlinnPhong => "LIGHTING_ALGORITHM_BLINN_PHONG", LightingMode::Lambertian => "LIGHTING_ALGORITHM_LAMBERTIAN", }, - match mode.shadow { + match pipeline_modes.shadow { ShadowMode::None => "SHADOW_MODE_NONE", ShadowMode::Map(_) if has_shadow_views => "SHADOW_MODE_MAP", ShadowMode::Cheap | ShadowMode::Map(_) => "SHADOW_MODE_CHEAP", }, + if dbg!(pipeline_modes.bloom) { + "BLOOM_ENABLED" + } else { + "BLOOM_DISABLED" + }, ); let anti_alias = shaders - .get(match mode.aa { + .get(match pipeline_modes.aa { AaMode::None => "antialias.none", AaMode::Fxaa => "antialias.fxaa", AaMode::MsaaX4 => "antialias.msaa-x4", @@ -191,7 +197,7 @@ impl ShaderModules { .unwrap(); let cloud = shaders - .get(match mode.cloud { + .get(match pipeline_modes.cloud { CloudMode::None => "include.cloud.none", _ => "include.cloud.regular", }) @@ -234,7 +240,7 @@ impl ShaderModules { create_shader_module(device, &mut compiler, glsl, kind, &file_name, &options) }; - let selected_fluid_shader = ["fluid-frag.", match mode.fluid { + let selected_fluid_shader = ["fluid-frag.", match pipeline_modes.fluid { FluidMode::Cheap => "cheap", FluidMode::Shiny => "shiny", }] @@ -317,7 +323,7 @@ struct PipelineNeeds<'a> { device: &'a wgpu::Device, layouts: &'a Layouts, shaders: &'a ShaderModules, - mode: &'a RenderMode, + pipeline_modes: &'a PipelineModes, sc_desc: &'a wgpu::SwapChainDescriptor, } @@ -380,7 +386,7 @@ fn create_ingame_and_shadow_pipelines( device, layouts, shaders, - mode, + pipeline_modes, sc_desc, } = needs; @@ -416,7 +422,7 @@ fn create_ingame_and_shadow_pipelines( &shaders.debug_frag, &layouts.global, &layouts.debug, - mode.aa, + pipeline_modes.aa, ) }, "debug pipeline creation", @@ -431,7 +437,7 @@ fn create_ingame_and_shadow_pipelines( &shaders.skybox_vert, &shaders.skybox_frag, &layouts.global, - mode.aa, + pipeline_modes.aa, ) }, "skybox pipeline creation", @@ -447,7 +453,7 @@ fn create_ingame_and_shadow_pipelines( &shaders.figure_frag, &layouts.global, &layouts.figure, - mode.aa, + pipeline_modes.aa, ) }, "figure pipeline creation", @@ -463,7 +469,7 @@ fn create_ingame_and_shadow_pipelines( &shaders.terrain_frag, &layouts.global, &layouts.terrain, - mode.aa, + pipeline_modes.aa, ) }, "terrain pipeline creation", @@ -479,7 +485,7 @@ fn create_ingame_and_shadow_pipelines( &shaders.fluid_frag, &layouts.global, &layouts.terrain, - mode.aa, + pipeline_modes.aa, ) }, "fluid pipeline creation", @@ -496,7 +502,7 @@ fn create_ingame_and_shadow_pipelines( &layouts.global, &layouts.sprite, &layouts.terrain, - mode.aa, + pipeline_modes.aa, ) }, "sprite pipeline creation", @@ -511,7 +517,7 @@ fn create_ingame_and_shadow_pipelines( &shaders.particle_vert, &shaders.particle_frag, &layouts.global, - mode.aa, + pipeline_modes.aa, ) }, "particle pipeline creation", @@ -526,7 +532,7 @@ fn create_ingame_and_shadow_pipelines( &shaders.lod_terrain_vert, &shaders.lod_terrain_frag, &layouts.global, - mode.aa, + pipeline_modes.aa, ) }, "lod terrain pipeline creation", @@ -542,7 +548,7 @@ fn create_ingame_and_shadow_pipelines( &shaders.clouds_frag, &layouts.global, &layouts.clouds, - mode.aa, + pipeline_modes.aa, ) }, "clouds pipeline creation", @@ -552,15 +558,17 @@ fn create_ingame_and_shadow_pipelines( let create_bloom = || { bloom_task.run( || { - bloom::BloomPipelines::new( - device, - &shaders.blit_vert, - &shaders.dual_downsample_filtered_frag, - &shaders.dual_downsample_frag, - &shaders.dual_upsample_frag, - wgpu::TextureFormat::Rgba16Float, - &layouts.bloom, - ) + pipeline_modes.bloom.then(|| { + bloom::BloomPipelines::new( + device, + &shaders.blit_vert, + &shaders.dual_downsample_filtered_frag, + &shaders.dual_downsample_frag, + &shaders.dual_upsample_frag, + wgpu::TextureFormat::Rgba16Float, + &layouts.bloom, + ) + }) }, "bloom pipelines creation", ) @@ -614,7 +622,7 @@ fn create_ingame_and_shadow_pipelines( &shaders.point_light_shadows_vert, &layouts.global, &layouts.terrain, - mode.aa, + pipeline_modes.aa, ) }, "point shadow pipeline creation", @@ -629,7 +637,7 @@ fn create_ingame_and_shadow_pipelines( &shaders.light_shadows_directed_vert, &layouts.global, &layouts.terrain, - mode.aa, + pipeline_modes.aa, ) }, "terrain directed shadow pipeline creation", @@ -644,7 +652,7 @@ fn create_ingame_and_shadow_pipelines( &shaders.light_shadows_figure_vert, &layouts.global, &layouts.figure, - mode.aa, + pipeline_modes.aa, ) }, "figure directed shadow pipeline creation", @@ -706,9 +714,9 @@ fn create_ingame_and_shadow_pipelines( /// NOTE: this tries to use all the CPU cores to complete as soon as possible pub(super) fn initial_create_pipelines( device: Arc, - layouts: Arc, + layouts: Layouts, shaders: Shaders, - mode: RenderMode, + pipeline_modes: PipelineModes, sc_desc: wgpu::SwapChainDescriptor, has_shadow_views: bool, ) -> Result< @@ -721,7 +729,7 @@ pub(super) fn initial_create_pipelines( prof_span!(_guard, "initial_create_pipelines"); // Process shaders into modules - let shader_modules = ShaderModules::new(&device, &shaders, &mode, has_shadow_views)?; + let shader_modules = ShaderModules::new(&device, &shaders, &pipeline_modes, has_shadow_views)?; // Create threadpool for parallel portion let pool = rayon::ThreadPoolBuilder::new() @@ -733,7 +741,7 @@ pub(super) fn initial_create_pipelines( device: &device, layouts: &layouts, shaders: &shader_modules, - mode: &mode, + pipeline_modes: &pipeline_modes, sc_desc: &sc_desc, }; @@ -760,7 +768,7 @@ pub(super) fn initial_create_pipelines( device: &device, layouts: &layouts, shaders: &shader_modules, - mode: &mode, + pipeline_modes: &pipeline_modes, sc_desc: &sc_desc, }; @@ -776,14 +784,25 @@ pub(super) fn initial_create_pipelines( /// Use this to recreate all the pipelines in the background. /// TODO: report progress /// NOTE: this tries to use all the CPU cores to complete as soon as possible +#[allow(clippy::type_complexity)] pub(super) fn recreate_pipelines( device: Arc, - layouts: Arc, + immutable_layouts: Arc, shaders: Shaders, - mode: RenderMode, + pipeline_modes: PipelineModes, sc_desc: wgpu::SwapChainDescriptor, has_shadow_views: bool, -) -> PipelineCreation> { +) -> PipelineCreation< + Result< + ( + Pipelines, + ShadowPipelines, + PipelineModes, + Arc, + ), + RenderError, + >, +> { prof_span!(_guard, "recreate_pipelines"); // Create threadpool for parallel portion @@ -811,20 +830,32 @@ pub(super) fn recreate_pipelines( // Process shaders into modules let guard = shader_task.start("process shaders"); - let shader_modules = match ShaderModules::new(&device, &shaders, &mode, has_shadow_views) { - Ok(modules) => modules, - Err(err) => { - result_send.send(Err(err)).expect("Channel disconnected"); - return; - }, - }; + let shader_modules = + match ShaderModules::new(&device, &shaders, &pipeline_modes, has_shadow_views) { + Ok(modules) => modules, + Err(err) => { + result_send.send(Err(err)).expect("Channel disconnected"); + return; + }, + }; drop(guard); + // Create new postprocess layouts + let postprocess_layouts = Arc::new(postprocess::PostProcessLayout::new( + &device, + &pipeline_modes, + )); + + let layouts = Layouts { + immutable: immutable_layouts, + postprocess: postprocess_layouts, + }; + let needs = PipelineNeeds { device: &device, layouts: &layouts, shaders: &shader_modules, - mode: &mode, + pipeline_modes: &pipeline_modes, sc_desc: &sc_desc, }; @@ -837,7 +868,12 @@ pub(super) fn recreate_pipelines( // Send them result_send - .send(Ok((Pipelines::consolidate(interface, ingame), shadow))) + .send(Ok(( + Pipelines::consolidate(interface, ingame), + shadow, + pipeline_modes, + layouts.postprocess, + ))) .expect("Channel disconnected"); }); diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs index 9fac2b5fff..6ec10087d4 100644 --- a/voxygen/src/scene/figure/mod.rs +++ b/voxygen/src/scene/figure/mod.rs @@ -518,7 +518,7 @@ impl FigureMgr { let ray_direction = scene_data.get_sun_dir(); let is_daylight = ray_direction.z < 0.0/*0.6*/; // Are shadows enabled at all? - let can_shadow_sun = renderer.render_mode().shadow.is_map() && is_daylight; + let can_shadow_sun = renderer.pipeline_modes().shadow.is_map() && is_daylight; let Dependents { proj_mat: _, view_mat: _, diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index c7659e18ca..4f6d0af4d5 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -681,7 +681,7 @@ impl Scene { let sun_dir = scene_data.get_sun_dir(); let is_daylight = sun_dir.z < 0.0; - if renderer.render_mode().shadow.is_map() && (is_daylight || !lights.is_empty()) { + if renderer.pipeline_modes().shadow.is_map() && (is_daylight || !lights.is_empty()) { let fov = self.camera.get_fov(); let aspect_ratio = self.camera.get_aspect_ratio(); @@ -1062,7 +1062,7 @@ impl Scene { let camera_data = (&self.camera, scene_data.figure_lod_render_distance); // would instead have this as an extension. - if drawer.render_mode().shadow.is_map() && (is_daylight || !self.light_data.is_empty()) { + if drawer.pipeline_modes().shadow.is_map() && (is_daylight || !self.light_data.is_empty()) { if is_daylight { prof_span!("directed shadows"); if let Some(mut shadow_pass) = drawer.shadow_pass() { diff --git a/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs index dae0847392..31d06afd4e 100644 --- a/voxygen/src/scene/terrain.rs +++ b/voxygen/src/scene/terrain.rs @@ -1244,7 +1244,7 @@ impl Terrain { return min.partial_cmple(&max).reduce_and(); }; let (visible_light_volume, visible_psr_bounds) = if ray_direction.z < 0.0 - && renderer.render_mode().shadow.is_map() + && renderer.pipeline_modes().shadow.is_map() { let visible_bounding_box = math::Aabb:: { min: math::Vec3::from(visible_bounding_box.min - focus_off), diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs index eaf2469d95..23c8bed695 100644 --- a/voxygen/src/session/mod.rs +++ b/voxygen/src/session/mod.rs @@ -1483,8 +1483,7 @@ impl PlayState for SessionState { second_pass.draw_clouds(); } } - // Bloom - // TODO: make optional + // Bloom (call does nothing if bloom is off) { prof_span!("bloom"); drawer.run_bloom_passes() diff --git a/voxygen/src/settings/graphics.rs b/voxygen/src/settings/graphics.rs index 1155a7a131..55cc52ba6b 100644 --- a/voxygen/src/settings/graphics.rs +++ b/voxygen/src/settings/graphics.rs @@ -43,6 +43,7 @@ pub struct GraphicsSettings { pub window_size: [u16; 2], pub fullscreen: FullScreenSettings, pub lod_detail: u32, + pub bloom_enabled: bool, } impl Default for GraphicsSettings { @@ -62,6 +63,7 @@ impl Default for GraphicsSettings { window_size: [1280, 720], fullscreen: FullScreenSettings::default(), lod_detail: 250, + bloom_enabled: false, } } } diff --git a/voxygen/src/window.rs b/voxygen/src/window.rs index 4f2c3da9d4..f29c197fb6 100644 --- a/voxygen/src/window.rs +++ b/voxygen/src/window.rs @@ -791,8 +791,7 @@ impl Window { let physical = self.window.inner_size(); self.renderer - .on_resize(Vec2::new(physical.width, physical.height)) - .unwrap(); + .on_resize(Vec2::new(physical.width, physical.height)); // TODO: update users of this event with the fact that it is now the physical // size let winit::dpi::PhysicalSize { width, height } = physical; From 48d7e31b4187b620ec58bbc7a70086b55814ff16 Mon Sep 17 00:00:00 2001 From: Imbris Date: Sun, 1 Aug 2021 00:15:20 -0400 Subject: [PATCH 7/8] Enhance bloom::NUM_SIZES docs, add TODO, remove dbgs --- voxygen/src/render/pipelines/bloom.rs | 39 +++++++++++++++++-- voxygen/src/render/renderer.rs | 13 +++---- .../src/render/renderer/pipeline_creation.rs | 2 +- 3 files changed, 42 insertions(+), 12 deletions(-) diff --git a/voxygen/src/render/pipelines/bloom.rs b/voxygen/src/render/pipelines/bloom.rs index 64463278e2..c1e4d9055b 100644 --- a/voxygen/src/render/pipelines/bloom.rs +++ b/voxygen/src/render/pipelines/bloom.rs @@ -1,4 +1,6 @@ //! 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 +//! +//! See additional details in the [NUM_SIZES] docs use super::super::Consts; use bytemuck::{Pod, Zeroable}; @@ -6,10 +8,39 @@ use vek::*; // TODO: auto-tune the number of passes to maintain roughly constant blur per // unit of FOV so changing resolution / FOV doesn't change the blur appearance -// significantly -/// 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. +// significantly. +// +/// Blurring is performed while downsampling to the smaller sizes in steps and +/// then upsampling back up to the original resolution. Each level is half the +/// size in both dimensions from the previous. For instance with 5 distinct +/// sizes there is a total of 8 passes going from the largest to the smallest to +/// the largest again: +/// +/// 1 -> 1/2 -> 1/4 -> 1/8 -> 1/16 -> 1/8 -> 1/4 -> 1/2 -> 1 +/// ~~~~ +/// [downsampling] smallest [upsampling] +/// +/// The textures used for downsampling are re-used when upsampling. +/// +/// Additionally, instead of clearing them the colors are added together in an +/// attempt to obtain a more concentrated bloom near bright areas rather than +/// a uniform blur. In the example above, the added layers would include 1/8, +/// 1/4, and 1/2. The smallest size is not upsampled to and the original full +/// resolution has no blurring and we are already combining the bloom into the +/// full resolution image in a later step, so they are not included here. The 3 +/// extra layers added in mean the total luminosity of the final blurred bloom +/// image will be 4 times more than the input image. To account for this, we +/// divide the bloom intensity by 4 before applying it. +/// +/// Nevertheless, we have not fully evaluated how this visually compares to the +/// bloom obtained without adding with the previous layers so there is the +/// potential for further artistic investigation here. +/// +/// NOTE: This constant includes the full resolution size and it is +/// assumed that there will be at least one smaller image to downsample to and +/// upsample back from (otherwise no blurring would be done). Thus, the minimum +/// valid value is 2 and panicking indexing operations we perform assume this +/// will be at least 2. pub const NUM_SIZES: usize = 5; pub struct BindGroup { diff --git a/voxygen/src/render/renderer.rs b/voxygen/src/render/renderer.rs index a7f2f78d48..86580d0f7a 100644 --- a/voxygen/src/render/renderer.rs +++ b/voxygen/src/render/renderer.rs @@ -555,12 +555,11 @@ impl Renderer { // We can't cancel the pending recreation even if the new settings are equal // to the current ones becuase the recreation could be triggered by something // else like shader hotloading - if dbg!(self.pipeline_modes != pipeline_modes) - || dbg!( - self.recreation_pending - .as_ref() - .map_or(false, |modes| modes != &pipeline_modes) - ) + if self.pipeline_modes != pipeline_modes + || self + .recreation_pending + .as_ref() + .map_or(false, |modes| modes != &pipeline_modes) { // Recreate pipelines with new modes self.recreate_pipelines(pipeline_modes); @@ -619,7 +618,7 @@ impl Renderer { ); self.views = views; - // appease borrow check + // appease borrow check (TODO: remove after Rust 2021) let device = &self.device; let queue = &self.queue; let views = &self.views; diff --git a/voxygen/src/render/renderer/pipeline_creation.rs b/voxygen/src/render/renderer/pipeline_creation.rs index 7331504271..520bec4a8f 100644 --- a/voxygen/src/render/renderer/pipeline_creation.rs +++ b/voxygen/src/render/renderer/pipeline_creation.rs @@ -179,7 +179,7 @@ impl ShaderModules { ShadowMode::Map(_) if has_shadow_views => "SHADOW_MODE_MAP", ShadowMode::Cheap | ShadowMode::Map(_) => "SHADOW_MODE_CHEAP", }, - if dbg!(pipeline_modes.bloom) { + if pipeline_modes.bloom { "BLOOM_ENABLED" } else { "BLOOM_DISABLED" From 4719f1f26d4483f6ee37e696c7e1f9d90d84ac85 Mon Sep 17 00:00:00 2001 From: Imbris Date: Sun, 1 Aug 2021 18:50:09 -0400 Subject: [PATCH 8/8] Make bloom intensity configurable, make whether less blurred layers are added in toggleable, hold onto copy of pipeline modes instead of returning it from pipeline creation --- assets/voxygen/shaders/include/constants.glsl | 3 - assets/voxygen/shaders/postprocess-frag.glsl | 14 ++-- voxygen/src/render/mod.rs | 64 ++++++++++++++++++- voxygen/src/render/pipelines/bloom.rs | 13 ++-- voxygen/src/render/pipelines/postprocess.rs | 2 +- voxygen/src/render/renderer.rs | 43 +++++++------ .../src/render/renderer/pipeline_creation.rs | 36 ++++++++--- voxygen/src/settings/graphics.rs | 2 - 8 files changed, 126 insertions(+), 51 deletions(-) diff --git a/assets/voxygen/shaders/include/constants.glsl b/assets/voxygen/shaders/include/constants.glsl index 8cd90ca072..f1bc2a8aa9 100644 --- a/assets/voxygen/shaders/include/constants.glsl +++ b/assets/voxygen/shaders/include/constants.glsl @@ -24,9 +24,6 @@ #define SHADOW_MODE_CHEAP 1 #define SHADOW_MODE_MAP 2 -#define BLOOM_DISABLED 0 -#define BLOOM_ENABLED 1 - /* Unlike the other flags (for now anyway), these are bitmask values */ #define LIGHTING_TYPE_REFLECTION 0x01 #define LIGHTING_TYPE_TRANSMISSION 0x02 diff --git a/assets/voxygen/shaders/postprocess-frag.glsl b/assets/voxygen/shaders/postprocess-frag.glsl index 57df871073..17b90c0895 100644 --- a/assets/voxygen/shaders/postprocess-frag.glsl +++ b/assets/voxygen/shaders/postprocess-frag.glsl @@ -35,7 +35,7 @@ uniform u_locals { mat4 view_mat_inv; }; -#if (BLOOM == BLOOM_ENABLED) +#ifdef BLOOM_FACTOR layout(set = 1, binding = 3) uniform texture2D t_src_bloom; #endif @@ -187,11 +187,13 @@ void main() { vec4 aa_color = aa_apply(t_src_color, s_src_color, uv * screen_res.xy, screen_res.xy); // Bloom - #if (BLOOM == BLOOM_ENABLED) - // divide by 4.0 to account for adding blurred layers together - vec4 bloom = textureLod(sampler2D(t_src_bloom, s_src_color), uv, 0) / 4.0; - float bloom_factor = 0.10; - aa_color = mix(aa_color, bloom, bloom_factor); + #ifdef BLOOM_FACTOR + vec4 bloom = textureLod(sampler2D(t_src_bloom, s_src_color), uv, 0); + #if (BLOOM_UNIFORM_BLUR == false) + // divide by 4.0 to account for adding blurred layers together + bloom /= 4.0; + #endif + aa_color = mix(aa_color, bloom, BLOOM_FACTOR); #endif // Tonemapping diff --git a/voxygen/src/render/mod.rs b/voxygen/src/render/mod.rs index a2a8786c01..26006ad843 100644 --- a/voxygen/src/render/mod.rs +++ b/voxygen/src/render/mod.rs @@ -265,6 +265,66 @@ impl From for wgpu::PresentMode { } } +/// Bloom factor +/// Controls fraction of output image luminosity that is blurred bloom +#[derive(PartialEq, Clone, Copy, Debug, Serialize, Deserialize)] +pub enum BloomFactor { + Low, + High, + /// Max valid value is 1.0 + Custom(f32), + // other variant has to be placed last + #[serde(other)] + Standard, +} + +impl Default for BloomFactor { + fn default() -> Self { Self::Standard } +} + +impl BloomFactor { + /// Fraction of output image luminosity that is blurred bloom + pub fn fraction(self) -> f32 { + match self { + Self::Low => 0.05, + Self::Standard => 0.10, + Self::High => 0.25, + Self::Custom(val) => val.max(0.0).min(1.0), + } + } +} + +/// Bloom settings +#[derive(PartialEq, Clone, Copy, Debug, Serialize, Deserialize)] +pub struct BloomConfig { + /// Controls fraction of output image luminosity that is blurred bloom + /// + /// Defaults to `Standard` + factor: BloomFactor, + /// Turning this on make the bloom blur less sharply concentrated around the + /// high intensity phenomena (removes adding in less blurred layers to the + /// final blur) + /// + /// Defaults to `false` + uniform_blur: bool, + // TODO: allow configuring the blur radius and/or the number of passes +} + +#[derive(PartialEq, Clone, Copy, Debug, Serialize, Deserialize)] +pub enum BloomMode { + On(BloomConfig), + #[serde(other)] + Off, +} + +impl Default for BloomMode { + fn default() -> Self { Self::Off } +} + +impl BloomMode { + fn is_on(&self) -> bool { matches!(self, BloomMode::On(_)) } +} + /// Render modes #[derive(PartialEq, Clone, Debug, Default, Serialize, Deserialize)] #[serde(default)] @@ -274,7 +334,7 @@ pub struct RenderMode { pub fluid: FluidMode, pub lighting: LightingMode, pub shadow: ShadowMode, - pub bloom: bool, + pub bloom: BloomMode, pub upscale_mode: UpscaleMode, pub present_mode: PresentMode, @@ -310,7 +370,7 @@ pub struct PipelineModes { fluid: FluidMode, lighting: LightingMode, pub shadow: ShadowMode, - bloom: bool, + bloom: BloomMode, } /// Other render modes that don't effect pipelines diff --git a/voxygen/src/render/pipelines/bloom.rs b/voxygen/src/render/pipelines/bloom.rs index c1e4d9055b..123f25680c 100644 --- a/voxygen/src/render/pipelines/bloom.rs +++ b/voxygen/src/render/pipelines/bloom.rs @@ -2,7 +2,7 @@ //! //! See additional details in the [NUM_SIZES] docs -use super::super::Consts; +use super::super::{BloomConfig, Consts}; use bytemuck::{Pod, Zeroable}; use vek::*; @@ -152,6 +152,7 @@ impl BloomPipelines { upsample_fs_module: &wgpu::ShaderModule, target_format: wgpu::TextureFormat, layout: &BloomLayout, + bloom_config: &BloomConfig, ) -> Self { common_base::span!(_guard, "BloomPipelines::new"); let render_pipeline_layout = @@ -207,18 +208,14 @@ impl BloomPipelines { let upsample_pipeline = create_pipeline( "Bloom upsample pipeline", upsample_fs_module, - Some(wgpu::BlendState { + (!bloom_config.uniform_blur).then(|| wgpu::BlendState { color: wgpu::BlendComponent { src_factor: wgpu::BlendFactor::One, dst_factor: wgpu::BlendFactor::One, operation: wgpu::BlendOperation::Add, }, - // we don't really use this, but... we need something here - alpha: wgpu::BlendComponent { - src_factor: wgpu::BlendFactor::One, - dst_factor: wgpu::BlendFactor::One, - operation: wgpu::BlendOperation::Add, - }, + // We don't reaaly use this but we need something here.. + alpha: wgpu::BlendComponent::REPLACE, }), ); diff --git a/voxygen/src/render/pipelines/postprocess.rs b/voxygen/src/render/pipelines/postprocess.rs index ccb83b52c1..3d67c2d4d4 100644 --- a/voxygen/src/render/pipelines/postprocess.rs +++ b/voxygen/src/render/pipelines/postprocess.rs @@ -66,7 +66,7 @@ impl PostProcessLayout { }, ]; - if pipeline_modes.bloom { + if pipeline_modes.bloom.is_on() { bind_entries.push( // src bloom wgpu::BindGroupLayoutEntry { diff --git a/voxygen/src/render/renderer.rs b/voxygen/src/render/renderer.rs index 86580d0f7a..603fb2a915 100644 --- a/voxygen/src/render/renderer.rs +++ b/voxygen/src/render/renderer.rs @@ -111,19 +111,19 @@ enum State { Complete { pipelines: Pipelines, shadow: Shadow, - recreating: Option< + recreating: Option<( + PipelineModes, PipelineCreation< Result< ( Pipelines, ShadowPipelines, - PipelineModes, Arc, ), RenderError, >, >, - >, + )>, }, } @@ -522,7 +522,7 @@ impl Renderer { /// Returns `Some((total, complete))` if in progress pub fn pipeline_recreation_status(&self) -> Option<(usize, usize)> { if let State::Complete { recreating, .. } = &self.state { - recreating.as_ref().map(|r| r.status()) + recreating.as_ref().map(|(_, c)| c.status()) } else { None } @@ -765,6 +765,7 @@ impl Renderer { let bloom_tgt_views = pipeline_modes .bloom + .is_on() .then(|| bloom_sizes.map(|size| color_view(size.x, size.y))); let tgt_depth_tex = device.create_texture(&wgpu::TextureDescriptor { @@ -964,11 +965,11 @@ impl Renderer { } else if let State::Complete { pipelines, mut shadow, - recreating: Some(recreating), + recreating: Some((new_pipeline_modes, pipeline_creation)), } = state { - match recreating.try_complete() { - Ok(Ok((pipelines, shadow_pipelines, new_pipeline_modes, postprocess_layout))) => { + match pipeline_creation.try_complete() { + Ok(Ok((pipelines, shadow_pipelines, postprocess_layout))) => { if let ( Some(point_pipeline), Some(terrain_directed_pipeline), @@ -1007,10 +1008,10 @@ impl Renderer { } }, // Not complete - Err(recreating) => State::Complete { + Err(pipeline_creation) => State::Complete { pipelines, shadow, - recreating: Some(recreating), + recreating: Some((new_pipeline_modes, pipeline_creation)), }, } } else { @@ -1080,16 +1081,20 @@ impl Renderer { State::Complete { recreating, shadow, .. } => { - *recreating = Some(pipeline_creation::recreate_pipelines( - Arc::clone(&self.device), - Arc::clone(&self.layouts.immutable), - self.shaders.read().clone(), - self.pipeline_modes.clone(), - // NOTE: if present_mode starts to be used to configure pipelines then it needs - // to become a part of the pipeline modes (note here since the present mode is - // accessible through the swap chain descriptor) - self.sc_desc.clone(), // Note: cheap clone - shadow.map.is_enabled(), + *recreating = Some(( + pipeline_modes.clone(), + pipeline_creation::recreate_pipelines( + Arc::clone(&self.device), + Arc::clone(&self.layouts.immutable), + self.shaders.read().clone(), + pipeline_modes, + // NOTE: if present_mode starts to be used to configure pipelines then it + // needs to become a part of the pipeline modes + // (note here since the present mode is accessible + // through the swap chain descriptor) + self.sc_desc.clone(), // Note: cheap clone + shadow.map.is_enabled(), + ), )); }, State::Interface { .. } => { diff --git a/voxygen/src/render/renderer/pipeline_creation.rs b/voxygen/src/render/renderer/pipeline_creation.rs index 520bec4a8f..3ec6f4f479 100644 --- a/voxygen/src/render/renderer/pipeline_creation.rs +++ b/voxygen/src/render/renderer/pipeline_creation.rs @@ -4,7 +4,8 @@ use super::{ blit, bloom, clouds, debug, figure, fluid, lod_terrain, particle, postprocess, shadow, skybox, sprite, terrain, ui, }, - AaMode, CloudMode, FluidMode, LightingMode, PipelineModes, RenderError, ShadowMode, + AaMode, BloomMode, CloudMode, FluidMode, LightingMode, PipelineModes, RenderError, + ShadowMode, }, shaders::Shaders, ImmutableLayouts, Layouts, @@ -151,7 +152,6 @@ impl ShaderModules { #define CLOUD_MODE {} #define LIGHTING_ALGORITHM {} #define SHADOW_MODE {} -#define BLOOM {} "#, &constants.0, @@ -179,13 +179,26 @@ impl ShaderModules { ShadowMode::Map(_) if has_shadow_views => "SHADOW_MODE_MAP", ShadowMode::Cheap | ShadowMode::Map(_) => "SHADOW_MODE_CHEAP", }, - if pipeline_modes.bloom { - "BLOOM_ENABLED" - } else { - "BLOOM_DISABLED" - }, ); + let constants = match pipeline_modes.bloom { + BloomMode::Off => constants, + BloomMode::On(config) => { + format!( + r#" +{} + +#define BLOOM_FACTOR {} +#define BLOOM_UNIFORM_BLUR {} + +"#, + constants, + config.factor.fraction(), + config.uniform_blur, + ) + }, + }; + let anti_alias = shaders .get(match pipeline_modes.aa { AaMode::None => "antialias.none", @@ -558,7 +571,11 @@ fn create_ingame_and_shadow_pipelines( let create_bloom = || { bloom_task.run( || { - pipeline_modes.bloom.then(|| { + match &pipeline_modes.bloom { + BloomMode::Off => None, + BloomMode::On(config) => Some(config), + } + .map(|bloom_config| { bloom::BloomPipelines::new( device, &shaders.blit_vert, @@ -567,6 +584,7 @@ fn create_ingame_and_shadow_pipelines( &shaders.dual_upsample_frag, wgpu::TextureFormat::Rgba16Float, &layouts.bloom, + bloom_config, ) }) }, @@ -797,7 +815,6 @@ pub(super) fn recreate_pipelines( ( Pipelines, ShadowPipelines, - PipelineModes, Arc, ), RenderError, @@ -871,7 +888,6 @@ pub(super) fn recreate_pipelines( .send(Ok(( Pipelines::consolidate(interface, ingame), shadow, - pipeline_modes, layouts.postprocess, ))) .expect("Channel disconnected"); diff --git a/voxygen/src/settings/graphics.rs b/voxygen/src/settings/graphics.rs index 55cc52ba6b..1155a7a131 100644 --- a/voxygen/src/settings/graphics.rs +++ b/voxygen/src/settings/graphics.rs @@ -43,7 +43,6 @@ pub struct GraphicsSettings { pub window_size: [u16; 2], pub fullscreen: FullScreenSettings, pub lod_detail: u32, - pub bloom_enabled: bool, } impl Default for GraphicsSettings { @@ -63,7 +62,6 @@ impl Default for GraphicsSettings { window_size: [1280, 720], fullscreen: FullScreenSettings::default(), lod_detail: 250, - bloom_enabled: false, } } }