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..9ce06098ae --- /dev/null +++ b/assets/voxygen/shaders/dual-downsample-filtered-frag.glsl @@ -0,0 +1,90 @@ +#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 u_locals { + vec2 halfpixel; +}; + +layout(location = 0) in vec2 uv; + +layout(location = 0) out vec4 tgt_color; + +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) { + // 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); + else + return vec4(0.0, 0.0, 0.0, 1.0); +} + +vec4 filteredFetch(ivec2 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 = 0.5 / 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; +} + +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/assets/voxygen/shaders/dual-downsample-frag.glsl b/assets/voxygen/shaders/dual-downsample-frag.glsl new file mode 100644 index 0000000000..af9fa46c09 --- /dev/null +++ b/assets/voxygen/shaders/dual-downsample-frag.glsl @@ -0,0 +1,34 @@ +#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 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); +} + +// 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/dual-upsample-frag.glsl b/assets/voxygen/shaders/dual-upsample-frag.glsl new file mode 100644 index 0000000000..10d1b1fdc6 --- /dev/null +++ b/assets/voxygen/shaders/dual-upsample-frag.glsl @@ -0,0 +1,35 @@ +#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 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); +} + +// 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/figure-frag.glsl b/assets/voxygen/shaders/figure-frag.glsl index 3b084366da..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 += 1000 * surf_color; + emitted_light += 20 * 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; diff --git a/assets/voxygen/shaders/postprocess-frag.glsl b/assets/voxygen/shaders/postprocess-frag.glsl index aeba45f350..17b90c0895 100644 --- a/assets/voxygen/shaders/postprocess-frag.glsl +++ b/assets/voxygen/shaders/postprocess-frag.glsl @@ -27,7 +27,6 @@ uniform texture2D t_src_color; layout(set = 1, binding = 1) uniform sampler s_src_color; - layout(location = 0) in vec2 uv; layout (std140, set = 1, binding = 2) @@ -36,6 +35,11 @@ uniform u_locals { mat4 view_mat_inv; }; +#ifdef BLOOM_FACTOR +layout(set = 1, binding = 3) +uniform texture2D t_src_bloom; +#endif + layout(location = 0) out vec4 tgt_color; vec3 rgb2hsv(vec3 c) { @@ -182,6 +186,16 @@ void main() { vec4 aa_color = aa_apply(t_src_color, s_src_color, uv * screen_res.xy, screen_res.xy); + // Bloom + #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 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/menu/char_selection/mod.rs b/voxygen/src/menu/char_selection/mod.rs index 8941cfc8bf..a5e96f3c20 100644 --- a/voxygen/src/menu/char_selection/mod.rs +++ b/voxygen/src/menu/char_selection/mod.rs @@ -257,6 +257,8 @@ impl PlayState for CharSelectionState { if let Some(mut second_pass) = drawer.second_pass() { second_pass.draw_clouds(); } + // 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..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,49 @@ pub struct RenderMode { pub fluid: FluidMode, pub lighting: LightingMode, pub shadow: ShadowMode, + pub bloom: BloomMode, + 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: BloomMode, +} + +/// 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 new file mode 100644 index 0000000000..123f25680c --- /dev/null +++ b/voxygen/src/render/pipelines/bloom.rs @@ -0,0 +1,228 @@ +//! 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::{BloomConfig, 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. +// +/// 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 { + pub(in super::super) bind_group: wgpu::BindGroup, +} + +#[repr(C)] +#[derive(Copy, Clone, Debug, Zeroable, Pod)] +pub struct Locals { + halfpixel: [f32; 2], +} + +impl Locals { + pub fn new(source_texture_resolution: Vec2) -> Self { + Self { + halfpixel: 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, + bloom_config: &BloomConfig, + ) -> 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 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 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, + (!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 reaaly use this but we need something here.. + alpha: wgpu::BlendComponent::REPLACE, + }), + ); + + Self { + downsample_filtered: downsample_filtered_pipeline, + 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..3d67c2d4d4 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,43 +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.is_on() { + 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::VERTEX | 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::VERTEX | 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, - }, - ], + entries: &bind_entries, }), } } @@ -76,26 +94,42 @@ impl PostProcessLayout { &self, device: &wgpu::Device, src_color: &wgpu::TextureView, + src_bloom: Option<&wgpu::TextureView>, sampler: &wgpu::Sampler, locals: &Consts, ) -> BindGroup { + 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: 3, + resource: wgpu::BindingResource::TextureView(src_bloom), + }, + ); + } + 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: locals.buf().as_entire_binding(), - }, - ], + entries: &entries, }); BindGroup { bind_group } diff --git a/voxygen/src/render/renderer.rs b/voxygen/src/render/renderer.rs index 4b58ca095f..603fb2a915 100644 --- a/voxygen/src/render/renderer.rs +++ b/voxygen/src/render/renderer.rs @@ -21,11 +21,12 @@ 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, + AaMode, AddressMode, FilterMode, OtherModes, PipelineModes, RenderError, RenderMode, + ShadowMapMode, ShadowMode, Vertex, }; use common::assets::{self, AssetExt, AssetHandle}; use common_base::span; @@ -45,21 +46,35 @@ 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, - 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, 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 @@ -67,6 +82,8 @@ struct Views { tgt_color: wgpu::TextureView, tgt_depth: wgpu::TextureView, + + bloom_tgts: Option<[wgpu::TextureView; bloom::NUM_SIZES]>, // TODO: rename tgt_color_pp: wgpu::TextureView, } @@ -81,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, @@ -93,7 +111,19 @@ enum State { Complete { pipelines: Pipelines, shadow: Shadow, - recreating: Option>>, + recreating: Option<( + PipelineModes, + PipelineCreation< + Result< + ( + Pipelines, + ShadowPipelines, + Arc, + ), + RenderError, + >, + >, + )>, }, } @@ -112,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, @@ -128,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 @@ -152,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. // @@ -285,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); @@ -293,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); @@ -305,41 +337,51 @@ 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 = 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, - clouds, debug, figure, - postprocess, shadow, sprite, terrain, + clouds, + bloom, 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(), )?; @@ -350,7 +392,12 @@ 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), + &pipeline_modes, + &other_modes, + ); let create_sampler = |filter| { device.create_sampler(&wgpu::SamplerDescriptor { @@ -389,6 +436,13 @@ impl Renderer { postprocess_locals, &views.tgt_color, &views.tgt_depth, + 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, @@ -399,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 = @@ -415,7 +469,7 @@ impl Renderer { sc_desc, state, - recreation_pending: false, + recreation_pending: None, layouts, locals, @@ -430,7 +484,8 @@ impl Renderer { shaders, - mode, + pipeline_modes, + other_modes, resolution: Vec2::new(dims.width, dims.height), take_screenshot: None, @@ -467,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 } @@ -475,37 +530,46 @@ 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 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); + } 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 @@ -535,7 +599,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; @@ -546,13 +610,36 @@ 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)?; - // Rebind views to clouds/postprocess bind groups + let (views, bloom_sizes) = Self::create_rt_views( + &self.device, + (dims.x, dims.y), + &self.pipeline_modes, + &self.other_modes, + ); + self.views = views; + + // appease borrow check (TODO: remove after Rust 2021) + 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, &self.views.tgt_color, &self.views.tgt_depth, + bloom_params, &self.views.tgt_color_pp, &self.sampler, &self.depth_sampler, @@ -576,7 +663,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)) => { @@ -608,8 +695,6 @@ impl Renderer { } else { self.is_minimized = true; } - - Ok(()) } pub fn maintain(&self) { @@ -624,12 +709,13 @@ impl Renderer { fn create_rt_views( device: &wgpu::Device, size: (u32, u32), - mode: &RenderMode, - ) -> Result { + 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), @@ -637,7 +723,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 +751,22 @@ 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 = 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 { label: None, @@ -717,12 +817,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, - }) + ( + 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. @@ -796,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; @@ -806,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, @@ -857,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))) => { + match pipeline_creation.try_complete() { + Ok(Ok((pipelines, shadow_pipelines, postprocess_layout))) => { if let ( Some(point_pipeline), Some(terrain_directed_pipeline), @@ -877,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, @@ -892,27 +1008,36 @@ 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 { 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() { @@ -920,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 @@ -945,29 +1071,36 @@ 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), - self.shaders.read().clone(), - self.mode.clone(), - 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 { .. } => { // 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 => {}, } @@ -1184,7 +1317,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 8034a251b5..16f67b76f2 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, @@ -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; } @@ -241,6 +241,94 @@ 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 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); + + 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(&pass_label), + color_attachments: &[wgpu::RenderPassColorAttachment { + resolve_target: None, + view, + ops: wgpu::Operations { store: true, load }, + }], + 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 = &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), + &bloom_pipelines.downsample_filtered, + ) + } else { + ( + format!("downsample {}", index + 1), + &bloom_pipelines.downsample, + ) + }; + run_bloom_pass( + bind, + view, + label, + pipeline, + wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), + ); + }); + + // Upsample filter passes + (0..bloom::NUM_SIZES - 1).for_each(|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, + &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) + } 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 + }, + ); + }); + } + pub fn third_pass(&mut self) -> ThirdPassDrawer { let encoder = self.encoder.as_mut().unwrap(); let device = self.borrow.device; @@ -321,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 b7c94c5396..fb3b0301ac 100644 --- a/voxygen/src/render/renderer/locals.rs +++ b/voxygen/src/render/renderer/locals.rs @@ -1,15 +1,23 @@ use super::{ super::{ consts::Consts, - pipelines::{clouds, postprocess}, + pipelines::{bloom, clouds, postprocess}, }, 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: Option<[bloom::BindGroup; bloom::NUM_SIZES]>, + pub postprocess: Consts, pub postprocess_bind: postprocess::BindGroup, } @@ -22,6 +30,7 @@ impl Locals { postprocess_locals: Consts, tgt_color_view: &wgpu::TextureView, tgt_depth_view: &wgpu::TextureView, + bloom: Option, tgt_color_pp_view: &wgpu::TextureView, sampler: &wgpu::Sampler, depth_sampler: &wgpu::Sampler, @@ -34,14 +43,26 @@ impl Locals { depth_sampler, &clouds_locals, ); - let postprocess_bind = - layouts - .postprocess - .bind(device, tgt_color_pp_view, sampler, &postprocess_locals); + + let postprocess_bind = layouts.postprocess.bind( + device, + tgt_color_pp_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, + bloom_binds, postprocess: postprocess_locals, postprocess_bind, } @@ -55,6 +76,7 @@ impl Locals { // e.g. resizing tgt_color_view: &wgpu::TextureView, tgt_depth_view: &wgpu::TextureView, + bloom: Option, tgt_color_pp_view: &wgpu::TextureView, sampler: &wgpu::Sampler, depth_sampler: &wgpu::Sampler, @@ -67,9 +89,18 @@ impl Locals { depth_sampler, &self.clouds, ); - self.postprocess_bind = - layouts - .postprocess - .bind(device, tgt_color_pp_view, sampler, &self.postprocess); + self.postprocess_bind = layouts.postprocess.bind( + device, + tgt_color_pp_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 1aca7f43f1..3ec6f4f479 100644 --- a/voxygen/src/render/renderer/pipeline_creation.rs +++ b/voxygen/src/render/renderer/pipeline_creation.rs @@ -1,13 +1,14 @@ 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, + AaMode, BloomMode, CloudMode, FluidMode, LightingMode, PipelineModes, RenderError, + ShadowMode, }, shaders::Shaders, - Layouts, + ImmutableLayouts, Layouts, }; use common_base::prof_span; use std::sync::Arc; @@ -20,6 +21,7 @@ pub struct Pipelines { pub lod_terrain: lod_terrain::LodTerrainPipeline, pub particle: particle::ParticlePipeline, pub clouds: clouds::CloudsPipeline, + pub bloom: Option, pub postprocess: postprocess::PostProcessPipeline, // Consider reenabling at some time // player_shadow: figure::FigurePipeline, @@ -39,6 +41,7 @@ pub struct IngamePipelines { lod_terrain: lod_terrain::LodTerrainPipeline, particle: particle::ParticlePipeline, clouds: clouds::CloudsPipeline, + pub bloom: Option, postprocess: postprocess::PostProcessPipeline, // Consider reenabling at some time // player_shadow: figure::FigurePipeline, @@ -74,6 +77,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 +111,9 @@ struct ShaderModules { lod_terrain_frag: wgpu::ShaderModule, clouds_vert: wgpu::ShaderModule, clouds_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, @@ -120,7 +127,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"); @@ -150,11 +157,11 @@ impl ShaderModules { &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", @@ -162,20 +169,38 @@ 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", }, ); + 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 mode.aa { + .get(match pipeline_modes.aa { AaMode::None => "antialias.none", AaMode::Fxaa => "antialias.fxaa", AaMode::MsaaX4 => "antialias.msaa-x4", @@ -185,7 +210,7 @@ impl ShaderModules { .unwrap(); let cloud = shaders - .get(match mode.cloud { + .get(match pipeline_modes.cloud { CloudMode::None => "include.cloud.none", _ => "include.cloud.regular", }) @@ -228,7 +253,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", }] @@ -255,6 +280,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)?, + 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)?, @@ -305,7 +336,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, } @@ -360,7 +391,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"); @@ -368,7 +399,7 @@ fn create_ingame_and_shadow_pipelines( device, layouts, shaders, - mode, + pipeline_modes, sc_desc, } = needs; @@ -382,6 +413,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 @@ -403,7 +435,7 @@ fn create_ingame_and_shadow_pipelines( &shaders.debug_frag, &layouts.global, &layouts.debug, - mode.aa, + pipeline_modes.aa, ) }, "debug pipeline creation", @@ -418,7 +450,7 @@ fn create_ingame_and_shadow_pipelines( &shaders.skybox_vert, &shaders.skybox_frag, &layouts.global, - mode.aa, + pipeline_modes.aa, ) }, "skybox pipeline creation", @@ -434,7 +466,7 @@ fn create_ingame_and_shadow_pipelines( &shaders.figure_frag, &layouts.global, &layouts.figure, - mode.aa, + pipeline_modes.aa, ) }, "figure pipeline creation", @@ -450,7 +482,7 @@ fn create_ingame_and_shadow_pipelines( &shaders.terrain_frag, &layouts.global, &layouts.terrain, - mode.aa, + pipeline_modes.aa, ) }, "terrain pipeline creation", @@ -466,7 +498,7 @@ fn create_ingame_and_shadow_pipelines( &shaders.fluid_frag, &layouts.global, &layouts.terrain, - mode.aa, + pipeline_modes.aa, ) }, "fluid pipeline creation", @@ -483,7 +515,7 @@ fn create_ingame_and_shadow_pipelines( &layouts.global, &layouts.sprite, &layouts.terrain, - mode.aa, + pipeline_modes.aa, ) }, "sprite pipeline creation", @@ -498,7 +530,7 @@ fn create_ingame_and_shadow_pipelines( &shaders.particle_vert, &shaders.particle_frag, &layouts.global, - mode.aa, + pipeline_modes.aa, ) }, "particle pipeline creation", @@ -513,7 +545,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", @@ -529,12 +561,36 @@ fn create_ingame_and_shadow_pipelines( &shaders.clouds_frag, &layouts.global, &layouts.clouds, - mode.aa, + pipeline_modes.aa, ) }, "clouds pipeline creation", ) }; + // Pipelines for rendering our bloom + let create_bloom = || { + bloom_task.run( + || { + match &pipeline_modes.bloom { + BloomMode::Off => None, + BloomMode::On(config) => Some(config), + } + .map(|bloom_config| { + 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_config, + ) + }) + }, + "bloom pipelines creation", + ) + }; // Pipeline for rendering our post-processing let create_postprocess = || { postprocess_task.run( @@ -584,7 +640,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", @@ -599,7 +655,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", @@ -614,7 +670,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", @@ -622,7 +678,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 +692,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 +709,7 @@ fn create_ingame_and_shadow_pipelines( lod_terrain, particle, clouds, + bloom, postprocess, skybox, sprite, @@ -675,9 +732,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< @@ -690,7 +747,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() @@ -702,7 +759,7 @@ pub(super) fn initial_create_pipelines( device: &device, layouts: &layouts, shaders: &shader_modules, - mode: &mode, + pipeline_modes: &pipeline_modes, sc_desc: &sc_desc, }; @@ -729,7 +786,7 @@ pub(super) fn initial_create_pipelines( device: &device, layouts: &layouts, shaders: &shader_modules, - mode: &mode, + pipeline_modes: &pipeline_modes, sc_desc: &sc_desc, }; @@ -745,14 +802,24 @@ 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, + Arc, + ), + RenderError, + >, +> { prof_span!(_guard, "recreate_pipelines"); // Create threadpool for parallel portion @@ -780,20 +847,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, }; @@ -806,7 +885,11 @@ pub(super) fn recreate_pipelines( // Send them result_send - .send(Ok((Pipelines::consolidate(interface, ingame), shadow))) + .send(Ok(( + Pipelines::consolidate(interface, ingame), + shadow, + layouts.postprocess, + ))) .expect("Channel disconnected"); }); diff --git a/voxygen/src/render/renderer/shaders.rs b/voxygen/src/render/renderer/shaders.rs index eb3a148a89..2d8f32556c 100644 --- a/voxygen/src/render/renderer/shaders.rs +++ b/voxygen/src/render/renderer/shaders.rs @@ -68,6 +68,10 @@ impl assets::Compound for Shaders { "lod-terrain-frag", "clouds-vert", "clouds-frag", + "dual-downsample-filtered-frag", + "dual-downsample-frag", + "dual-upsample-frag", + "clouds-frag", "postprocess-vert", "postprocess-frag", "blit-vert", 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 3e7bda0b77..23c8bed695 100644 --- a/voxygen/src/session/mod.rs +++ b/voxygen/src/session/mod.rs @@ -1483,6 +1483,11 @@ impl PlayState for SessionState { second_pass.draw_clouds(); } } + // Bloom (call does nothing if bloom is off) + { + prof_span!("bloom"); + drawer.run_bloom_passes() + } // PostProcess and UI { prof_span!("post-process and ui"); 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;