From 71a561fc70e6d57013d158d7814884a23c943bb6 Mon Sep 17 00:00:00 2001 From: Imbris Date: Sun, 25 Apr 2021 02:13:41 -0400 Subject: [PATCH] Move pipeline creation into the background, still needs Drawer modications and UI to display status --- common/base/src/lib.rs | 11 +- voxygen/Cargo.toml | 1 + voxygen/src/render/pipelines/shadow.rs | 3 - voxygen/src/render/renderer.rs | 920 +++++------------- voxygen/src/render/renderer/drawer.rs | 76 +- .../src/render/renderer/pipeline_creation.rs | 897 +++++++++++++++++ voxygen/src/render/renderer/shaders.rs | 9 +- voxygen/src/render/renderer/shadow_map.rs | 247 ++++- 8 files changed, 1431 insertions(+), 733 deletions(-) create mode 100644 voxygen/src/render/renderer/pipeline_creation.rs diff --git a/common/base/src/lib.rs b/common/base/src/lib.rs index 4f9d8ed951..eed7df08b6 100644 --- a/common/base/src/lib.rs +++ b/common/base/src/lib.rs @@ -58,7 +58,10 @@ macro_rules! span { }; } -pub struct DummySpan; +#[cfg(feature = "tracy")] +pub struct ProfSpan(tracy_client::Span); +#[cfg(not(feature = "tracy"))] +pub struct ProfSpan; /// Like the span macro but only used when profiling and not in regular tracing /// operations @@ -66,16 +69,16 @@ pub struct DummySpan; macro_rules! prof_span { ($guard_name:tt, $name:expr) => { #[cfg(feature = "tracy")] - let $guard_name = $crate::tracy_client::Span::new( + let $guard_name = $crate::ProfSpan($crate::tracy_client::Span::new( $name, "", module_path!(), line!(), // No callstack since this has significant overhead 0, - ); + )); #[cfg(not(feature = "tracy"))] - let $guard_name = $crate::DummySpan; + let $guard_name = $crate::ProfSpan; }; } diff --git a/voxygen/Cargo.toml b/voxygen/Cargo.toml index 1e6a621eac..ffa966111a 100644 --- a/voxygen/Cargo.toml +++ b/voxygen/Cargo.toml @@ -93,6 +93,7 @@ native-dialog = { version = "0.5.2", optional = true } num = "0.4" ordered-float = { version = "2.0.1", default-features = false } rand = "0.8" +rayon = "1.5" rodio = {version = "0.13", default-features = false, features = ["vorbis"]} ron = {version = "0.6", default-features = false} serde = {version = "1.0", features = [ "rc", "derive" ]} diff --git a/voxygen/src/render/pipelines/shadow.rs b/voxygen/src/render/pipelines/shadow.rs index 7bb52f019b..84988c0a38 100644 --- a/voxygen/src/render/pipelines/shadow.rs +++ b/voxygen/src/render/pipelines/shadow.rs @@ -132,7 +132,6 @@ impl ShadowFigurePipeline { pub fn new( device: &wgpu::Device, vs_module: &wgpu::ShaderModule, - fs_module: &wgpu::ShaderModule, global_layout: &GlobalsLayouts, figure_layout: &FigureLayout, aa_mode: AaMode, @@ -209,7 +208,6 @@ impl ShadowPipeline { pub fn new( device: &wgpu::Device, vs_module: &wgpu::ShaderModule, - fs_module: &wgpu::ShaderModule, global_layout: &GlobalsLayouts, terrain_layout: &TerrainLayout, aa_mode: AaMode, @@ -283,7 +281,6 @@ impl PointShadowPipeline { pub fn new( device: &wgpu::Device, vs_module: &wgpu::ShaderModule, - fs_module: &wgpu::ShaderModule, global_layout: &GlobalsLayouts, terrain_layout: &TerrainLayout, aa_mode: AaMode, diff --git a/voxygen/src/render/renderer.rs b/voxygen/src/render/renderer.rs index 3aedb7cf58..55cc8678e4 100644 --- a/voxygen/src/render/renderer.rs +++ b/voxygen/src/render/renderer.rs @@ -2,11 +2,15 @@ mod binding; pub(super) mod drawer; // Consts and bind groups for post-process and clouds mod locals; +mod pipeline_creation; mod screenshot; mod shaders; mod shadow_map; use locals::Locals; +use pipeline_creation::{ + IngameAndShadowPipelines, InterfacePipelines, PipelineCreation, Pipelines, ShadowPipelines, +}; use shaders::Shaders; use shadow_map::{ShadowMap, ShadowMapRenderer}; @@ -27,6 +31,7 @@ use super::{ use common::assets::{self, AssetExt, AssetHandle}; use common_base::span; use core::convert::TryFrom; +use std::sync::Arc; use tracing::{error, info, warn}; use vek::*; @@ -54,23 +59,6 @@ struct Layouts { blit: blit::BlitLayout, } -/// A type that stores all the pipelines associated with this renderer. -struct Pipelines { - figure: figure::FigurePipeline, - fluid: fluid::FluidPipeline, - lod_terrain: lod_terrain::LodTerrainPipeline, - particle: particle::ParticlePipeline, - clouds: clouds::CloudsPipeline, - postprocess: postprocess::PostProcessPipeline, - // Consider reenabling at some time - // player_shadow: figure::FigurePipeline, - skybox: skybox::SkyboxPipeline, - sprite: sprite::SpritePipeline, - terrain: terrain::TerrainPipeline, - ui: ui::UiPipeline, - blit: blit::BlitPipeline, -} - /// Render target views struct Views { // NOTE: unused for now @@ -88,13 +76,32 @@ struct Shadow { bind: ShadowTexturesBindGroup, } +/// Represent two states of the renderer: +/// 1. Only interface pipelines created +/// 2. All of the pipelines have been created +enum State { + // NOTE: this is used as a transient placeholder for moving things out of State temporarily + Nothing, + Interface { + pipelines: InterfacePipelines, + shadow_views: Option<(Texture, Texture)>, + // In progress creation of the remaining pipelines in the background + creating: PipelineCreation, + }, + Complete { + pipelines: Pipelines, + shadow: Shadow, + recreating: Option>>, + }, +} + /// A type that encapsulates rendering state. `Renderer` is central to Voxygen's /// rendering subsystem and contains any state necessary to interact with the /// GPU, along with pipeline state objects (PSOs) needed to renderer different /// kinds of models to the screen. pub struct Renderer { // TODO: remove pub(super) - pub(super) device: wgpu::Device, + pub(super) device: Arc, queue: wgpu::Queue, surface: wgpu::Surface, swap_chain: wgpu::SwapChain, @@ -103,9 +110,12 @@ pub struct Renderer { sampler: wgpu::Sampler, depth_sampler: wgpu::Sampler, - layouts: Layouts, - pipelines: Pipelines, - shadow: Shadow, + state: State, + // true if there is a pending need to recreate the pipelines (e.g. RenderMode change or shader + // hotloading) + recreation_pending: bool, + + layouts: Arc, // Note: we keep these here since their bind groups need to be updated if we resize the // color/depth textures locals: Locals, @@ -231,7 +241,7 @@ impl Renderer { let swap_chain = device.create_swap_chain(&surface, &sc_desc); - let shadow_views = Self::create_shadow_views( + let shadow_views = ShadowMap::create_shadow_views( &device, (dims.width, dims.height), &ShadowMapMode::try_from(mode.shadow).unwrap_or_default(), @@ -271,69 +281,27 @@ impl Renderer { } }; - let ( - pipelines, - //player_shadow_pipeline, - point_shadow_pipeline, - terrain_directed_shadow_pipeline, - figure_directed_shadow_pipeline, - ) = create_pipelines( - &device, - &layouts, - &shaders.read(), - &mode, - &sc_desc, + // Arcify the device and layouts + 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), + shaders.read().clone(), + mode.clone(), + sc_desc.clone(), // Note: cheap clone shadow_views.is_some(), )?; - let views = Self::create_rt_views(&device, (dims.width, dims.height), &mode)?; - - let shadow_map = if let ( - Some(point_pipeline), - Some(terrain_directed_pipeline), - Some(figure_directed_pipeline), - Some(shadow_views), - ) = ( - point_shadow_pipeline, - terrain_directed_shadow_pipeline, - figure_directed_shadow_pipeline, + let state = State::Interface { + pipelines: interface_pipelines, shadow_views, - ) { - let (point_depth, directed_depth) = shadow_views; - - let layout = shadow::ShadowLayout::new(&device); - - ShadowMap::Enabled(ShadowMapRenderer { - // point_encoder: factory.create_command_buffer().into(), - // directed_encoder: factory.create_command_buffer().into(), - directed_depth, - point_depth, - - point_pipeline, - terrain_directed_pipeline, - figure_directed_pipeline, - - layout, - }) - } else { - let (dummy_point, dummy_directed) = Self::create_dummy_shadow_tex(&device, &queue); - ShadowMap::Disabled { - dummy_point, - dummy_directed, - } + creating, }; - let shadow_bind = { - let (point, directed) = shadow_map.textures(); - layouts - .global - .bind_shadow_textures(&device, point, directed) - }; - - let shadow = Shadow { - map: shadow_map, - bind: shadow_bind, - }; + let views = Self::create_rt_views(&device, (dims.width, dims.height), &mode)?; let create_sampler = |filter| { device.create_sampler(&wgpu::SamplerDescriptor { @@ -393,9 +361,10 @@ impl Renderer { swap_chain, sc_desc, + state, + recreation_pending: false, + layouts, - pipelines, - shadow, locals, views, @@ -421,6 +390,13 @@ 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(); @@ -496,18 +472,48 @@ impl Renderer { &self.depth_sampler, ); - if let (ShadowMap::Enabled(shadow_map), ShadowMode::Map(mode)) = - (&mut self.shadow.map, self.mode.shadow) + let mode = &self.mode; + // Get mutable reference to shadow views out of the current state + let shadow_views = match &mut self.state { + State::Interface { shadow_views, .. } => { + shadow_views.as_mut().map(|s| (&mut s.0, &mut s.1)) + }, + State::Complete { + shadow: + Shadow { + map: ShadowMap::Enabled(shadow_map), + .. + }, + .. + } => Some((&mut shadow_map.point_depth, &mut shadow_map.directed_depth)), + State::Complete { .. } => None, + State::Nothing => None, // Should never hit this + }; + + if let (Some((point_depth, directed_depth)), ShadowMode::Map(mode)) = + (shadow_views, self.mode.shadow) { - match Self::create_shadow_views(&mut self.device, (dims.x, dims.y), &mode) { - Ok((point_depth, directed_depth)) => { - shadow_map.point_depth = point_depth; - shadow_map.directed_depth = directed_depth; - self.shadow.bind = self.layouts.global.bind_shadow_textures( - &self.device, - &shadow_map.point_depth, - &shadow_map.directed_depth, - ); + match ShadowMap::create_shadow_views(&mut self.device, (dims.x, dims.y), &mode) { + Ok((new_point_depth, new_directed_depth)) => { + *point_depth = new_point_depth; + *directed_depth = new_directed_depth; + // Recreate the shadow bind group if needed + if let State::Complete { + shadow: + Shadow { + bind, + map: ShadowMap::Enabled(shadow_map), + .. + }, + .. + } = &mut self.state + { + *bind = self.layouts.global.bind_shadow_textures( + &self.device, + &shadow_map.point_depth, + &shadow_map.directed_depth, + ); + } }, Err(err) => { warn!("Could not create shadow map views: {:?}", err); @@ -626,222 +632,25 @@ impl Renderer { }) } - fn create_dummy_shadow_tex(device: &wgpu::Device, queue: &wgpu::Queue) -> (Texture, Texture) { - let make_tex = |view_dim, depth| { - let tex = wgpu::TextureDescriptor { - label: None, - size: wgpu::Extent3d { - width: 4, - height: 4, - depth_or_array_layers: depth, - }, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Depth24Plus, - usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::RENDER_ATTACHMENT, - }; - - let view = wgpu::TextureViewDescriptor { - label: None, - format: Some(wgpu::TextureFormat::Depth24Plus), - dimension: Some(view_dim), - aspect: wgpu::TextureAspect::DepthOnly, - base_mip_level: 0, - mip_level_count: None, - base_array_layer: 0, - array_layer_count: None, - }; - - let sampler_info = wgpu::SamplerDescriptor { - label: None, - address_mode_u: wgpu::AddressMode::ClampToEdge, - address_mode_v: wgpu::AddressMode::ClampToEdge, - address_mode_w: wgpu::AddressMode::ClampToEdge, - mag_filter: wgpu::FilterMode::Linear, - min_filter: wgpu::FilterMode::Linear, - mipmap_filter: wgpu::FilterMode::Nearest, - compare: Some(wgpu::CompareFunction::LessEqual), - ..Default::default() - }; - - Texture::new_raw(device, &tex, &view, &sampler_info) - }; - - let cube_tex = make_tex(wgpu::TextureViewDimension::Cube, 6); - let tex = make_tex(wgpu::TextureViewDimension::D2, 1); - - // Clear to 1.0 - let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { - label: Some("Dummy shadow tex clearing encoder"), - }); - let mut clear = |tex: &Texture| { - encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("Clear dummy shadow texture"), - color_attachments: &[], - depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { - view: &tex.view, - depth_ops: Some(wgpu::Operations { - load: wgpu::LoadOp::Clear(1.0), - store: true, - }), - stencil_ops: None, - }), - }); - }; - clear(&cube_tex); - clear(&tex); - drop(clear); - queue.submit(std::iter::once(encoder.finish())); - - (cube_tex, tex) - } - - /// Create textures and views for shadow maps. - // This is a one-use type and the two halves are not guaranteed to remain identical, so we - // disable the type complexity lint. - #[allow(clippy::type_complexity)] - fn create_shadow_views( - device: &wgpu::Device, - size: (u32, u32), - mode: &ShadowMapMode, - ) -> Result<(Texture, Texture), RenderError> { - // (Attempt to) apply resolution factor to shadow map resolution. - let resolution_factor = mode.resolution.clamped(0.25, 4.0); - - let max_texture_size = Self::max_texture_size_raw(device); - // Limit to max texture size, rather than erroring. - let size = Vec2::new(size.0, size.1).map(|e| { - let size = e as f32 * resolution_factor; - // NOTE: We know 0 <= e since we clamped the resolution factor to be between - // 0.25 and 4.0. - if size <= max_texture_size as f32 { - size as u32 - } else { - max_texture_size - } - }); - - let levels = 1; - // Limit to max texture size rather than erroring. - let two_size = size.map(|e| { - u32::checked_next_power_of_two(e) - .filter(|&e| e <= max_texture_size) - .unwrap_or(max_texture_size) - }); - let min_size = size.reduce_min(); - let max_size = size.reduce_max(); - let _min_two_size = two_size.reduce_min(); - let _max_two_size = two_size.reduce_max(); - // For rotated shadow maps, the maximum size of a pixel along any axis is the - // size of a diagonal along that axis. - let diag_size = size.map(f64::from).magnitude(); - let diag_cross_size = f64::from(min_size) / f64::from(max_size) * diag_size; - let (diag_size, _diag_cross_size) = - if 0.0 < diag_size && diag_size <= f64::from(max_texture_size) { - // NOTE: diag_cross_size must be non-negative, since it is the ratio of a - // non-negative and a positive number (if max_size were zero, - // diag_size would be 0 too). And it must be <= diag_size, - // since min_size <= max_size. Therefore, if diag_size fits in a - // u16, so does diag_cross_size. - (diag_size as u32, diag_cross_size as u32) - } else { - // Limit to max texture resolution rather than error. - (max_texture_size as u32, max_texture_size as u32) - }; - let diag_two_size = u32::checked_next_power_of_two(diag_size) - .filter(|&e| e <= max_texture_size) - // Limit to max texture resolution rather than error. - .unwrap_or(max_texture_size); - - let point_shadow_tex = wgpu::TextureDescriptor { - label: None, - size: wgpu::Extent3d { - width: diag_two_size / 4, - height: diag_two_size / 4, - depth_or_array_layers: 6, - }, - mip_level_count: levels, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Depth24Plus, - usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::RENDER_ATTACHMENT, - }; - - //TODO: (0, levels - 1), ?? from master - let point_shadow_view = wgpu::TextureViewDescriptor { - label: None, - format: Some(wgpu::TextureFormat::Depth24Plus), - dimension: Some(wgpu::TextureViewDimension::Cube), - aspect: wgpu::TextureAspect::DepthOnly, - base_mip_level: 0, - mip_level_count: None, - base_array_layer: 0, - array_layer_count: None, - }; - - let directed_shadow_tex = wgpu::TextureDescriptor { - label: None, - size: wgpu::Extent3d { - width: diag_two_size, - height: diag_two_size, - depth_or_array_layers: 1, - }, - mip_level_count: levels, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Depth24Plus, - usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::RENDER_ATTACHMENT, - }; - - let directed_shadow_view = wgpu::TextureViewDescriptor { - label: None, - format: Some(wgpu::TextureFormat::Depth24Plus), - dimension: Some(wgpu::TextureViewDimension::D2), - aspect: wgpu::TextureAspect::DepthOnly, - base_mip_level: 0, - mip_level_count: None, - base_array_layer: 0, - array_layer_count: None, - }; - - let sampler_info = wgpu::SamplerDescriptor { - label: None, - address_mode_u: wgpu::AddressMode::ClampToEdge, - address_mode_v: wgpu::AddressMode::ClampToEdge, - address_mode_w: wgpu::AddressMode::ClampToEdge, - mag_filter: wgpu::FilterMode::Linear, - min_filter: wgpu::FilterMode::Linear, - mipmap_filter: wgpu::FilterMode::Nearest, - compare: Some(wgpu::CompareFunction::LessEqual), - ..Default::default() - }; - - let point_shadow_tex = - Texture::new_raw(device, &point_shadow_tex, &point_shadow_view, &sampler_info); - let directed_shadow_tex = Texture::new_raw( - device, - &directed_shadow_tex, - &directed_shadow_view, - &sampler_info, - ); - - Ok((point_shadow_tex, directed_shadow_tex)) - } - /// Get the resolution of the render target. pub fn resolution(&self) -> Vec2 { self.resolution } /// Get the resolution of the shadow render target. pub fn get_shadow_resolution(&self) -> (Vec2, Vec2) { - if let ShadowMap::Enabled(shadow_map) = &self.shadow.map { - ( - shadow_map.point_depth.get_dimensions().xy(), - shadow_map.directed_depth.get_dimensions().xy(), - ) - } else { - (Vec2::new(1, 1), Vec2::new(1, 1)) + match &self.state { + State::Interface { shadow_views, .. } => shadow_views.as_ref().map(|s| (&s.0, &s.1)), + State::Complete { + shadow: + Shadow { + map: ShadowMap::Enabled(shadow_map), + .. + }, + .. + } => Some((&shadow_map.point_depth, &shadow_map.directed_depth)), + State::Complete { .. } | State::Nothing => None, } + .map(|(point, directed)| (point.get_dimensions().xy(), directed.get_dimensions().xy())) + .unwrap_or_else(|| (Vec2::new(1, 1), Vec2::new(1, 1))) } // /// Queue the clearing of the shadow targets ready for a new frame to be @@ -901,6 +710,7 @@ impl Renderer { "start_recording_frame", "Renderer::start_recording_frame" ); + // Try to get the latest profiling results if self.mode.profiler_enabled { // Note: this lags a few frames behind @@ -909,14 +719,118 @@ impl Renderer { } } - // TODO: does this make sense here? - self.device.poll(wgpu::Maintain::Poll); + // 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); + // If still creating initial pipelines, check if complete + self.state = if let State::Interface { + pipelines: interface, + shadow_views, + creating, + } = state + { + match creating.try_complete() { + Ok(pipelines) => { + let IngameAndShadowPipelines { ingame, shadow } = pipelines; + + let pipelines = Pipelines::consolidate(interface, ingame); + + let shadow_map = ShadowMap::new( + &self.device, + &self.queue, + shadow.point, + shadow.directed, + shadow.figure, + shadow_views, + ); + + let shadow_bind = { + let (point, directed) = shadow_map.textures(); + self.layouts + .global + .bind_shadow_textures(&self.device, point, directed) + }; + + let shadow = Shadow { + map: shadow_map, + bind: shadow_bind, + }; + + State::Complete { + pipelines, + shadow, + recreating: None, + } + }, + // Not complete + Err(creating) => State::Interface { + pipelines: interface, + shadow_views, + creating, + }, + } + // If recreating the pipelines, check if that is complete + } else if let State::Complete { + pipelines, + mut shadow, + recreating: Some(recreating), + } = state + { + match recreating.try_complete() { + Ok(Ok((pipelines, shadow_pipelines))) => { + if let ( + Some(point_pipeline), + Some(terrain_directed_pipeline), + Some(figure_directed_pipeline), + ShadowMap::Enabled(shadow_map), + ) = ( + shadow_pipelines.point, + shadow_pipelines.directed, + shadow_pipelines.figure, + &mut shadow.map, + ) { + shadow_map.point_pipeline = point_pipeline; + shadow_map.terrain_directed_pipeline = terrain_directed_pipeline; + shadow_map.figure_directed_pipeline = figure_directed_pipeline; + } + State::Complete { + pipelines, + shadow, + recreating: None, + } + }, + Ok(Err(e)) => { + error!(?e, "Could not recreate shaders from assets due to an error"); + State::Complete { + pipelines, + shadow, + recreating: None, + } + }, + // Not complete + Err(recreating) => State::Complete { + pipelines, + shadow, + recreating: Some(recreating), + }, + } + } else { + state + }; // If the shaders files were changed attempt to recreate the shaders if self.shaders.reloaded() { self.recreate_pipelines(); } + // 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(); + } + let tex = match self.swap_chain.get_current_frame() { Ok(frame) => frame.output, // If lost recreate the swap chain @@ -947,40 +861,30 @@ impl Renderer { /// Recreate the pipelines fn recreate_pipelines(&mut self) { - match create_pipelines( - &self.device, - &self.layouts, - &self.shaders.read(), - &self.mode, - &self.sc_desc, - self.shadow.map.is_enabled(), - ) { - Ok(( - pipelines, - //player_shadow_pipeline, - point_shadow_pipeline, - terrain_directed_shadow_pipeline, - figure_directed_shadow_pipeline, - )) => { - self.pipelines = pipelines; - //self.player_shadow_pipeline = player_shadow_pipeline; - if let ( - Some(point_pipeline), - Some(terrain_directed_pipeline), - Some(figure_directed_pipeline), - ShadowMap::Enabled(shadow_map), - ) = ( - point_shadow_pipeline, - terrain_directed_shadow_pipeline, - figure_directed_shadow_pipeline, - &mut self.shadow.map, - ) { - shadow_map.point_pipeline = point_pipeline; - shadow_map.terrain_directed_pipeline = terrain_directed_pipeline; - shadow_map.figure_directed_pipeline = figure_directed_pipeline; - } + 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; }, - Err(e) => error!(?e, "Could not recreate shaders from assets due to an error",), + 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(), + )); + }, + State::Interface { .. } => { + // Defer recreation so that we are not building multiple sets of pipelines in + // the background at once + self.recreation_pending = true; + }, + State::Nothing => {}, } } @@ -1926,348 +1830,6 @@ impl Renderer { // } } -/// Creates all the pipelines used to render. -fn create_pipelines( - device: &wgpu::Device, - layouts: &Layouts, - shaders: &Shaders, - mode: &RenderMode, - sc_desc: &wgpu::SwapChainDescriptor, - has_shadow_views: bool, -) -> Result< - ( - Pipelines, - //figure::FigurePipeline, - Option, - Option, - Option, - ), - RenderError, -> { - use shaderc::{CompileOptions, Compiler, OptimizationLevel, ResolvedInclude, ShaderKind}; - - let constants = shaders.get("include.constants").unwrap(); - let globals = shaders.get("include.globals").unwrap(); - let sky = shaders.get("include.sky").unwrap(); - let light = shaders.get("include.light").unwrap(); - let srgb = shaders.get("include.srgb").unwrap(); - let random = shaders.get("include.random").unwrap(); - let lod = shaders.get("include.lod").unwrap(); - let shadows = shaders.get("include.shadows").unwrap(); - - // We dynamically add extra configuration settings to the constants file. - let constants = format!( - r#" -{} - -#define VOXYGEN_COMPUTATION_PREFERENCE {} -#define FLUID_MODE {} -#define CLOUD_MODE {} -#define LIGHTING_ALGORITHM {} -#define SHADOW_MODE {} - -"#, - &constants.0, - // TODO: Configurable vertex/fragment shader preference. - "VOXYGEN_COMPUTATION_PREFERENCE_FRAGMENT", - match mode.fluid { - FluidMode::Cheap => "FLUID_MODE_CHEAP", - FluidMode::Shiny => "FLUID_MODE_SHINY", - }, - match mode.cloud { - CloudMode::None => "CLOUD_MODE_NONE", - CloudMode::Minimal => "CLOUD_MODE_MINIMAL", - CloudMode::Low => "CLOUD_MODE_LOW", - CloudMode::Medium => "CLOUD_MODE_MEDIUM", - CloudMode::High => "CLOUD_MODE_HIGH", - CloudMode::Ultra => "CLOUD_MODE_ULTRA", - }, - match mode.lighting { - LightingMode::Ashikhmin => "LIGHTING_ALGORITHM_ASHIKHMIN", - LightingMode::BlinnPhong => "LIGHTING_ALGORITHM_BLINN_PHONG", - LightingMode::Lambertian => "LIGHTING_ALGORITHM_LAMBERTIAN", - }, - match mode.shadow { - ShadowMode::None => "SHADOW_MODE_NONE", - ShadowMode::Map(_) if has_shadow_views => "SHADOW_MODE_MAP", - ShadowMode::Cheap | ShadowMode::Map(_) => "SHADOW_MODE_CHEAP", - }, - ); - - let anti_alias = shaders - .get(match mode.aa { - AaMode::None => "antialias.none", - AaMode::Fxaa => "antialias.fxaa", - AaMode::MsaaX4 => "antialias.msaa-x4", - AaMode::MsaaX8 => "antialias.msaa-x8", - AaMode::MsaaX16 => "antialias.msaa-x16", - }) - .unwrap(); - - let cloud = shaders - .get(match mode.cloud { - CloudMode::None => "include.cloud.none", - _ => "include.cloud.regular", - }) - .unwrap(); - - let mut compiler = Compiler::new().ok_or(RenderError::ErrorInitializingCompiler)?; - let mut options = CompileOptions::new().ok_or(RenderError::ErrorInitializingCompiler)?; - options.set_optimization_level(OptimizationLevel::Performance); - options.set_forced_version_profile(430, shaderc::GlslProfile::Core); - options.set_include_callback(move |name, _, shader_name, _| { - Ok(ResolvedInclude { - resolved_name: name.to_string(), - content: match name { - "constants.glsl" => constants.clone(), - "globals.glsl" => globals.0.to_owned(), - "shadows.glsl" => shadows.0.to_owned(), - "sky.glsl" => sky.0.to_owned(), - "light.glsl" => light.0.to_owned(), - "srgb.glsl" => srgb.0.to_owned(), - "random.glsl" => random.0.to_owned(), - "lod.glsl" => lod.0.to_owned(), - "anti-aliasing.glsl" => anti_alias.0.to_owned(), - "cloud.glsl" => cloud.0.to_owned(), - other => return Err(format!("Include {} is not defined", other)), - }, - }) - }); - - let mut create_shader = |name, kind| { - let glsl = &shaders - .get(name) - .unwrap_or_else(|| panic!("Can't retrieve shader: {}", name)) - .0; - let file_name = format!("{}.glsl", name); - create_shader_module(device, &mut compiler, glsl, kind, &file_name, &options) - }; - - let figure_vert_mod = create_shader("figure-vert", ShaderKind::Vertex)?; - - // let terrain_point_shadow_vert_mod = create_shader("Point-light-shadows-vert", - // ShaderKind::Vertex)?; - - let terrain_directed_shadow_vert_mod = - create_shader("light-shadows-directed-vert", ShaderKind::Vertex)?; - - let figure_directed_shadow_vert_mod = - create_shader("light-shadows-figure-vert", ShaderKind::Vertex)?; - - let directed_shadow_frag_mod = - create_shader("light-shadows-directed-frag", ShaderKind::Fragment)?; - - // Construct a pipeline for rendering skyboxes - let skybox_pipeline = skybox::SkyboxPipeline::new( - device, - &create_shader("skybox-vert", ShaderKind::Vertex)?, - &create_shader("skybox-frag", ShaderKind::Fragment)?, - &layouts.global, - mode.aa, - ); - - // Construct a pipeline for rendering figures - let figure_pipeline = figure::FigurePipeline::new( - device, - &figure_vert_mod, - &create_shader("figure-frag", ShaderKind::Fragment)?, - &layouts.global, - &layouts.figure, - mode.aa, - ); - - let terrain_vert = create_shader("terrain-vert", ShaderKind::Vertex)?; - // Construct a pipeline for rendering terrain - let terrain_pipeline = terrain::TerrainPipeline::new( - device, - &terrain_vert, - &create_shader("terrain-frag", ShaderKind::Fragment)?, - &layouts.global, - &layouts.terrain, - mode.aa, - ); - - // Construct a pipeline for rendering fluids - let selected_fluid_shader = ["fluid-frag.", match mode.fluid { - FluidMode::Cheap => "cheap", - FluidMode::Shiny => "shiny", - }] - .concat(); - let fluid_pipeline = fluid::FluidPipeline::new( - device, - &create_shader("fluid-vert", ShaderKind::Vertex)?, - &create_shader(&selected_fluid_shader, ShaderKind::Fragment)?, - &layouts.global, - &layouts.fluid, - &layouts.terrain, - mode.aa, - ); - - // Construct a pipeline for rendering sprites - let sprite_pipeline = sprite::SpritePipeline::new( - device, - &create_shader("sprite-vert", ShaderKind::Vertex)?, - &create_shader("sprite-frag", ShaderKind::Fragment)?, - &layouts.global, - &layouts.sprite, - &layouts.terrain, - mode.aa, - ); - - // Construct a pipeline for rendering particles - let particle_pipeline = particle::ParticlePipeline::new( - device, - &create_shader("particle-vert", ShaderKind::Vertex)?, - &create_shader("particle-frag", ShaderKind::Fragment)?, - &layouts.global, - mode.aa, - ); - - // Construct a pipeline for rendering UI elements - let ui_pipeline = ui::UiPipeline::new( - device, - &create_shader("ui-vert", ShaderKind::Vertex)?, - &create_shader("ui-frag", ShaderKind::Fragment)?, - sc_desc, - &layouts.global, - &layouts.ui, - ); - - // Construct a pipeline for rendering terrain - let lod_terrain_pipeline = lod_terrain::LodTerrainPipeline::new( - device, - &create_shader("lod-terrain-vert", ShaderKind::Vertex)?, - &create_shader("lod-terrain-frag", ShaderKind::Fragment)?, - &layouts.global, - mode.aa, - ); - - // Construct a pipeline for rendering our clouds (a kind of post-processing) - let clouds_pipeline = clouds::CloudsPipeline::new( - device, - &create_shader("clouds-vert", ShaderKind::Vertex)?, - &create_shader("clouds-frag", ShaderKind::Fragment)?, - // TODO: pass in format of intermediate color buffer - &layouts.global, - &layouts.clouds, - mode.aa, - ); - - // Construct a pipeline for rendering our post-processing - let postprocess_pipeline = postprocess::PostProcessPipeline::new( - device, - &create_shader("postprocess-vert", ShaderKind::Vertex)?, - &create_shader("postprocess-frag", ShaderKind::Fragment)?, - sc_desc, - &layouts.global, - &layouts.postprocess, - ); - - // Construct a pipeline for blitting, used during screenshotting - let blit_pipeline = blit::BlitPipeline::new( - device, - &create_shader("blit-vert", ShaderKind::Vertex)?, - &create_shader("blit-frag", ShaderKind::Fragment)?, - sc_desc, - &layouts.blit, - ); - - // Consider reenabling at some time in the future - // - // // Construct a pipeline for rendering the player silhouette - // let player_shadow_pipeline = create_pipeline( - // factory, - // figure::pipe::Init { - // tgt_depth: (gfx::preset::depth::PASS_TEST/*, - // Stencil::new( - // Comparison::Equal, - // 0xff, - // (StencilOp::Keep, StencilOp::Keep, StencilOp::Keep), - // ),*/), - // ..figure::pipe::new() - // }, - // &figure_vert, - // &Glsl::load_watched( - // "voxygen.shaders.player-shadow-frag", - // shader_reload_indicator, - // ) - // .unwrap(), - // &include_ctx, - // gfx::state::CullFace::Back, - // )?; - - // Construct a pipeline for rendering point light terrain shadow maps. - let point_shadow_pipeline = shadow::PointShadowPipeline::new( - device, - &create_shader("point-light-shadows-vert", ShaderKind::Vertex)?, - &create_shader("light-shadows-frag", ShaderKind::Fragment)?, - &layouts.global, - &layouts.terrain, - mode.aa, - ); - - // Construct a pipeline for rendering directional light terrain shadow maps. - let terrain_directed_shadow_pipeline = shadow::ShadowPipeline::new( - device, - &terrain_directed_shadow_vert_mod, - &directed_shadow_frag_mod, - &layouts.global, - &layouts.terrain, - mode.aa, - ); - - // Construct a pipeline for rendering directional light figure shadow maps. - let figure_directed_shadow_pipeline = shadow::ShadowFigurePipeline::new( - device, - &figure_directed_shadow_vert_mod, - &directed_shadow_frag_mod, - &layouts.global, - &layouts.figure, - mode.aa, - ); - - Ok(( - Pipelines { - skybox: skybox_pipeline, - figure: figure_pipeline, - terrain: terrain_pipeline, - fluid: fluid_pipeline, - sprite: sprite_pipeline, - particle: particle_pipeline, - ui: ui_pipeline, - lod_terrain: lod_terrain_pipeline, - clouds: clouds_pipeline, - postprocess: postprocess_pipeline, - blit: blit_pipeline, - }, - // player_shadow_pipeline, - Some(point_shadow_pipeline), - Some(terrain_directed_shadow_pipeline), - Some(figure_directed_shadow_pipeline), - )) -} - -fn create_shader_module( - device: &wgpu::Device, - compiler: &mut shaderc::Compiler, - source: &str, - kind: shaderc::ShaderKind, - file_name: &str, - options: &shaderc::CompileOptions, -) -> Result { - use std::borrow::Cow; - - let spv = compiler - .compile_into_spirv(source, kind, file_name, "main", Some(options)) - .map_err(|e| (file_name, e))?; - - Ok(device.create_shader_module(&wgpu::ShaderModuleDescriptor { - label: Some(source), - source: wgpu::ShaderSource::SpirV(Cow::Borrowed(spv.as_binary())), - flags: wgpu::ShaderFlags::empty(), // TODO: renable wgpu::ShaderFlags::VALIDATION, - })) -} - fn create_quad_index_buffer_u16(device: &wgpu::Device, vert_length: usize) -> Buffer { assert!(vert_length <= u16::MAX as usize); let indices = [0, 1, 2, 2, 1, 3] diff --git a/voxygen/src/render/renderer/drawer.rs b/voxygen/src/render/renderer/drawer.rs index f73fbaccac..98e6d7f9c6 100644 --- a/voxygen/src/render/renderer/drawer.rs +++ b/voxygen/src/render/renderer/drawer.rs @@ -60,8 +60,8 @@ impl<'frame> Drawer<'frame> { let borrow = RendererBorrow { queue: &renderer.queue, device: &renderer.device, - shadow: &renderer.shadow, - pipelines: &renderer.pipelines, + shadow: todo!(), //&renderer.shadow, + pipelines: todo!(), //&renderer.pipelines, locals: &renderer.locals, views: &renderer.views, mode: &renderer.mode, @@ -92,16 +92,14 @@ impl<'frame> Drawer<'frame> { encoder.scoped_render_pass("shadow_pass", device, &wgpu::RenderPassDescriptor { label: Some("shadow pass"), color_attachments: &[], - depth_stencil_attachment: Some( - wgpu::RenderPassDepthStencilAttachment { - view: &shadow_renderer.directed_depth.view, - depth_ops: Some(wgpu::Operations { - load: wgpu::LoadOp::Clear(1.0), - store: true, - }), - stencil_ops: None, - }, - ), + depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { + view: &shadow_renderer.directed_depth.view, + depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Clear(1.0), + store: true, + }), + stencil_ops: None, + }), }); render_pass.set_bind_group(0, &self.globals.bind_group, &[]); @@ -244,16 +242,14 @@ impl<'frame> Drawer<'frame> { encoder.scoped_render_pass(&label, device, &wgpu::RenderPassDescriptor { label: Some(&label), color_attachments: &[], - depth_stencil_attachment: Some( - wgpu::RenderPassDepthStencilAttachment { - view: &view, - depth_ops: Some(wgpu::Operations { - load: wgpu::LoadOp::Clear(1.0), - store: true, - }), - stencil_ops: None, - }, - ), + depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { + view: &view, + depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Clear(1.0), + store: true, + }), + stencil_ops: None, + }), }); render_pass.set_pipeline(&shadow_renderer.point_pipeline.pipeline); @@ -294,16 +290,14 @@ impl<'frame> Drawer<'frame> { &wgpu::RenderPassDescriptor { label: Some("clear directed shadow pass"), color_attachments: &[], - depth_stencil_attachment: Some( - wgpu::RenderPassDepthStencilAttachment { - view: &shadow_renderer.directed_depth.view, - depth_ops: Some(wgpu::Operations { - load: wgpu::LoadOp::Clear(1.0), - store: true, - }), - stencil_ops: None, - }, - ), + depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { + view: &shadow_renderer.directed_depth.view, + depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Clear(1.0), + store: true, + }), + stencil_ops: None, + }), }, ); @@ -328,16 +322,14 @@ impl<'frame> Drawer<'frame> { let _ = encoder.scoped_render_pass(&label, device, &wgpu::RenderPassDescriptor { label: Some(&label), color_attachments: &[], - depth_stencil_attachment: Some( - wgpu::RenderPassDepthStencilAttachment { - view: &view, - depth_ops: Some(wgpu::Operations { - load: wgpu::LoadOp::Clear(1.0), - store: true, - }), - stencil_ops: None, - }, - ), + depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { + view: &view, + depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Clear(1.0), + store: true, + }), + stencil_ops: None, + }), }); } } diff --git a/voxygen/src/render/renderer/pipeline_creation.rs b/voxygen/src/render/renderer/pipeline_creation.rs new file mode 100644 index 0000000000..e8bf80d35f --- /dev/null +++ b/voxygen/src/render/renderer/pipeline_creation.rs @@ -0,0 +1,897 @@ +use super::{ + super::{ + pipelines::{ + blit, clouds, figure, fluid, lod_terrain, particle, postprocess, shadow, skybox, + sprite, terrain, ui, + }, + AaMode, CloudMode, FluidMode, LightingMode, RenderError, RenderMode, ShadowMode, + }, + shaders::Shaders, + Layouts, +}; +use common_base::prof_span; +use std::sync::Arc; + +/// All the pipelines +pub struct Pipelines { + pub figure: figure::FigurePipeline, + pub fluid: fluid::FluidPipeline, + pub lod_terrain: lod_terrain::LodTerrainPipeline, + pub particle: particle::ParticlePipeline, + pub clouds: clouds::CloudsPipeline, + pub postprocess: postprocess::PostProcessPipeline, + // Consider reenabling at some time + // player_shadow: figure::FigurePipeline, + pub skybox: skybox::SkyboxPipeline, + pub sprite: sprite::SpritePipeline, + pub terrain: terrain::TerrainPipeline, + pub ui: ui::UiPipeline, + pub blit: blit::BlitPipeline, +} + +/// Pipelines that are needed to render 3D stuff in-game +/// Use to decouple interface pipeline creation when initializing the renderer +pub struct IngamePipelines { + figure: figure::FigurePipeline, + fluid: fluid::FluidPipeline, + lod_terrain: lod_terrain::LodTerrainPipeline, + particle: particle::ParticlePipeline, + clouds: clouds::CloudsPipeline, + postprocess: postprocess::PostProcessPipeline, + // Consider reenabling at some time + // player_shadow: figure::FigurePipeline, + skybox: skybox::SkyboxPipeline, + sprite: sprite::SpritePipeline, + terrain: terrain::TerrainPipeline, +} + +pub struct ShadowPipelines { + pub point: Option, + pub directed: Option, + pub figure: Option, +} + +pub struct IngameAndShadowPipelines { + pub ingame: IngamePipelines, + pub shadow: ShadowPipelines, +} + +/// Pipelines neccesary to display the UI and take screenshots +/// Use to decouple interface pipeline creation when initializing the renderer +pub struct InterfacePipelines { + pub ui: ui::UiPipeline, + pub blit: blit::BlitPipeline, +} + +impl Pipelines { + pub fn consolidate(interface: InterfacePipelines, ingame: IngamePipelines) -> Self { + Self { + figure: ingame.figure, + fluid: ingame.fluid, + lod_terrain: ingame.lod_terrain, + particle: ingame.particle, + clouds: ingame.clouds, + postprocess: ingame.postprocess, + //player_shadow: ingame.player_shadow, + skybox: ingame.skybox, + sprite: ingame.sprite, + terrain: ingame.terrain, + ui: interface.ui, + blit: interface.blit, + } + } +} + +// TODO: remove +/// For abstraction over types containing the pipelines necessary to render the +/// UI +/*pub trait HasInterfacePipelines { + fn ui(&self) -> &ui::Pipeline; + fn blit(&self) -> &blit::Pipeline; +} + +impl HasInterfacePipelines for InterfacePipelines { + fn ui(&self) -> &ui::Pipeline { &self.ui } + + fn blit(&self) -> &blit::Pipeline { &self.blit } +} + +impl HasInterfacePipelines for Pipelines { + fn ui(&self) -> &ui::Pipeline { &self.ui } + + fn blit(&self) -> &blit::Pipeline { &self.blit } +}*/ + +/// Processed shaders ready for use in pipeline creation +struct ShaderModules { + skybox_vert: wgpu::ShaderModule, + skybox_frag: wgpu::ShaderModule, + figure_vert: wgpu::ShaderModule, + figure_frag: wgpu::ShaderModule, + terrain_vert: wgpu::ShaderModule, + terrain_frag: wgpu::ShaderModule, + fluid_vert: wgpu::ShaderModule, + fluid_frag: wgpu::ShaderModule, + sprite_vert: wgpu::ShaderModule, + sprite_frag: wgpu::ShaderModule, + particle_vert: wgpu::ShaderModule, + particle_frag: wgpu::ShaderModule, + ui_vert: wgpu::ShaderModule, + ui_frag: wgpu::ShaderModule, + lod_terrain_vert: wgpu::ShaderModule, + lod_terrain_frag: wgpu::ShaderModule, + clouds_vert: wgpu::ShaderModule, + clouds_frag: wgpu::ShaderModule, + postprocess_vert: wgpu::ShaderModule, + postprocess_frag: wgpu::ShaderModule, + blit_vert: wgpu::ShaderModule, + blit_frag: wgpu::ShaderModule, + point_light_shadows_vert: wgpu::ShaderModule, + light_shadows_directed_vert: wgpu::ShaderModule, + light_shadows_figure_vert: wgpu::ShaderModule, +} + +impl ShaderModules { + pub fn new( + device: &wgpu::Device, + shaders: &Shaders, + mode: &RenderMode, + has_shadow_views: bool, + ) -> Result { + prof_span!(_guard, "ShaderModules::new"); + use shaderc::{CompileOptions, Compiler, OptimizationLevel, ResolvedInclude, ShaderKind}; + + let constants = shaders.get("include.constants").unwrap(); + let globals = shaders.get("include.globals").unwrap(); + let sky = shaders.get("include.sky").unwrap(); + let light = shaders.get("include.light").unwrap(); + let srgb = shaders.get("include.srgb").unwrap(); + let random = shaders.get("include.random").unwrap(); + let lod = shaders.get("include.lod").unwrap(); + let shadows = shaders.get("include.shadows").unwrap(); + + // We dynamically add extra configuration settings to the constants file. + let constants = format!( + r#" +{} + +#define VOXYGEN_COMPUTATION_PREFERENCE {} +#define FLUID_MODE {} +#define CLOUD_MODE {} +#define LIGHTING_ALGORITHM {} +#define SHADOW_MODE {} + +"#, + &constants.0, + // TODO: Configurable vertex/fragment shader preference. + "VOXYGEN_COMPUTATION_PREFERENCE_FRAGMENT", + match mode.fluid { + FluidMode::Cheap => "FLUID_MODE_CHEAP", + FluidMode::Shiny => "FLUID_MODE_SHINY", + }, + match mode.cloud { + CloudMode::None => "CLOUD_MODE_NONE", + CloudMode::Minimal => "CLOUD_MODE_MINIMAL", + CloudMode::Low => "CLOUD_MODE_LOW", + CloudMode::Medium => "CLOUD_MODE_MEDIUM", + CloudMode::High => "CLOUD_MODE_HIGH", + CloudMode::Ultra => "CLOUD_MODE_ULTRA", + }, + match mode.lighting { + LightingMode::Ashikhmin => "LIGHTING_ALGORITHM_ASHIKHMIN", + LightingMode::BlinnPhong => "LIGHTING_ALGORITHM_BLINN_PHONG", + LightingMode::Lambertian => "LIGHTING_ALGORITHM_LAMBERTIAN", + }, + match mode.shadow { + ShadowMode::None => "SHADOW_MODE_NONE", + ShadowMode::Map(_) if has_shadow_views => "SHADOW_MODE_MAP", + ShadowMode::Cheap | ShadowMode::Map(_) => "SHADOW_MODE_CHEAP", + }, + ); + + let anti_alias = shaders + .get(match mode.aa { + AaMode::None => "antialias.none", + AaMode::Fxaa => "antialias.fxaa", + AaMode::MsaaX4 => "antialias.msaa-x4", + AaMode::MsaaX8 => "antialias.msaa-x8", + AaMode::MsaaX16 => "antialias.msaa-x16", + }) + .unwrap(); + + let cloud = shaders + .get(match mode.cloud { + CloudMode::None => "include.cloud.none", + _ => "include.cloud.regular", + }) + .unwrap(); + + let mut compiler = Compiler::new().ok_or(RenderError::ErrorInitializingCompiler)?; + let mut options = CompileOptions::new().ok_or(RenderError::ErrorInitializingCompiler)?; + options.set_optimization_level(OptimizationLevel::Performance); + options.set_forced_version_profile(430, shaderc::GlslProfile::Core); + options.set_include_callback(move |name, _, shader_name, _| { + Ok(ResolvedInclude { + resolved_name: name.to_string(), + content: match name { + "constants.glsl" => constants.clone(), + "globals.glsl" => globals.0.to_owned(), + "shadows.glsl" => shadows.0.to_owned(), + "sky.glsl" => sky.0.to_owned(), + "light.glsl" => light.0.to_owned(), + "srgb.glsl" => srgb.0.to_owned(), + "random.glsl" => random.0.to_owned(), + "lod.glsl" => lod.0.to_owned(), + "anti-aliasing.glsl" => anti_alias.0.to_owned(), + "cloud.glsl" => cloud.0.to_owned(), + other => return Err(format!("Include {} is not defined", other)), + }, + }) + }); + + let mut create_shader = |name, kind| { + let glsl = &shaders + .get(name) + .unwrap_or_else(|| panic!("Can't retrieve shader: {}", name)) + .0; + let file_name = format!("{}.glsl", name); + create_shader_module(device, &mut compiler, glsl, kind, &file_name, &options) + }; + + let selected_fluid_shader = ["fluid-frag.", match mode.fluid { + FluidMode::Cheap => "cheap", + FluidMode::Shiny => "shiny", + }] + .concat(); + + Ok(Self { + skybox_vert: create_shader("skybox-vert", ShaderKind::Vertex)?, + skybox_frag: create_shader("skybox-frag", ShaderKind::Fragment)?, + figure_vert: create_shader("figure-vert", ShaderKind::Vertex)?, + figure_frag: create_shader("figure-frag", ShaderKind::Fragment)?, + terrain_vert: create_shader("terrain-vert", ShaderKind::Vertex)?, + terrain_frag: create_shader("terrain-frag", ShaderKind::Fragment)?, + fluid_vert: create_shader("fluid-vert", ShaderKind::Vertex)?, + fluid_frag: create_shader(&selected_fluid_shader, ShaderKind::Fragment)?, + sprite_vert: create_shader("sprite-vert", ShaderKind::Vertex)?, + sprite_frag: create_shader("sprite-frag", ShaderKind::Fragment)?, + particle_vert: create_shader("particle-vert", ShaderKind::Vertex)?, + particle_frag: create_shader("particle-frag", ShaderKind::Fragment)?, + ui_vert: create_shader("ui-vert", ShaderKind::Vertex)?, + ui_frag: create_shader("ui-frag", ShaderKind::Fragment)?, + lod_terrain_vert: create_shader("lod-terrain-vert", ShaderKind::Vertex)?, + 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)?, + postprocess_vert: create_shader("postprocess-vert", ShaderKind::Vertex)?, + postprocess_frag: create_shader("postprocess-frag", ShaderKind::Fragment)?, + blit_vert: create_shader("blit-vert", ShaderKind::Vertex)?, + blit_frag: create_shader("blit-frag", ShaderKind::Fragment)?, + point_light_shadows_vert: create_shader( + "point-light-shadows-vert", + ShaderKind::Vertex, + )?, + light_shadows_directed_vert: create_shader( + "light-shadows-directed-vert", + ShaderKind::Vertex, + )?, + light_shadows_figure_vert: create_shader( + "light-shadows-figure-vert", + ShaderKind::Vertex, + )?, + }) + } +} + +fn create_shader_module( + device: &wgpu::Device, + compiler: &mut shaderc::Compiler, + source: &str, + kind: shaderc::ShaderKind, + file_name: &str, + options: &shaderc::CompileOptions, +) -> Result { + prof_span!(_guard, "create_shader_modules"); + use std::borrow::Cow; + + let spv = compiler + .compile_into_spirv(source, kind, file_name, "main", Some(options)) + .map_err(|e| (file_name, e))?; + + Ok(device.create_shader_module(&wgpu::ShaderModuleDescriptor { + label: Some(source), + source: wgpu::ShaderSource::SpirV(Cow::Borrowed(spv.as_binary())), + flags: wgpu::ShaderFlags::empty(), // TODO: renable wgpu::ShaderFlags::VALIDATION, + })) +} + +/// Things needed to create a pipeline +#[derive(Clone, Copy)] +struct PipelineNeeds<'a> { + device: &'a wgpu::Device, + layouts: &'a Layouts, + shaders: &'a ShaderModules, + mode: &'a RenderMode, + sc_desc: &'a wgpu::SwapChainDescriptor, +} + +/// Creates InterfacePipelines in parallel +fn create_interface_pipelines( + needs: PipelineNeeds, + pool: &rayon::ThreadPool, + tasks: [Task; 2], +) -> InterfacePipelines { + prof_span!(_guard, "create_interface_pipelines"); + + let [ui_task, blit_task] = tasks; + // Construct a pipeline for rendering UI elements + let create_ui = || { + ui_task.run( + || { + ui::UiPipeline::new( + needs.device, + &needs.shaders.ui_vert, + &needs.shaders.ui_frag, + needs.sc_desc, + &needs.layouts.global, + &needs.layouts.ui, + ) + }, + "ui pipeline creation", + ) + }; + + // Construct a pipeline for blitting, used during screenshotting + let create_blit = || { + blit_task.run( + || { + blit::BlitPipeline::new( + needs.device, + &needs.shaders.blit_vert, + &needs.shaders.blit_frag, + needs.sc_desc, + &needs.layouts.blit, + ) + }, + "blit pipeline creation", + ) + }; + + let (ui, blit) = pool.join(create_ui, create_blit); + + InterfacePipelines { ui, blit } +} + +/// Create IngamePipelines and shadow pipelines in parallel +fn create_ingame_and_shadow_pipelines( + needs: PipelineNeeds, + pool: &rayon::ThreadPool, + tasks: [Task; 12], +) -> IngameAndShadowPipelines { + prof_span!(_guard, "create_ingame_and_shadow_pipelines"); + + let PipelineNeeds { + device, + layouts, + shaders, + mode, + sc_desc, + } = needs; + + let [ + skybox_task, + figure_task, + terrain_task, + fluid_task, + sprite_task, + particle_task, + lod_terrain_task, + clouds_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 + // be skipped + point_shadow_task, + terrain_directed_shadow_task, + figure_directed_shadow_task, + ] = tasks; + + // TODO: pass in format of target color buffer + + // Pipeline for rendering skyboxes + let create_skybox = || { + skybox_task.run( + || { + skybox::SkyboxPipeline::new( + device, + &shaders.skybox_vert, + &shaders.skybox_frag, + &layouts.global, + mode.aa, + ) + }, + "skybox pipeline creation", + ) + }; + // Pipeline for rendering figures + let create_figure = || { + figure_task.run( + || { + figure::FigurePipeline::new( + device, + &shaders.figure_vert, + &shaders.figure_frag, + &layouts.global, + &layouts.figure, + mode.aa, + ) + }, + "figure pipeline creation", + ) + }; + // Pipeline for rendering terrain + let create_terrain = || { + terrain_task.run( + || { + terrain::TerrainPipeline::new( + device, + &shaders.terrain_vert, + &shaders.terrain_frag, + &layouts.global, + &layouts.terrain, + mode.aa, + ) + }, + "terrain pipeline creation", + ) + }; + // Pipeline for rendering fluids + let create_fluid = || { + fluid_task.run( + || { + fluid::FluidPipeline::new( + device, + &shaders.fluid_vert, + &shaders.fluid_frag, + &layouts.global, + &layouts.fluid, + &layouts.terrain, + mode.aa, + ) + }, + "fluid pipeline creation", + ) + }; + // Pipeline for rendering sprites + let create_sprite = || { + sprite_task.run( + || { + sprite::SpritePipeline::new( + device, + &shaders.sprite_frag, + &shaders.sprite_vert, + &layouts.global, + &layouts.sprite, + &layouts.terrain, + mode.aa, + ) + }, + "sprite pipeline creation", + ) + }; + // Pipeline for rendering particles + let create_particle = || { + particle_task.run( + || { + particle::ParticlePipeline::new( + device, + &shaders.particle_vert, + &shaders.particle_frag, + &layouts.global, + mode.aa, + ) + }, + "particle pipeline creation", + ) + }; + // Pipeline for rendering terrain + let create_lod_terrain = || { + lod_terrain_task.run( + || { + lod_terrain::LodTerrainPipeline::new( + device, + &shaders.lod_terrain_vert, + &shaders.lod_terrain_frag, + &layouts.global, + mode.aa, + ) + }, + "lod terrain pipeline creation", + ) + }; + // Pipeline for rendering our clouds (a kind of post-processing) + let create_clouds = || { + clouds_task.run( + || { + clouds::CloudsPipeline::new( + device, + &shaders.clouds_vert, + &shaders.clouds_frag, + &layouts.global, + &layouts.clouds, + mode.aa, + ) + }, + "clouds pipeline creation", + ) + }; + // Pipeline for rendering our post-processing + let create_postprocess = || { + postprocess_task.run( + || { + postprocess::PostProcessPipeline::new( + device, + &shaders.postprocess_vert, + &shaders.postprocess_frag, + sc_desc, + &layouts.global, + &layouts.postprocess, + ) + }, + "postprocess pipeline creation", + ) + }; + + // + // // Pipeline for rendering the player silhouette + // let player_shadow_pipeline = create_pipeline( + // factory, + // figure::pipe::Init { + // tgt_depth: (gfx::preset::depth::PASS_TEST/*, + // Stencil::new( + // Comparison::Equal, + // 0xff, + // (StencilOp::Keep, StencilOp::Keep, StencilOp::Keep), + // ),*/), + // ..figure::pipe::new() + // }, + // &figure_vert, + // &Glsl::load_watched( + // "voxygen.shaders.player-shadow-frag", + // shader_reload_indicator, + // ) + // .unwrap(), + // &include_ctx, + // gfx::state::CullFace::Back, + // )?; + + // Pipeline for rendering point light terrain shadow maps. + let create_point_shadow = || { + point_shadow_task.run( + || { + shadow::PointShadowPipeline::new( + device, + &shaders.point_light_shadows_vert, + &layouts.global, + &layouts.terrain, + mode.aa, + ) + }, + "point shadow pipeline creation", + ) + }; + // Pipeline for rendering directional light terrain shadow maps. + let create_terrain_directed_shadow = || { + terrain_directed_shadow_task.run( + || { + shadow::ShadowPipeline::new( + device, + &shaders.light_shadows_directed_vert, + &layouts.global, + &layouts.terrain, + mode.aa, + ) + }, + "terrain directed shadow pipeline creation", + ) + }; + // Pipeline for rendering directional light figure shadow maps. + let create_figure_directed_shadow = || { + figure_directed_shadow_task.run( + || { + shadow::ShadowFigurePipeline::new( + device, + &shaders.light_shadows_figure_vert, + &layouts.global, + &layouts.figure, + mode.aa, + ) + }, + "figure directed shadow pipeline creation", + ) + }; + + let j1 = || pool.join(create_skybox, create_figure); + let j2 = || pool.join(create_terrain, create_fluid); + 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); + let j6 = || { + pool.join( + create_terrain_directed_shadow, + create_figure_directed_shadow, + ) + }; + + // Ignore this + let ( + (((skybox, figure), (terrain, fluid)), ((sprite, particle), (lod_terrain, clouds))), + ((postprocess, point_shadow), (terrain_directed_shadow, figure_directed_shadow)), + ) = pool.join( + || pool.join(|| pool.join(j1, j2), || pool.join(j3, j4)), + || pool.join(j5, j6), + ); + + IngameAndShadowPipelines { + ingame: IngamePipelines { + skybox, + figure, + terrain, + fluid, + sprite, + particle, + lod_terrain, + clouds, + postprocess, + // player_shadow_pipeline, + }, + shadow: ShadowPipelines { + point: Some(point_shadow), + directed: Some(terrain_directed_shadow), + figure: Some(figure_directed_shadow), + }, + } +} + +/// Creates all the pipelines used to render. +/// Use this for the initial creation. +/// It blocks the main thread to create the interface pipelines while moving the +/// creation of other pipelines into the background +/// 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, + shaders: Shaders, + mode: RenderMode, + sc_desc: wgpu::SwapChainDescriptor, + has_shadow_views: bool, +) -> Result< + ( + InterfacePipelines, + PipelineCreation, + ), + RenderError, +> { + prof_span!(_guard, "initial_create_pipelines"); + + // Process shaders into modules + let shader_modules = ShaderModules::new(&device, &shaders, &mode, has_shadow_views)?; + + // Create threadpool for parallel portion + let pool = rayon::ThreadPoolBuilder::new() + .thread_name(|n| format!("pipeline-creation-{}", n)) + .build() + .unwrap(); + + let needs = PipelineNeeds { + device: &device, + layouts: &layouts, + shaders: &shader_modules, + mode: &mode, + sc_desc: &sc_desc, + }; + + // Create interface pipelines while blocking the main thread + // Note: we use a throwaway Progress tracker here since we don't need to track + // the progress + let interface_pipelines = + create_interface_pipelines(needs, &pool, Progress::new().create_tasks()); + + let pool = Arc::new(pool); + let send_pool = Arc::clone(&pool); + // Track pipeline creation progress + let progress = Arc::new(Progress::new()); + let (pipeline_send, pipeline_recv) = crossbeam::channel::bounded(0); + let pipeline_creation = PipelineCreation { + progress: Arc::clone(&progress), + recv: pipeline_recv, + }; + // Start background compilation + pool.spawn(move || { + let pool = &*send_pool; + + let needs = PipelineNeeds { + device: &device, + layouts: &layouts, + shaders: &shader_modules, + mode: &mode, + sc_desc: &sc_desc, + }; + + let pipelines = create_ingame_and_shadow_pipelines(needs, &pool, progress.create_tasks()); + + pipeline_send.send(pipelines).expect("Channel disconnected"); + }); + + Ok((interface_pipelines, pipeline_creation)) +} + +/// Creates all the pipelines used to render. +/// 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 +pub(super) fn recreate_pipelines( + device: Arc, + layouts: Arc, + shaders: Shaders, + mode: RenderMode, + sc_desc: wgpu::SwapChainDescriptor, + has_shadow_views: bool, +) -> PipelineCreation> { + prof_span!(_guard, "recreate_pipelines"); + + // Create threadpool for parallel portion + let pool = rayon::ThreadPoolBuilder::new() + .thread_name(|n| format!("pipeline-recreation-{}", n)) + .build() + .unwrap(); + let pool = Arc::new(pool); + let send_pool = Arc::clone(&pool); + // Track pipeline creation progress + let progress = Arc::new(Progress::new()); + let (result_send, result_recv) = crossbeam::channel::bounded(0); + let pipeline_creation = PipelineCreation { + progress: Arc::clone(&progress), + recv: result_recv, + }; + // Start background compilation + pool.spawn(move || { + let pool = &*send_pool; + + // Create tasks upfront so the total counter will be accurate + let shader_task = progress.create_task(); + let interface_tasks = progress.create_tasks(); + let ingame_and_shadow_tasks = progress.create_tasks(); + + // 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; + }, + }; + drop(guard); + + let needs = PipelineNeeds { + device: &device, + layouts: &layouts, + shaders: &shader_modules, + mode: &mode, + sc_desc: &sc_desc, + }; + + // Create interface pipelines + let interface = create_interface_pipelines(needs, &pool, interface_tasks); + + // Create the rest of the pipelines + let IngameAndShadowPipelines { ingame, shadow } = + create_ingame_and_shadow_pipelines(needs, &pool, ingame_and_shadow_tasks); + + // Send them + result_send + .send(Ok((Pipelines::consolidate(interface, ingame), shadow))) + .expect("Channel disconnected"); + }); + + pipeline_creation +} + +use core::sync::atomic::{AtomicUsize, Ordering}; + +/// Represents future task that has not been started +/// Dropping this will mark the task as complete though +struct Task<'a> { + progress: &'a Progress, +} + +/// Represents in-progress task, drop when complete +struct StartedTask<'a> { + span: common_base::ProfSpan, + task: Task<'a>, +} + +#[derive(Default)] +struct Progress { + total: AtomicUsize, + complete: AtomicUsize, + // Note: could easily add a "started counter" if that would be useful +} + +impl Progress { + pub fn new() -> Self { Self::default() } + + /// Creates a task incrementing the total number of tasks + /// NOTE: all tasks should be created as upfront as possible so that the + /// total reflects the amount of tasks that will need to be completed + pub fn create_task(&self) -> Task { + self.total.fetch_add(1, Ordering::Relaxed); + Task { progress: &self } + } + + /// Helper method for creating tasks to do in bulk + pub fn create_tasks(&self) -> [Task; N] { [(); N].map(|()| self.create_task()) } +} + +impl<'a> Task<'a> { + /// Start a task. + /// The name is used for profiling. + fn start(self, name: &str) -> StartedTask<'a> { + StartedTask { + span: { + prof_span!(guard, name); + guard + }, + task: self, + } + } + + /// Convenience function to run the provided closure as the task + /// Completing the task when this function returns + fn run(self, task: impl FnOnce() -> T, name: &str) -> T { + let _guard = self.start(name); + task() + } +} + +impl Drop for Task<'_> { + fn drop(&mut self) { self.progress.complete.fetch_add(1, Ordering::Relaxed); } +} + +pub struct PipelineCreation { + progress: Arc, + recv: crossbeam::channel::Receiver, +} + +impl PipelineCreation { + /// Returns the number of pipelines being built and completed + /// (total, complete) + /// NOTE: there is no guarantee that `total >= complete` due to relaxed + /// atomics but this property should hold most of the time + pub fn status(&self) -> (usize, usize) { + let progress = &*self.progress; + ( + progress.total.load(Ordering::Relaxed), + progress.complete.load(Ordering::Relaxed), + ) + } + + /// Checks if the pipelines were completed and returns the result if they + /// were + pub fn try_complete(self) -> Result { + use crossbeam::channel::TryRecvError; + match self.recv.try_recv() { + // Yay! + Ok(T) => Ok(T), + // Normal error, we have not gotten anything yet + Err(TryRecvError::Empty) => Err(self), + // How rude! + Err(TryRecvError::Disconnected) => { + panic!( + "Background thread panicked or dropped the sender without sending anything!" + ); + }, + } + } +} diff --git a/voxygen/src/render/renderer/shaders.rs b/voxygen/src/render/renderer/shaders.rs index 8e21c8b12c..3f1b0bb2b5 100644 --- a/voxygen/src/render/renderer/shaders.rs +++ b/voxygen/src/render/renderer/shaders.rs @@ -14,6 +14,9 @@ impl assets::Asset for Glsl { const EXTENSION: &'static str = "glsl"; } +// Note: we use this clone to send the shaders to a background thread +// TODO: use Arc-ed asset and clone that instead +#[derive(Clone)] pub struct Shaders { shaders: HashMap>, } @@ -44,7 +47,6 @@ impl assets::Compound for Shaders { "figure-vert", "light-shadows-figure-vert", "light-shadows-directed-vert", - "light-shadows-directed-frag", "point-light-shadows-vert", "skybox-vert", "skybox-frag", @@ -68,9 +70,8 @@ impl assets::Compound for Shaders { "postprocess-frag", "blit-vert", "blit-frag", - "player-shadow-frag", - "light-shadows-geom", - "light-shadows-frag", + //"player-shadow-frag", + //"light-shadows-geom", ]; let shaders = shaders diff --git a/voxygen/src/render/renderer/shadow_map.rs b/voxygen/src/render/renderer/shadow_map.rs index 6ba75961a2..068bdd5654 100644 --- a/voxygen/src/render/renderer/shadow_map.rs +++ b/voxygen/src/render/renderer/shadow_map.rs @@ -1,4 +1,8 @@ -use super::super::{pipelines::shadow, texture::Texture}; +use super::{ + super::{pipelines::shadow, texture::Texture, RenderError, ShadowMapMode}, + Renderer, +}; +use vek::*; /// A type that holds shadow map data. Since shadow mapping may not be /// supported on all platforms, we try to keep it separate. @@ -24,6 +28,247 @@ pub enum ShadowMap { } impl ShadowMap { + pub fn new( + device: &wgpu::Device, + queue: &wgpu::Queue, + point: Option, + directed: Option, + figure: Option, + shadow_views: Option<(Texture, Texture)>, + ) -> Self { + if let ( + Some(point_pipeline), + Some(terrain_directed_pipeline), + Some(figure_directed_pipeline), + Some(shadow_views), + ) = (point, directed, figure, shadow_views) + { + let (point_depth, directed_depth) = shadow_views; + + let layout = shadow::ShadowLayout::new(&device); + + Self::Enabled(ShadowMapRenderer { + // point_encoder: factory.create_command_buffer().into(), + // directed_encoder: factory.create_command_buffer().into(), + directed_depth, + point_depth, + + point_pipeline, + terrain_directed_pipeline, + figure_directed_pipeline, + + layout, + }) + } else { + let (dummy_point, dummy_directed) = Self::create_dummy_shadow_tex(&device, &queue); + Self::Disabled { + dummy_point, + dummy_directed, + } + } + } + + fn create_dummy_shadow_tex(device: &wgpu::Device, queue: &wgpu::Queue) -> (Texture, Texture) { + let make_tex = |view_dim, depth| { + let tex = wgpu::TextureDescriptor { + label: None, + size: wgpu::Extent3d { + width: 4, + height: 4, + depth_or_array_layers: depth, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Depth24Plus, + usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::RENDER_ATTACHMENT, + }; + + let view = wgpu::TextureViewDescriptor { + label: None, + format: Some(wgpu::TextureFormat::Depth24Plus), + dimension: Some(view_dim), + aspect: wgpu::TextureAspect::DepthOnly, + base_mip_level: 0, + mip_level_count: None, + base_array_layer: 0, + array_layer_count: None, + }; + + let sampler_info = wgpu::SamplerDescriptor { + label: None, + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Linear, + mipmap_filter: wgpu::FilterMode::Nearest, + compare: Some(wgpu::CompareFunction::LessEqual), + ..Default::default() + }; + + Texture::new_raw(device, &tex, &view, &sampler_info) + }; + + let cube_tex = make_tex(wgpu::TextureViewDimension::Cube, 6); + let tex = make_tex(wgpu::TextureViewDimension::D2, 1); + + // Clear to 1.0 + let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Dummy shadow tex clearing encoder"), + }); + let mut clear = |tex: &Texture| { + encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("Clear dummy shadow texture"), + color_attachments: &[], + depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { + view: &tex.view, + depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Clear(1.0), + store: true, + }), + stencil_ops: None, + }), + }); + }; + clear(&cube_tex); + clear(&tex); + drop(clear); + queue.submit(std::iter::once(encoder.finish())); + + (cube_tex, tex) + } + + /// Create textures and views for shadow maps. + /// Returns (point, directed) + pub(super) fn create_shadow_views( + device: &wgpu::Device, + size: (u32, u32), + mode: &ShadowMapMode, + ) -> Result<(Texture, Texture), RenderError> { + // (Attempt to) apply resolution factor to shadow map resolution. + let resolution_factor = mode.resolution.clamped(0.25, 4.0); + + let max_texture_size = Renderer::max_texture_size_raw(device); + // Limit to max texture size, rather than erroring. + let size = Vec2::new(size.0, size.1).map(|e| { + let size = e as f32 * resolution_factor; + // NOTE: We know 0 <= e since we clamped the resolution factor to be between + // 0.25 and 4.0. + if size <= max_texture_size as f32 { + size as u32 + } else { + max_texture_size + } + }); + + let levels = 1; + // Limit to max texture size rather than erroring. + let two_size = size.map(|e| { + u32::checked_next_power_of_two(e) + .filter(|&e| e <= max_texture_size) + .unwrap_or(max_texture_size) + }); + let min_size = size.reduce_min(); + let max_size = size.reduce_max(); + let _min_two_size = two_size.reduce_min(); + let _max_two_size = two_size.reduce_max(); + // For rotated shadow maps, the maximum size of a pixel along any axis is the + // size of a diagonal along that axis. + let diag_size = size.map(f64::from).magnitude(); + let diag_cross_size = f64::from(min_size) / f64::from(max_size) * diag_size; + let (diag_size, _diag_cross_size) = + if 0.0 < diag_size && diag_size <= f64::from(max_texture_size) { + // NOTE: diag_cross_size must be non-negative, since it is the ratio of a + // non-negative and a positive number (if max_size were zero, + // diag_size would be 0 too). And it must be <= diag_size, + // since min_size <= max_size. Therefore, if diag_size fits in a + // u16, so does diag_cross_size. + (diag_size as u32, diag_cross_size as u32) + } else { + // Limit to max texture resolution rather than error. + (max_texture_size as u32, max_texture_size as u32) + }; + let diag_two_size = u32::checked_next_power_of_two(diag_size) + .filter(|&e| e <= max_texture_size) + // Limit to max texture resolution rather than error. + .unwrap_or(max_texture_size); + + let point_shadow_tex = wgpu::TextureDescriptor { + label: None, + size: wgpu::Extent3d { + width: diag_two_size / 4, + height: diag_two_size / 4, + depth_or_array_layers: 6, + }, + mip_level_count: levels, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Depth24Plus, + usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::RENDER_ATTACHMENT, + }; + + //TODO: (0, levels - 1), ?? from master + let point_shadow_view = wgpu::TextureViewDescriptor { + label: None, + format: Some(wgpu::TextureFormat::Depth24Plus), + dimension: Some(wgpu::TextureViewDimension::Cube), + aspect: wgpu::TextureAspect::DepthOnly, + base_mip_level: 0, + mip_level_count: None, + base_array_layer: 0, + array_layer_count: None, + }; + + let directed_shadow_tex = wgpu::TextureDescriptor { + label: None, + size: wgpu::Extent3d { + width: diag_two_size, + height: diag_two_size, + depth_or_array_layers: 1, + }, + mip_level_count: levels, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Depth24Plus, + usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::RENDER_ATTACHMENT, + }; + + let directed_shadow_view = wgpu::TextureViewDescriptor { + label: None, + format: Some(wgpu::TextureFormat::Depth24Plus), + dimension: Some(wgpu::TextureViewDimension::D2), + aspect: wgpu::TextureAspect::DepthOnly, + base_mip_level: 0, + mip_level_count: None, + base_array_layer: 0, + array_layer_count: None, + }; + + let sampler_info = wgpu::SamplerDescriptor { + label: None, + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Linear, + mipmap_filter: wgpu::FilterMode::Nearest, + compare: Some(wgpu::CompareFunction::LessEqual), + ..Default::default() + }; + + let point_shadow_tex = + Texture::new_raw(device, &point_shadow_tex, &point_shadow_view, &sampler_info); + let directed_shadow_tex = Texture::new_raw( + device, + &directed_shadow_tex, + &directed_shadow_view, + &sampler_info, + ); + + Ok((point_shadow_tex, directed_shadow_tex)) + } + pub fn textures(&self) -> (&Texture, &Texture) { match self { Self::Enabled(renderer) => (&renderer.point_depth, &renderer.directed_depth),