Merge branch 'imbris/bloom' into 'master'

Bloom

See merge request veloren/veloren!2693
This commit is contained in:
Imbris 2021-08-02 00:01:44 +00:00
commit 1bb9a9e315
23 changed files with 1108 additions and 223 deletions

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -4,6 +4,8 @@
#![deny(clippy::clone_on_ref_ptr)]
#![feature(
array_map,
array_methods,
array_zip,
bool_to_option,
const_generics,
drain_filter,

View File

@ -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();

View File

@ -265,6 +265,66 @@ impl From<PresentMode> 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,
}

View File

@ -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<f32>) -> 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<Locals>,
) -> 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,
}
}
}

View File

@ -1,4 +1,5 @@
pub mod blit;
pub mod bloom;
pub mod clouds;
pub mod debug;
pub mod figure;

View File

@ -1,4 +1,4 @@
use super::super::{Consts, GlobalsLayouts};
use super::super::{Consts, GlobalsLayouts, PipelineModes};
use bytemuck::{Pod, Zeroable};
use vek::*;
@ -31,15 +31,12 @@ pub struct PostProcessLayout {
}
impl PostProcessLayout {
pub fn new(device: &wgpu::Device) -> Self {
Self {
layout: device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: None,
entries: &[
pub fn new(device: &wgpu::Device, pipeline_modes: &PipelineModes) -> Self {
let mut bind_entries = vec![
// 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,
@ -49,7 +46,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,
@ -67,7 +64,28 @@ impl PostProcessLayout {
},
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: &bind_entries,
}),
}
}
@ -76,13 +94,11 @@ impl PostProcessLayout {
&self,
device: &wgpu::Device,
src_color: &wgpu::TextureView,
src_bloom: Option<&wgpu::TextureView>,
sampler: &wgpu::Sampler,
locals: &Consts<Locals>,
) -> BindGroup {
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: None,
layout: &self.layout,
entries: &[
let mut entries = vec![
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(src_color),
@ -95,7 +111,25 @@ impl PostProcessLayout {
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: &entries,
});
BindGroup { bind_group }

View File

@ -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<u16>);
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<ImmutableLayouts>,
postprocess: Arc<postprocess::PostProcessLayout>,
}
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<PipelineCreation<Result<(Pipelines, ShadowPipelines), RenderError>>>,
recreating: Option<(
PipelineModes,
PipelineCreation<
Result<
(
Pipelines,
ShadowPipelines,
Arc<postprocess::PostProcessLayout>,
),
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<PipelineModes>,
layouts: Arc<Layouts>,
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<Shaders>,
mode: RenderMode,
pipeline_modes: PipelineModes,
other_modes: OtherModes,
resolution: Vec2<u32>,
// 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<Self, RenderError> {
pub fn new(window: &winit::window::Window, mode: RenderMode) -> Result<Self, RenderError> {
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();
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.mode.profiler_enabled &= self.profiler_features_enabled;
self.other_modes.profiler_enabled &= self.profiler_features_enabled;
// Enable/disable profiler
if !self.mode.profiler_enabled {
if !self.other_modes.profiler_enabled {
// Clear the times if disabled
core::mem::take(&mut self.profile_times);
}
self.profiler.enable_timer = self.mode.profiler_enabled;
self.profiler.enable_debug_marker = self.mode.profiler_enabled;
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.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<u32>) -> Result<(), RenderError> {
pub fn on_resize(&mut self, dims: Vec2<u32>) {
// 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<Views, RenderError> {
pipeline_modes: &PipelineModes,
other_modes: &OtherModes,
) -> (Views, [Vec2<f32>; bloom::NUM_SIZES]) {
let upscaled = Vec2::<u32>::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 {
(
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(
*recreating = Some((
pipeline_modes.clone(),
pipeline_creation::recreate_pipelines(
Arc::clone(&self.device),
Arc::clone(&self.layouts),
Arc::clone(&self.layouts.immutable),
self.shaders.read().clone(),
self.mode.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()

View File

@ -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<u16>,
quad_index_buffer_u32: &'frame Buffer<u32>,
#[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<ShadowPassDrawer> {
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<Item = (&'data Model<terrain::Vertex>, &'data terrain::BoundLocals)>,
) {
if !self.borrow.mode.shadow.is_map() {
if !self.borrow.pipeline_modes.shadow.is_map() {
return;
}

View File

@ -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::Locals>; 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<clouds::Locals>,
pub clouds_bind: clouds::BindGroup,
pub bloom_binds: Option<[bloom::BindGroup; bloom::NUM_SIZES]>,
pub postprocess: Consts<postprocess::Locals>,
pub postprocess_bind: postprocess::BindGroup,
}
@ -22,6 +30,7 @@ impl Locals {
postprocess_locals: Consts<postprocess::Locals>,
tgt_color_view: &wgpu::TextureView,
tgt_depth_view: &wgpu::TextureView,
bloom: Option<BloomParams>,
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<BloomParams>,
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))
});
}
}

View File

@ -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<bloom::BloomPipelines>,
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<bloom::BloomPipelines>,
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<Self, RenderError> {
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<wgpu::Device>,
layouts: Arc<Layouts>,
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<wgpu::Device>,
layouts: Arc<Layouts>,
immutable_layouts: Arc<ImmutableLayouts>,
shaders: Shaders,
mode: RenderMode,
pipeline_modes: PipelineModes,
sc_desc: wgpu::SwapChainDescriptor,
has_shadow_views: bool,
) -> PipelineCreation<Result<(Pipelines, ShadowPipelines), RenderError>> {
) -> PipelineCreation<
Result<
(
Pipelines,
ShadowPipelines,
Arc<postprocess::PostProcessLayout>,
),
RenderError,
>,
> {
prof_span!(_guard, "recreate_pipelines");
// Create threadpool for parallel portion
@ -780,7 +847,8 @@ 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) {
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");
@ -789,11 +857,22 @@ pub(super) fn recreate_pipelines(
};
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");
});

View File

@ -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",

View File

@ -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: _,

View File

@ -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() {

View File

@ -1244,7 +1244,7 @@ impl<V: RectRasterableVol> Terrain<V> {
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::<f32> {
min: math::Vec3::from(visible_bounding_box.min - focus_off),

View File

@ -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");

View File

@ -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;