use super::{ consts::Consts, instances::Instances, mesh::Mesh, model::Model, pipelines::{ clouds, figure, fluid, lod_terrain, particle, postprocess, shadow, skybox, sprite, terrain, ui, GlobalModel, Globals, }, texture::Texture, AaMode, AddressMode, CloudMode, FilterMode, FluidMode, LightingMode, RenderError, RenderMode, ShadowMapMode, ShadowMode, Vertex, }; use common::assets::{self, AssetExt, AssetHandle}; use common_base::span; use core::convert::TryFrom; use glsl_include::Context as IncludeContext; use tracing::{error, info, warn}; use vek::*; // /// Represents the format of the pre-processed color target. // // TODO: `(gfx::format::R11_G11_B10, gfx::format::Float)` would be better in // // theory, but it doesn't seem to work // pub type TgtColorFmt = gfx::format::Rgba16F; // /// Represents the format of the pre-processed depth and stencil target. // pub type TgtDepthStencilFmt = gfx::format::Depth; // // /// Represents the format of the window's color target. // pub type WinColorFmt = gfx::format::Srgba8; // /// Represents the format of the window's depth target. // pub type WinDepthFmt = gfx::format::Depth; // // /// Represents the format of the pre-processed shadow depth target. // pub type ShadowDepthStencilFmt = gfx::format::Depth; // // /// A handle to a pre-processed color target. // pub type TgtColorView = gfx::handle::RenderTargetView; /// A handle to a pre-processed depth target. // pub type TgtDepthStencilView = // gfx::handle::DepthStencilView; // // /// A handle to a window color target. // pub type WinColorView = gfx::handle::RenderTargetView; /// A handle to a window depth target. // pub type WinDepthView = gfx::handle::DepthStencilView; // // /// Represents the format of LOD shadows. // pub type LodTextureFmt = (gfx::format::R8_G8_B8_A8, gfx::format::Unorm); // // /// Represents the format of LOD altitudes. // pub type LodAltFmt = (gfx::format::R16_G16, gfx::format::Unorm); // // /// Represents the format of LOD map colors. // pub type LodColorFmt = (gfx::format::R8_G8_B8_A8, gfx::format::Srgb); // // /// Represents the format of greedy meshed color-light textures. // pub type ColLightFmt = (gfx::format::R8_G8_B8_A8, gfx::format::Unorm); // // /// A handle to a shadow depth target. // pub type ShadowDepthStencilView = // gfx::handle::DepthStencilView; /// A handle to a shadow depth target as a resource. // pub type ShadowResourceView = gfx::handle::ShaderResourceView< // gfx_backend::Resources, // ::View, // >; // // /// A handle to a render color target as a resource. // pub type TgtColorRes = gfx::handle::ShaderResourceView< // gfx_backend::Resources, // ::View, // >; // // /// A handle to a render depth target as a resource. // pub type TgtDepthRes = gfx::handle::ShaderResourceView< // gfx_backend::Resources, // ::View, // >; // // /// A handle to a greedy meshed color-light texture as a resource. // pub type ColLightRes = gfx::handle::ShaderResourceView< // gfx_backend::Resources, // ::View, // >; // /// A type representing data that can be converted to an immutable texture // map /// of ColLight data (used for texture atlases created during greedy // meshing). pub type ColLightInfo = ( // Vec<<::Surface as // gfx::format::SurfaceTyped>::DataType>, Vec2, // ); /// Load from a GLSL file. pub struct Glsl(String); impl From for Glsl { fn from(s: String) -> Glsl { Glsl(s) } } impl assets::Asset for Glsl { type Loader = assets::LoadFrom; const EXTENSION: &'static str = "glsl"; } struct Shaders { constants: AssetHandle, globals: AssetHandle, sky: AssetHandle, light: AssetHandle, srgb: AssetHandle, random: AssetHandle, lod: AssetHandle, shadows: AssetHandle, anti_alias_none: AssetHandle, anti_alias_fxaa: AssetHandle, anti_alias_msaa_x4: AssetHandle, anti_alias_msaa_x8: AssetHandle, anti_alias_msaa_x16: AssetHandle, cloud_none: AssetHandle, cloud_regular: AssetHandle, figure_vert: AssetHandle, terrain_point_shadow_vert: AssetHandle, terrain_directed_shadow_vert: AssetHandle, figure_directed_shadow_vert: AssetHandle, directed_shadow_frag: AssetHandle, skybox_vert: AssetHandle, skybox_frag: AssetHandle, figure_frag: AssetHandle, terrain_vert: AssetHandle, terrain_frag: AssetHandle, fluid_vert: AssetHandle, fluid_frag_cheap: AssetHandle, fluid_frag_shiny: AssetHandle, sprite_vert: AssetHandle, sprite_frag: AssetHandle, particle_vert: AssetHandle, particle_frag: AssetHandle, ui_vert: AssetHandle, ui_frag: AssetHandle, lod_terrain_vert: AssetHandle, lod_terrain_frag: AssetHandle, clouds_vert: AssetHandle, clouds_frag: AssetHandle, postprocess_vert: AssetHandle, postprocess_frag: AssetHandle, player_shadow_frag: AssetHandle, light_shadows_geom: AssetHandle, light_shadows_frag: AssetHandle, } impl assets::Compound for Shaders { // TODO: Taking the specifier argument as a base for shaders specifiers // would allow to use several shaders groups easily fn load( _: &assets::AssetCache, _: &str, ) -> Result { Ok(Shaders { constants: AssetExt::load("voxygen.shaders.include.constants")?, globals: AssetExt::load("voxygen.shaders.include.globals")?, sky: AssetExt::load("voxygen.shaders.include.sky")?, light: AssetExt::load("voxygen.shaders.include.light")?, srgb: AssetExt::load("voxygen.shaders.include.srgb")?, random: AssetExt::load("voxygen.shaders.include.random")?, lod: AssetExt::load("voxygen.shaders.include.lod")?, shadows: AssetExt::load("voxygen.shaders.include.shadows")?, anti_alias_none: AssetExt::load("voxygen.shaders.antialias.none")?, anti_alias_fxaa: AssetExt::load("voxygen.shaders.antialias.fxaa")?, anti_alias_msaa_x4: AssetExt::load("voxygen.shaders.antialias.msaa-x4")?, anti_alias_msaa_x8: AssetExt::load("voxygen.shaders.antialias.msaa-x8")?, anti_alias_msaa_x16: AssetExt::load("voxygen.shaders.antialias.msaa-x16")?, cloud_none: AssetExt::load("voxygen.shaders.include.cloud.none")?, cloud_regular: AssetExt::load("voxygen.shaders.include.cloud.regular")?, figure_vert: AssetExt::load("voxygen.shaders.figure-vert")?, terrain_point_shadow_vert: AssetExt::load("voxygen.shaders.light-shadows-vert")?, terrain_directed_shadow_vert: AssetExt::load( "voxygen.shaders.light-shadows-directed-vert", )?, figure_directed_shadow_vert: AssetExt::load( "voxygen.shaders.light-shadows-figure-vert", )?, directed_shadow_frag: AssetExt::load("voxygen.shaders.light-shadows-directed-frag")?, skybox_vert: AssetExt::load("voxygen.shaders.skybox-vert")?, skybox_frag: AssetExt::load("voxygen.shaders.skybox-frag")?, figure_frag: AssetExt::load("voxygen.shaders.figure-frag")?, terrain_vert: AssetExt::load("voxygen.shaders.terrain-vert")?, terrain_frag: AssetExt::load("voxygen.shaders.terrain-frag")?, fluid_vert: AssetExt::load("voxygen.shaders.fluid-vert")?, fluid_frag_cheap: AssetExt::load("voxygen.shaders.fluid-frag.cheap")?, fluid_frag_shiny: AssetExt::load("voxygen.shaders.fluid-frag.shiny")?, sprite_vert: AssetExt::load("voxygen.shaders.sprite-vert")?, sprite_frag: AssetExt::load("voxygen.shaders.sprite-frag")?, particle_vert: AssetExt::load("voxygen.shaders.particle-vert")?, particle_frag: AssetExt::load("voxygen.shaders.particle-frag")?, ui_vert: AssetExt::load("voxygen.shaders.ui-vert")?, ui_frag: AssetExt::load("voxygen.shaders.ui-frag")?, lod_terrain_vert: AssetExt::load("voxygen.shaders.lod-terrain-vert")?, lod_terrain_frag: AssetExt::load("voxygen.shaders.lod-terrain-frag")?, clouds_vert: AssetExt::load("voxygen.shaders.clouds-vert")?, clouds_frag: AssetExt::load("voxygen.shaders.clouds-frag")?, postprocess_vert: AssetExt::load("voxygen.shaders.postprocess-vert")?, postprocess_frag: AssetExt::load("voxygen.shaders.postprocess-frag")?, player_shadow_frag: AssetExt::load("voxygen.shaders.player-shadow-frag")?, light_shadows_geom: AssetExt::load("voxygen.shaders.light-shadows-geom")?, light_shadows_frag: AssetExt::load("voxygen.shaders.light-shadows-frag")?, }) } } /// A type that holds shadow map data. Since shadow mapping may not be /// supported on all platforms, we try to keep it separate. pub struct ShadowMapRenderer { // directed_encoder: gfx::Encoder, // point_encoder: gfx::Encoder, directed_depth_stencil_view: wgpu::TextureView, directed_sampler: wgpu::Sampler, point_depth_stencil_view: wgpu::TextureView, point_sampler: wgpu::Sampler, point_pipeline: wgpu::RenderPipeline, terrain_directed_pipeline: wgpu::RenderPipeline, figure_directed_pipeline: wgpu::RenderPipeline, } /// 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 { device: wgpu::Device, queue: wgpu::Queue, swap_chain: wgpu::SwapChain, sc_desc: wgpu::SwapChainDescriptor, win_depth_view: wgpu::TextureView, tgt_color_view: wgpu::TextureView, tgt_depth_stencil_view: wgpu::TextureView, // TODO: rename tgt_color_pp_view: wgpu::TextureView, sampler: wgpu::Sampler, shadow_map: Option, skybox_pipeline: wgpu::RenderPipeline, figure_pipeline: wgpu::RenderPipeline, terrain_pipeline: wgpu::RenderPipeline, fluid_pipeline: wgpu::RenderPipeline, sprite_pipeline: wgpu::RenderPipeline, particle_pipeline: wgpu::RenderPipeline, ui_pipeline: wgpu::RenderPipeline, lod_terrain_pipeline: wgpu::RenderPipeline, clouds_pipeline: wgpu::RenderPipeline, postprocess_pipeline: wgpu::RenderPipeline, #[allow(dead_code)] //TODO: remove ? player_shadow_pipeline: wgpu::RenderPipeline, shaders: AssetHandle, noise_tex: Texture, mode: RenderMode, } impl Renderer { /// Create a new `Renderer` from a variety of backend-specific components /// and the window targets. pub async fn new( window: &winit::window::Window, mode: RenderMode, ) -> Result { // Enable seamless cubemaps globally, where available--they are essentially a // strict improvement on regular cube maps. // // Note that since we only have to enable this once globally, there is no point // in doing this on rerender. // Self::enable_seamless_cube_maps(&mut device); let dims = window.inner_size(); let instance = wgpu::Instance::new(wgpu::BackendBit::PRIMARY | wgpu::BackendBit::SECONDARY); // This is unsafe because the window handle must be valid, if you find a way to // have an invalid winit::Window then you have bigger issues #[allow(unsafe_code)] let surface = unsafe { instance.create_surface(window) }; let adapter = instance .request_adapter(wgpu::RequestAdapterOptionsBase { power_preference: wgpu::PowerPreference::HighPerformance, compatible_surface: Some(surface), }) .await .ok_or(RenderError::CouldNotFindAdapter)?; use wgpu::{Features, Limits}; let (device, queue) = adapter .request_device( wgpu::DeviceDescriptor { // TODO features: Features::DEPTH_CLAMPING, limits: Limits::default(), shader_validation: true, }, None, ) .await?; let info = device.get_info(); info!( ?info.name, ?info.vendor, ?info.backend, ?info.device, ?info.device_type, "selected graphics device" ); let sc_desc = wgpu::SwapChainDescriptor { usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, format: wgpu::TextureFormat::Bgra8UnormSrgb, width: dims.0, height: dims.1, present_mode: wgpu::PresentMode::Immediate, }; let swap_chain = device.create_swap_chain(&surface, &sc_desc); let shadow_views = Self::create_shadow_views( &device, (dims.0, dims.1), &ShadowMapMode::try_from(mode.shadow).unwrap_or_default(), ) .map_err(|err| { warn!("Could not create shadow map views: {:?}", err); }) .ok(); let shaders = Shaders::load_expect(""); let ( skybox_pipeline, figure_pipeline, terrain_pipeline, fluid_pipeline, sprite_pipeline, particle_pipeline, ui_pipeline, lod_terrain_pipeline, clouds_pipeline, postprocess_pipeline, player_shadow_pipeline, point_shadow_pipeline, terrain_directed_shadow_pipeline, figure_directed_shadow_pipeline, ) = create_pipelines( &device, &mode, shadow_views.is_some(), )?; let (tgt_color_view, tgt_depth_stencil_view, tgt_color_pp_view, win_depth_view) = Self::create_rt_views(&device, (dims.0, dims.1), &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, shadow_views, ) { let ( point_depth_stencil_view, point_res, point_sampler, directed_depth_stencil_view, directed_res, directed_sampler, ) = shadow_views; Some(ShadowMapRenderer { directed_depth_stencil_view, directed_sampler, // point_encoder: factory.create_command_buffer().into(), // directed_encoder: factory.create_command_buffer().into(), point_depth_stencil_view, point_sampler, point_pipeline, terrain_directed_pipeline, figure_directed_pipeline, }) } else { None }; let sampler = device.create_sampler(&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: None, ..Default::default() }); let noise_tex = Texture::new( &device, &queue, &assets::Image::load_expect("voxygen.texture.noise").read().0, Some(wgpu::FilterMode::Linear), Some(wgpu::AddressMode::Repeat), )?; Ok(Self { device, queue, swap_chain, sc_desc, win_depth_view, tgt_color_view, tgt_depth_stencil_view, tgt_color_pp_view, sampler, shadow_map, skybox_pipeline, figure_pipeline, terrain_pipeline, fluid_pipeline, sprite_pipeline, particle_pipeline, ui_pipeline, lod_terrain_pipeline, clouds_pipeline, postprocess_pipeline, player_shadow_pipeline, shaders, noise_tex, mode, }) } /// Get references to the internal render target views that get rendered to /// before post-processing. #[allow(dead_code)] pub fn tgt_views(&self) -> (&wgpu::TextureView, &wgpu::TextureView) { (&self.tgt_color_view, &self.tgt_depth_stencil_view) } /// Get references to the internal render target views that get displayed /// directly by the window. #[allow(dead_code)] pub fn win_views(&self) -> &wgpu::TextureView { &self.win_depth_view } /// Change the render mode. pub fn set_render_mode(&mut self, mode: RenderMode) -> Result<(), RenderError> { self.mode = mode; // Recreate render target self.on_resize()?; // Recreate pipelines with the new AA mode self.recreate_pipelines(); Ok(()) } /// Get the render mode. pub fn render_mode(&self) -> &RenderMode { &self.mode } /// Resize internal render targets to match window render target dimensions. pub fn on_resize(&mut self) -> Result<(), RenderError> { let dims = self.win_color_view.get_dimensions(); // Avoid panics when creating texture with w,h of 0,0. if dims.0 != 0 && dims.1 != 0 { let ( tgt_color_view, tgt_depth_stencil_view, tgt_color_pp_view, tgt_color_res, tgt_depth_res, tgt_color_res_pp, ) = Self::create_rt_views(&mut self.factory, (dims.0, dims.1), &self.mode)?; self.tgt_color_res = tgt_color_res; self.tgt_depth_res = tgt_depth_res; self.tgt_color_res_pp = tgt_color_res_pp; self.tgt_color_view = tgt_color_view; self.tgt_depth_stencil_view = tgt_depth_stencil_view; self.tgt_color_pp_view = tgt_color_pp_view; if let (Some(shadow_map), ShadowMode::Map(mode)) = (self.shadow_map.as_mut(), self.mode.shadow) { match Self::create_shadow_views(&mut self.factory, (dims.0, dims.1), &mode) { Ok(( point_depth_stencil_view, point_res, point_sampler, directed_depth_stencil_view, directed_res, directed_sampler, )) => { shadow_map.point_depth_stencil_view = point_depth_stencil_view; shadow_map.point_res = point_res; shadow_map.point_sampler = point_sampler; shadow_map.directed_depth_stencil_view = directed_depth_stencil_view; shadow_map.directed_res = directed_res; shadow_map.directed_sampler = directed_sampler; }, Err(err) => { warn!("Could not create shadow map views: {:?}", err); }, } } } Ok(()) } fn create_rt_views( device: &wgpu::Device, size: (u16, u16), mode: &RenderMode, ) -> Result< ( wgpu::TextureView, wgpu::TextureView, wgpu::TextureView, wgpu::TextureView, ), RenderError, > { let upscaled = Vec2::from(size) .map(|e: u16| (e as f32 * mode.upscale_mode.factor) as u16) .into_tuple(); let (width, height, sample_count) = match mode.aa { AaMode::None | AaMode::Fxaa => (upscaled.0, upscaled.1, 1), // TODO: Ensure sampling in the shader is exactly between the 4 texels // TODO: Figure out how to do upscaling correctly with SSAA AaMode::MsaaX4 => (upscaled.0, upscaled.1, 4), AaMode::MsaaX8 => (upscaled.0, upscaled.1, 8), AaMode::MsaaX16 => (upscaled.0, upscaled.1, 16), }; let levels = 1; let mut color_view = || { let tex = device.create_texture(&wgpu::TextureDescriptor { label: None, size: wgpu::Extent3d { width, height, depth: 1, }, mip_level_count: levels, sample_count, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::Rgba8UnormSrgb, usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::OUTPUT_ATTACHMENT, }); tex.create_view(&wgpu::TextureViewDescriptor { label: None, format: Some(wgpu::TextureFormat::Rgba8UnormSrgb), dimension: Some(wgpu::TextureViewDimension::D2), aspect: wgpu::TextureAspect::Color, base_mip_level: 0, level_count: Some(levels), base_array_layer: 0, array_layer_count: None, }) }; let tgt_color_view = color_view(); let tgt_color_pp_view = color_view(); let tgt_depth_stencil_tex = device.create_texture(&wgpu::TextureDescriptor { label: None, size: wgpu::Extent3d { width, height, depth: 1, }, mip_level_count: levels, sample_count, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::Depth24Plus, usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::OUTPUT_ATTACHMENT, }); let tgt_depth_stencil_view = tgt_depth_stencil_tex.create_view(&wgpu::TextureViewDescriptor { label: None, format: Some(wgpu::TextureFormat::Depth24Plus), dimension: Some(wgpu::TextureViewDimension::D2), aspect: wgpu::TextureAspect::DepthOnly, base_mip_level: 0, level_count: Some(levels), base_array_layer: 0, array_layer_count: None, }); let win_depth_tex = device.create_texture(&wgpu::TextureDescriptor { label: None, size: wgpu::Extent3d { width: size.0, height: size.1, depth: 1, }, mip_level_count: levels, sample_count, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::Depth24Plus, usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, }); let win_depth_view = tgt_depth_stencil_tex.create_view(&wgpu::TextureViewDescriptor { label: None, format: Some(wgpu::TextureFormat::Depth24Plus), dimension: Some(wgpu::TextureViewDimension::D2), aspect: wgpu::TextureAspect::DepthOnly, base_mip_level: 0, level_count: Some(levels), base_array_layer: 0, array_layer_count: None, }); Ok(( tgt_color_view, tgt_depth_stencil_view, tgt_color_pp_view, win_depth_view, )) } /// 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: (u16, u16), mode: &ShadowMapMode, ) -> Result< ( wgpu::TextureView, wgpu::Sampler, wgpu::TextureView, wgpu::Sampler, ), 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 = f32::from(e) * resolution_factor; // NOTE: We know 0 <= e since we clamped the resolution factor to be between // 0.25 and 4.0. if size <= f32::from(max_texture_size) { size as u16 } else { max_texture_size } }); let levels = 1; // Limit to max texture size rather than erroring. let two_size = size.map(|e| { u16::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 u16, diag_cross_size as u16) } else { // Limit to max texture resolution rather than error. (max_texture_size as u16, max_texture_size as u16) }; let diag_two_size = u16::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 = device.create_texture(&wgpu::TextureDescriptor { label: None, size: wgpu::Extent3d { width: diag_two_size / 4, height: diag_two_size / 4, depth: 6, }, mip_level_count: levels, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::Depth24Plus, usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::OUTPUT_ATTACHMENT, }); let point_tgt_shadow_view = point_shadow_tex.create_view(&wgpu::TextureViewDescriptor { label: None, format: Some(wgpu::TextureFormat::Depth24Plus), dimension: Some(wgpu::TextureViewDimension::Cube), aspect: wgpu::TextureAspect::DepthOnly, base_mip_level: 0, level_count: Some(levels), base_array_layer: 0, array_layer_count: None, }); let directed_shadow_tex = device.create_texture(&wgpu::TextureDescriptor { label: None, size: wgpu::Extent3d { width: diag_two_size, height: diag_two_size, depth: 1, }, mip_level_count: levels, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::Depth24Plus, usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::OUTPUT_ATTACHMENT, }); let directed_tgt_shadow_view = point_shadow_tex.create_view(&wgpu::TextureViewDescriptor { label: None, format: Some(wgpu::TextureFormat::Depth24Plus), dimension: Some(wgpu::TextureViewDimension::D2), aspect: wgpu::TextureAspect::DepthOnly, base_mip_level: 0, level_count: Some(levels), 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_sampler = device.create_sampler(&sampler_info); let directed_shadow_tex_sampler = device.create_sampler(&sampler_info); Ok(( point_tgt_shadow_view, point_shadow_tex_sampler, directed_tgt_shadow_view, directed_shadow_tex_sampler, )) } /// Get the resolution of the render target. /// Note: the change after a resize can be delayed so /// don't rely on this value being constant between resize events pub fn get_resolution(&self) -> Vec2 { Vec2::new( self.win_color_view.get_dimensions().0, self.win_color_view.get_dimensions().1, ) } /// Get the resolution of the shadow render target. pub fn get_shadow_resolution(&self) -> (Vec2, Vec2) { if let Some(shadow_map) = &self.shadow_map { let point_dims = shadow_map.point_depth_stencil_view.get_dimensions(); let directed_dims = shadow_map.directed_depth_stencil_view.get_dimensions(); ( Vec2::new(point_dims.0, point_dims.1), Vec2::new(directed_dims.0, directed_dims.1), ) } else { (Vec2::new(1, 1), Vec2::new(1, 1)) } } /// Queue the clearing of the shadow targets ready for a new frame to be /// rendered. pub fn clear_shadows(&mut self) { span!(_guard, "clear_shadows", "Renderer::clear_shadows"); if !self.mode.shadow.is_map() { return; } if let Some(shadow_map) = self.shadow_map.as_mut() { // let point_encoder = &mut shadow_map.point_encoder; let point_encoder = &mut self.encoder; point_encoder.clear_depth(&shadow_map.point_depth_stencil_view, 1.0); // let directed_encoder = &mut shadow_map.directed_encoder; let directed_encoder = &mut self.encoder; directed_encoder.clear_depth(&shadow_map.directed_depth_stencil_view, 1.0); } } /// NOTE: Supported by Vulkan (by default), DirectX 10+ (it seems--it's hard /// to find proof of this, but Direct3D 10 apparently does it by /// default, and 11 definitely does, so I assume it's natively supported /// by DirectX itself), OpenGL 3.2+, and Metal (done by default). While /// there may be some GPUs that don't quite support it correctly, the /// impact is relatively small, so there is no reason not to enable it where /// available. #[allow(unsafe_code)] fn enable_seamless_cube_maps() { todo!() // unsafe { // // NOTE: Currently just fail silently rather than complain if the // computer is on // a version lower than 3.2, where // seamless cubemaps were introduced. if !device.get_info(). // is_version_supported(3, 2) { return; // } // // NOTE: Safe because GL_TEXTURE_CUBE_MAP_SEAMLESS is supported // by OpenGL 3.2+ // (see https://www.khronos.org/opengl/wiki/Cubemap_Texture#Seamless_cubemap); // // enabling seamless cube maps should always be safe regardless // of the state of // the OpenGL context, so no further // checks are needed. device.with_gl(|gl| { // gl.Enable(gfx_gl::TEXTURE_CUBE_MAP_SEAMLESS); // }); // } } /// Queue the clearing of the depth target ready for a new frame to be /// rendered. pub fn clear(&mut self) { span!(_guard, "clear", "Renderer::clear"); self.encoder.clear_depth(&self.tgt_depth_stencil_view, 1.0); // self.encoder.clear_stencil(&self.tgt_depth_stencil_view, 0); self.encoder.clear_depth(&self.win_depth_view, 1.0); } /// Set up shadow rendering. pub fn start_shadows(&mut self) { if !self.mode.shadow.is_map() { return; } if let Some(_shadow_map) = self.shadow_map.as_mut() { self.encoder.flush(&mut self.device); Self::set_depth_clamp(&mut self.device, true); } } /// Perform all queued draw calls for global.shadows. pub fn flush_shadows(&mut self) { if !self.mode.shadow.is_map() { return; } if let Some(_shadow_map) = self.shadow_map.as_mut() { let point_encoder = &mut self.encoder; // let point_encoder = &mut shadow_map.point_encoder; point_encoder.flush(&mut self.device); // let directed_encoder = &mut shadow_map.directed_encoder; // directed_encoder.flush(&mut self.device); // Reset depth clamping. Self::set_depth_clamp(&mut self.device, false); } } /// Perform all queued draw calls for this frame and clean up discarded /// items. pub fn flush(&mut self) { span!(_guard, "flush", "Renderer::flush"); self.encoder.flush(&mut self.device); self.device.cleanup(); // If the shaders files were changed attempt to recreate the shaders if self.shaders.reloaded() { self.recreate_pipelines(); } } /// Recreate the pipelines fn recreate_pipelines(&mut self) { match create_pipelines( &mut self.factory, &self.shaders.read(), &self.mode, self.shadow_map.is_some(), ) { Ok(( skybox_pipeline, figure_pipeline, terrain_pipeline, fluid_pipeline, sprite_pipeline, particle_pipeline, ui_pipeline, lod_terrain_pipeline, clouds_pipeline, postprocess_pipeline, player_shadow_pipeline, point_shadow_pipeline, terrain_directed_shadow_pipeline, figure_directed_shadow_pipeline, )) => { self.skybox_pipeline = skybox_pipeline; self.figure_pipeline = figure_pipeline; self.terrain_pipeline = terrain_pipeline; self.fluid_pipeline = fluid_pipeline; self.sprite_pipeline = sprite_pipeline; self.particle_pipeline = particle_pipeline; self.ui_pipeline = ui_pipeline; self.lod_terrain_pipeline = lod_terrain_pipeline; self.clouds_pipeline = clouds_pipeline; self.postprocess_pipeline = postprocess_pipeline; self.player_shadow_pipeline = player_shadow_pipeline; if let ( Some(point_pipeline), Some(terrain_directed_pipeline), Some(figure_directed_pipeline), Some(shadow_map), ) = ( point_shadow_pipeline, terrain_directed_shadow_pipeline, figure_directed_shadow_pipeline, self.shadow_map.as_mut(), ) { shadow_map.point_pipeline = point_pipeline; shadow_map.terrain_directed_pipeline = terrain_directed_pipeline; shadow_map.figure_directed_pipeline = figure_directed_pipeline; } }, Err(e) => error!(?e, "Could not recreate shaders from assets due to an error",), } } /// Create a new set of constants with the provided values. pub fn create_consts( &mut self, vals: &[T], ) -> Result, RenderError> { let mut consts = Consts::new(&mut self.factory, vals.len()); consts.update(&mut self.encoder, vals, 0)?; Ok(consts) } /// Update a set of constants with the provided values. pub fn update_consts( &mut self, consts: &mut Consts, vals: &[T], ) -> Result<(), RenderError> { consts.update(&mut self.encoder, vals, 0) } /// Create a new set of instances with the provided values. pub fn create_instances( &mut self, vals: &[T], ) -> Result, RenderError> { let mut instances = Instances::new(&mut self.factory, vals.len())?; instances.update(&mut self.encoder, vals)?; Ok(instances) } /// Create a new model from the provided mesh. pub fn create_model(&mut self, mesh: &Mesh) -> Result, RenderError> { Ok(Model::new(&mut self.factory, mesh)) } /// Create a new dynamic model with the specified size. pub fn create_dynamic_model( &mut self, size: usize, ) -> Result, RenderError> { Model::new(&self.device, size) } /// Update a dynamic model with a mesh and a offset. pub fn update_model( &mut self, model: &Model, mesh: &Mesh, offset: usize, ) -> Result<(), RenderError> { model.update(&mut self.encoder, mesh, offset) } /// Return the maximum supported texture size. pub fn max_texture_size(&self) -> u16 { Self::max_texture_size_raw(&self.factory) } /// Return the maximum supported texture size from the factory. fn max_texture_size_raw(device: &wgpu::Device) -> u16 { // This value is temporary as there are plans to include a way to get this in // wgpu this is just a sane standard for now 8192 } /// Create a new immutable texture from the provided image. pub fn create_texture_with_data_raw( &mut self, texture_info: wgpu::TextureDescriptor, sampler_info: wgpu::SamplerDescriptor, bytes_per_row: u32, size: [u16; 2], data: &[u8], ) -> Texture { let tex = Texture::new_raw(&self.device, texture_info, sampler_info); tex.update(&self.device, &self.queue, [0; 2], size, data, bytes_per_row); tex } /// Create a new raw texture. pub fn create_texture_raw( &mut self, texture_info: wgpu::TextureDescriptor, sampler_info: wgpu::SamplerDescriptor, ) -> Texture { Texture::new_raw(&self.device, texture_info, sampler_info) } /// Create a new texture from the provided image. pub fn create_texture( &mut self, image: &image::DynamicImage, filter_method: Option, addresse_mode: Option, ) -> Texture { Texture::new( &self.device, &self.queue, image, filter_method, addresse_mode, ) } /// Create a new dynamic texture (gfx::memory::Usage::Dynamic) with the /// specified dimensions. pub fn create_dynamic_texture(&mut self, dims: Vec2) -> Texture { Texture::new_dynamic(&mut self.factory, dims.x, dims.y) } /// Update a texture with the provided offset, size, and data. pub fn update_texture( &mut self, texture: &Texture, /* */ offset: [u16; 2], size: [u16; 2], // TODO // data: &[<::Surface as // gfx::format::SurfaceTyped>::DataType], ) -> Result<(), RenderError> // where // ::Surface: gfx::format::TextureSurface, // ::Channel: gfx::format::TextureChannel, // <::Surface as gfx::format::SurfaceTyped>::DataType: // Copy, { // texture.update(&mut self.encoder, offset, size, data) data: &[[u8; 4]], bytes_per_row: u32, ) { texture.update(&mut self.encoder, offset, size, data, bytes_per_row) } /// Creates a download buffer, downloads the win_color_view, and converts to /// a image::DynamicImage. #[allow(clippy::map_clone)] // TODO: Pending review in #587 pub fn create_screenshot(&mut self) -> Result { todo!() // let (width, height) = self.get_resolution().into_tuple(); // let download_buf = self // .device // .create_buffer(&wgpu::BufferDescriptor { // label: None, // size: width * height * 4, // usage : wgpu::BufferUsage::COPY_DST, // mapped_at_creation: true // }); // let encoder = // self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor // {label: None}); // encoder.copy_texture_to_buffer(&wgpu::TextureCopyViewBase { // origin: &self.wi // }, destination, copy_size) // self.encoder.copy_texture_to_buffer_raw( // self.win_color_view.raw().get_texture(), // None, // gfx::texture::RawImageInfo { // xoffset: 0, // yoffset: 0, // zoffset: 0, // width, // height, // depth: 0, // format: WinColorFmt::get_format(), // mipmap: 0, // }, // download.raw(), // 0, // )?; // self.flush(); // // Assumes that the format is Rgba8. // let raw_data = self // .factory // .read_mapping(&download)? // .chunks_exact(width as usize) // .rev() // .flatten() // .flatten() // .map(|&e| e) // .collect::>(); // Ok(image::DynamicImage::ImageRgba8( // // Should not fail if the dimensions are correct. // image::ImageBuffer::from_raw(width as u32, height as u32, // raw_data).unwrap(), )) } /// Queue the rendering of the provided skybox model in the upcoming frame. pub fn render_skybox( &mut self, model: &Model, global: &GlobalModel, locals: &Consts, lod: &lod_terrain::LodData, ) { self.encoder.draw( &gfx::Slice { start: model.vertex_range().start, end: model.vertex_range().end, base_vertex: 0, instances: None, buffer: gfx::IndexBuffer::Auto, }, &self.skybox_pipeline.pso, &skybox::pipe::Data { vbuf: model.vbuf.clone(), locals: locals.buf.clone(), globals: global.globals.buf.clone(), noise: (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), alt: (lod.alt.srv.clone(), lod.alt.sampler.clone()), horizon: (lod.horizon.srv.clone(), lod.horizon.sampler.clone()), tgt_color: self.tgt_color_view.clone(), tgt_depth_stencil: (self.tgt_depth_stencil_view.clone()/* , (1, 1) */), }, ); } /// Queue the rendering of the provided figure model in the upcoming frame. pub fn render_figure( &mut self, model: &figure::FigureModel, col_lights: &Texture, global: &GlobalModel, locals: &Consts, bones: &Consts, lod: &lod_terrain::LodData, ) { let (point_shadow_maps, directed_shadow_maps) = if let Some(shadow_map) = &mut self.shadow_map { ( ( shadow_map.point_res.clone(), shadow_map.point_sampler.clone(), ), ( shadow_map.directed_res.clone(), shadow_map.directed_sampler.clone(), ), ) } else { ( (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), ) }; let model = &model.opaque; self.encoder.draw( &gfx::Slice { start: model.vertex_range().start, end: model.vertex_range().end, base_vertex: 0, instances: None, buffer: gfx::IndexBuffer::Auto, }, &self.figure_pipeline.pso, &figure::pipe::Data { vbuf: model.vbuf.clone(), col_lights: (col_lights.srv.clone(), col_lights.sampler.clone()), locals: locals.buf.clone(), globals: global.globals.buf.clone(), bones: bones.buf.clone(), lights: global.lights.buf.clone(), shadows: global.shadows.buf.clone(), light_shadows: global.shadow_mats.buf.clone(), point_shadow_maps, directed_shadow_maps, noise: (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), alt: (lod.alt.srv.clone(), lod.alt.sampler.clone()), horizon: (lod.horizon.srv.clone(), lod.horizon.sampler.clone()), tgt_color: self.tgt_color_view.clone(), tgt_depth_stencil: (self.tgt_depth_stencil_view.clone()/* , (1, 1) */), }, ); } /// Queue the rendering of the player silhouette in the upcoming frame. pub fn render_player_shadow( &mut self, _model: &figure::FigureModel, _col_lights: &Texture, _global: &GlobalModel, _bones: &Consts, _lod: &lod_terrain::LodData, _locals: &Consts, ) { // FIXME: Consider reenabling at some point. /* let (point_shadow_maps, directed_shadow_maps) = if let Some(shadow_map) = &mut self.shadow_map { ( ( shadow_map.point_res.clone(), shadow_map.point_sampler.clone(), ), ( shadow_map.directed_res.clone(), shadow_map.directed_sampler.clone(), ), ) } else { ( (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), ) }; let model = &model.opaque; self.encoder.draw( &gfx::Slice { start: model.vertex_range().start, end: model.vertex_range().end, base_vertex: 0, instances: None, buffer: gfx::IndexBuffer::Auto, }, &self.player_shadow_pipeline.pso, &figure::pipe::Data { vbuf: model.vbuf.clone(), col_lights: (col_lights.srv.clone(), col_lights.sampler.clone()), locals: locals.buf.clone(), globals: global.globals.buf.clone(), bones: bones.buf.clone(), lights: global.lights.buf.clone(), shadows: global.shadows.buf.clone(), light_shadows: global.shadow_mats.buf.clone(), point_shadow_maps, directed_shadow_maps, noise: (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), alt: (lod.alt.srv.clone(), lod.alt.sampler.clone()), horizon: (lod.horizon.srv.clone(), lod.horizon.sampler.clone()), tgt_color: self.tgt_color_view.clone(), tgt_depth_stencil: (self.tgt_depth_stencil_view.clone()/* , (0, 0) */), }, ); */ } /// Queue the rendering of the player model in the upcoming frame. pub fn render_player( &mut self, model: &figure::FigureModel, col_lights: &Texture, global: &GlobalModel, locals: &Consts, bones: &Consts, lod: &lod_terrain::LodData, ) { let (point_shadow_maps, directed_shadow_maps) = if let Some(shadow_map) = &mut self.shadow_map { ( ( shadow_map.point_res.clone(), shadow_map.point_sampler.clone(), ), ( shadow_map.directed_res.clone(), shadow_map.directed_sampler.clone(), ), ) } else { ( (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), ) }; let model = &model.opaque; self.encoder.draw( &gfx::Slice { start: model.vertex_range().start, end: model.vertex_range().end, base_vertex: 0, instances: None, buffer: gfx::IndexBuffer::Auto, }, &self.figure_pipeline.pso, &figure::pipe::Data { vbuf: model.vbuf.clone(), col_lights: (col_lights.srv.clone(), col_lights.sampler.clone()), locals: locals.buf.clone(), globals: global.globals.buf.clone(), bones: bones.buf.clone(), lights: global.lights.buf.clone(), shadows: global.shadows.buf.clone(), light_shadows: global.shadow_mats.buf.clone(), point_shadow_maps, directed_shadow_maps, noise: (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), alt: (lod.alt.srv.clone(), lod.alt.sampler.clone()), horizon: (lod.horizon.srv.clone(), lod.horizon.sampler.clone()), tgt_color: self.tgt_color_view.clone(), tgt_depth_stencil: (self.tgt_depth_stencil_view.clone()/* , (1, 1) */), }, ); } /// Queue the rendering of the provided terrain chunk model in the upcoming /// frame. pub fn render_terrain_chunk( &mut self, model: &Model, col_lights: &Texture, global: &GlobalModel, locals: &Consts, lod: &lod_terrain::LodData, ) { let (point_shadow_maps, directed_shadow_maps) = if let Some(shadow_map) = &mut self.shadow_map { ( ( shadow_map.point_res.clone(), shadow_map.point_sampler.clone(), ), ( shadow_map.directed_res.clone(), shadow_map.directed_sampler.clone(), ), ) } else { ( (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), ) }; self.encoder.draw( &gfx::Slice { start: model.vertex_range().start, end: model.vertex_range().end, base_vertex: 0, instances: None, buffer: gfx::IndexBuffer::Auto, }, &self.terrain_pipeline.pso, &terrain::pipe::Data { vbuf: model.vbuf.clone(), // TODO: Consider splitting out texture atlas data into a separate vertex buffer, // since we don't need it for things like global.shadows. col_lights: (col_lights.srv.clone(), col_lights.sampler.clone()), locals: locals.buf.clone(), globals: global.globals.buf.clone(), lights: global.lights.buf.clone(), shadows: global.shadows.buf.clone(), light_shadows: global.shadow_mats.buf.clone(), point_shadow_maps, directed_shadow_maps, noise: (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), alt: (lod.alt.srv.clone(), lod.alt.sampler.clone()), horizon: (lod.horizon.srv.clone(), lod.horizon.sampler.clone()), tgt_color: self.tgt_color_view.clone(), tgt_depth_stencil: (self.tgt_depth_stencil_view.clone()/* , (1, 1) */), }, ); } /// Queue the rendering of a shadow map from a point light in the upcoming /// frame. pub fn render_shadow_point( &mut self, model: &Model, global: &GlobalModel, terrain_locals: &Consts, locals: &Consts, ) { if !self.mode.shadow.is_map() { return; } // NOTE: Don't render shadows if the shader is not supported. let shadow_map = if let Some(shadow_map) = &mut self.shadow_map { shadow_map } else { return; }; // let point_encoder = &mut shadow_map.point_encoder; let point_encoder = &mut self.encoder; point_encoder.draw( &gfx::Slice { start: model.vertex_range().start, end: model.vertex_range().end, base_vertex: 0, instances: None, buffer: gfx::IndexBuffer::Auto, }, &shadow_map.point_pipeline.pso, &shadow::pipe::Data { // Terrain vertex stuff vbuf: model.vbuf.clone(), locals: terrain_locals.buf.clone(), globals: global.globals.buf.clone(), // Shadow stuff light_shadows: locals.buf.clone(), tgt_depth_stencil: shadow_map.point_depth_stencil_view.clone(), }, ); } /// Queue the rendering of terrain shadow map from all directional lights in /// the upcoming frame. pub fn render_terrain_shadow_directed( &mut self, model: &Model, global: &GlobalModel, terrain_locals: &Consts, locals: &Consts, ) { if !self.mode.shadow.is_map() { return; } // NOTE: Don't render shadows if the shader is not supported. let shadow_map = if let Some(shadow_map) = &mut self.shadow_map { shadow_map } else { return; }; // let directed_encoder = &mut shadow_map.directed_encoder; let directed_encoder = &mut self.encoder; directed_encoder.draw( &gfx::Slice { start: model.vertex_range().start, end: model.vertex_range().end, base_vertex: 0, instances: None, buffer: gfx::IndexBuffer::Auto, }, &shadow_map.terrain_directed_pipeline.pso, &shadow::pipe::Data { // Terrain vertex stuff vbuf: model.vbuf.clone(), locals: terrain_locals.buf.clone(), globals: global.globals.buf.clone(), // Shadow stuff light_shadows: locals.buf.clone(), tgt_depth_stencil: shadow_map.directed_depth_stencil_view.clone(), }, ); } /// Queue the rendering of figure shadow map from all directional lights in /// the upcoming frame. pub fn render_figure_shadow_directed( &mut self, model: &figure::FigureModel, global: &GlobalModel, figure_locals: &Consts, bones: &Consts, locals: &Consts, ) { if !self.mode.shadow.is_map() { return; } // NOTE: Don't render shadows if the shader is not supported. let shadow_map = if let Some(shadow_map) = &mut self.shadow_map { shadow_map } else { return; }; let model = &model.opaque; // let directed_encoder = &mut shadow_map.directed_encoder; let directed_encoder = &mut self.encoder; directed_encoder.draw( &gfx::Slice { start: model.vertex_range().start, end: model.vertex_range().end, base_vertex: 0, instances: None, buffer: gfx::IndexBuffer::Auto, }, &shadow_map.figure_directed_pipeline.pso, &shadow::figure_pipe::Data { // Terrain vertex stuff vbuf: model.vbuf.clone(), locals: figure_locals.buf.clone(), bones: bones.buf.clone(), globals: global.globals.buf.clone(), // Shadow stuff light_shadows: locals.buf.clone(), tgt_depth_stencil: shadow_map.directed_depth_stencil_view.clone(), }, ); } /// Queue the rendering of the provided terrain chunk model in the upcoming /// frame. pub fn render_fluid_chunk( &mut self, model: &Model, global: &GlobalModel, locals: &Consts, lod: &lod_terrain::LodData, waves: &Texture, ) { let (point_shadow_maps, directed_shadow_maps) = if let Some(shadow_map) = &mut self.shadow_map { ( ( shadow_map.point_res.clone(), shadow_map.point_sampler.clone(), ), ( shadow_map.directed_res.clone(), shadow_map.directed_sampler.clone(), ), ) } else { ( (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), ) }; self.encoder.draw( &gfx::Slice { start: model.vertex_range().start, end: model.vertex_range().end, base_vertex: 0, instances: None, buffer: gfx::IndexBuffer::Auto, }, &self.fluid_pipeline.pso, &fluid::pipe::Data { vbuf: model.vbuf.clone(), locals: locals.buf.clone(), globals: global.globals.buf.clone(), lights: global.lights.buf.clone(), shadows: global.shadows.buf.clone(), light_shadows: global.shadow_mats.buf.clone(), point_shadow_maps, directed_shadow_maps, alt: (lod.alt.srv.clone(), lod.alt.sampler.clone()), horizon: (lod.horizon.srv.clone(), lod.horizon.sampler.clone()), noise: (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), waves: (waves.srv.clone(), waves.sampler.clone()), tgt_color: self.tgt_color_view.clone(), tgt_depth_stencil: (self.tgt_depth_stencil_view.clone()/* , (1, 1) */), }, ); } /// Queue the rendering of the provided terrain chunk model in the upcoming /// frame. pub fn render_sprites( &mut self, model: &Model, col_lights: &Texture, global: &GlobalModel, terrain_locals: &Consts, locals: &Consts, instances: &Instances, lod: &lod_terrain::LodData, ) { let (point_shadow_maps, directed_shadow_maps) = if let Some(shadow_map) = &mut self.shadow_map { ( ( shadow_map.point_res.clone(), shadow_map.point_sampler.clone(), ), ( shadow_map.directed_res.clone(), shadow_map.directed_sampler.clone(), ), ) } else { ( (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), ) }; self.encoder.draw( &gfx::Slice { start: model.vertex_range().start, end: model.vertex_range().end, base_vertex: 0, instances: Some((instances.count() as u32, 0)), buffer: gfx::IndexBuffer::Auto, }, &self.sprite_pipeline.pso, &sprite::pipe::Data { vbuf: model.vbuf.clone(), ibuf: instances.ibuf.clone(), col_lights: (col_lights.srv.clone(), col_lights.sampler.clone()), terrain_locals: terrain_locals.buf.clone(), // NOTE: It would be nice if this wasn't needed and we could use a constant buffer // offset into the sprite data. Hopefully, when we switch to wgpu we can do this, // as it offers the exact API we want (the equivalent can be done in OpenGL using // glBindBufferOffset). locals: locals.buf.clone(), globals: global.globals.buf.clone(), lights: global.lights.buf.clone(), shadows: global.shadows.buf.clone(), light_shadows: global.shadow_mats.buf.clone(), point_shadow_maps, directed_shadow_maps, noise: (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), alt: (lod.alt.srv.clone(), lod.alt.sampler.clone()), horizon: (lod.horizon.srv.clone(), lod.horizon.sampler.clone()), tgt_color: self.tgt_color_view.clone(), tgt_depth_stencil: (self.tgt_depth_stencil_view.clone()/* , (1, 1) */), }, ); } /// Queue the rendering of the provided LoD terrain model in the upcoming /// frame. pub fn render_lod_terrain( &mut self, model: &Model, global: &GlobalModel, locals: &Consts, lod: &lod_terrain::LodData, ) { self.encoder.draw( &gfx::Slice { start: model.vertex_range().start, end: model.vertex_range().end, base_vertex: 0, instances: None, buffer: gfx::IndexBuffer::Auto, }, &self.lod_terrain_pipeline.pso, &lod_terrain::pipe::Data { vbuf: model.vbuf.clone(), locals: locals.buf.clone(), globals: global.globals.buf.clone(), noise: (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), map: (lod.map.srv.clone(), lod.map.sampler.clone()), alt: (lod.alt.srv.clone(), lod.alt.sampler.clone()), horizon: (lod.horizon.srv.clone(), lod.horizon.sampler.clone()), tgt_color: self.tgt_color_view.clone(), tgt_depth_stencil: (self.tgt_depth_stencil_view.clone()/* , (1, 1) */), }, ); } /// Queue the rendering of the provided particle in the upcoming frame. pub fn render_particles( &mut self, model: &Model, global: &GlobalModel, instances: &Instances, lod: &lod_terrain::LodData, ) { let (point_shadow_maps, directed_shadow_maps) = if let Some(shadow_map) = &mut self.shadow_map { ( ( shadow_map.point_res.clone(), shadow_map.point_sampler.clone(), ), ( shadow_map.directed_res.clone(), shadow_map.directed_sampler.clone(), ), ) } else { ( (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), ) }; self.encoder.draw( &gfx::Slice { start: model.vertex_range().start, end: model.vertex_range().end, base_vertex: 0, instances: Some((instances.count() as u32, 0)), buffer: gfx::IndexBuffer::Auto, }, &self.particle_pipeline.pso, &particle::pipe::Data { vbuf: model.vbuf.clone(), ibuf: instances.ibuf.clone(), globals: global.globals.buf.clone(), lights: global.lights.buf.clone(), shadows: global.shadows.buf.clone(), light_shadows: global.shadow_mats.buf.clone(), point_shadow_maps, directed_shadow_maps, noise: (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), alt: (lod.alt.srv.clone(), lod.alt.sampler.clone()), horizon: (lod.horizon.srv.clone(), lod.horizon.sampler.clone()), tgt_color: self.tgt_color_view.clone(), tgt_depth_stencil: (self.tgt_depth_stencil_view.clone()/* , (1, 1) */), }, ); } /// Queue the rendering of the provided UI element in the upcoming frame. pub fn render_ui_element>( &mut self, model: Model, tex: &Texture, scissor: Aabr, globals: &Consts, locals: &Consts, ) where F::Surface: gfx::format::TextureSurface, F::Channel: gfx::format::TextureChannel, ::DataType: Copy, { let Aabr { min, max } = scissor; self.encoder.draw( &gfx::Slice { start: model.vertex_range.start, end: model.vertex_range.end, base_vertex: 0, instances: None, buffer: gfx::IndexBuffer::Auto, }, &self.ui_pipeline.pso, &ui::pipe::Data { vbuf: model.vbuf, scissor: gfx::Rect { x: min.x, y: min.y, w: max.x - min.x, h: max.y - min.y, }, tex: (tex.srv.clone(), tex.sampler.clone()), locals: locals.buf.clone(), globals: globals.buf.clone(), tgt_color: self.win_color_view.clone(), tgt_depth: self.win_depth_view.clone(), }, ); } pub fn render_clouds( &mut self, model: &Model, globals: &Consts, locals: &Consts, lod: &lod_terrain::LodData, ) { self.encoder.draw( &gfx::Slice { start: model.vertex_range().start, end: model.vertex_range().end, base_vertex: 0, instances: None, buffer: gfx::IndexBuffer::Auto, }, &self.clouds_pipeline.pso, &clouds::pipe::Data { vbuf: model.vbuf.clone(), locals: locals.buf.clone(), globals: globals.buf.clone(), map: (lod.map.srv.clone(), lod.map.sampler.clone()), alt: (lod.alt.srv.clone(), lod.alt.sampler.clone()), horizon: (lod.horizon.srv.clone(), lod.horizon.sampler.clone()), color_sampler: (self.tgt_color_res.clone(), self.sampler.clone()), depth_sampler: (self.tgt_depth_res.clone(), self.sampler.clone()), noise: (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), tgt_color: self.tgt_color_pp_view.clone(), }, ) } pub fn render_post_process( &mut self, model: &Model, globals: &Consts, locals: &Consts, lod: &lod_terrain::LodData, ) { self.encoder.draw( &gfx::Slice { start: model.vertex_range().start, end: model.vertex_range().end, base_vertex: 0, instances: None, buffer: gfx::IndexBuffer::Auto, }, &self.postprocess_pipeline.pso, &postprocess::pipe::Data { vbuf: model.vbuf.clone(), locals: locals.buf.clone(), globals: globals.buf.clone(), map: (lod.map.srv.clone(), lod.map.sampler.clone()), alt: (lod.alt.srv.clone(), lod.alt.sampler.clone()), horizon: (lod.horizon.srv.clone(), lod.horizon.sampler.clone()), color_sampler: (self.tgt_color_res_pp.clone(), self.sampler.clone()), depth_sampler: (self.tgt_depth_res.clone(), self.sampler.clone()), noise: (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), tgt_color: self.win_color_view.clone(), }, ) } } /// Creates all the pipelines used to render. #[allow(clippy::type_complexity)] // TODO: Pending review in #587 fn create_pipelines( factory: &mut gfx_backend::Factory, shaders: &Shaders, mode: &RenderMode, has_shadow_views: bool, ) -> Result< ( wgpu::RenderPipeline, wgpu::RenderPipeline, wgpu::RenderPipeline, wgpu::RenderPipeline, wgpu::RenderPipeline, wgpu::RenderPipeline, wgpu::RenderPipeline, wgpu::RenderPipeline, wgpu::RenderPipeline, wgpu::RenderPipeline, wgpu::RenderPipeline, Option, Option, Option, ), RenderError, > { // 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 {} "#, shaders.constants.read().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 = &match mode.aa { AaMode::None => shaders.anti_alias_none, AaMode::Fxaa => shaders.anti_alias_fxaa, AaMode::MsaaX4 => shaders.anti_alias_msaa_x4, AaMode::MsaaX8 => shaders.anti_alias_msaa_x8, AaMode::MsaaX16 => shaders.anti_alias_msaa_x16, }; let cloud = &match mode.cloud { CloudMode::None => shaders.cloud_none, _ => shaders.cloud_regular, }; let mut include_ctx = IncludeContext::new(); include_ctx.include("constants.glsl", &constants); include_ctx.include("globals.glsl", &shaders.globals.read().0); include_ctx.include("shadows.glsl", &shaders.shadows.read().0); include_ctx.include("sky.glsl", &shaders.sky.read().0); include_ctx.include("light.glsl", &shaders.light.read().0); include_ctx.include("srgb.glsl", &shaders.srgb.read().0); include_ctx.include("random.glsl", &shaders.random.read().0); include_ctx.include("lod.glsl", &shaders.lod.read().0); include_ctx.include("anti-aliasing.glsl", &anti_alias.read().0); include_ctx.include("cloud.glsl", &cloud.read().0); // Construct a pipeline for rendering skyboxes let skybox_pipeline = create_pipeline( factory, skybox::pipe::new(), &shaders.skybox_vert.read().0, &shaders.skybox_frag.read().0, &include_ctx, gfx::state::CullFace::Back, )?; // Construct a pipeline for rendering figures let figure_pipeline = create_pipeline( factory, figure::pipe::new(), &shaders.figure_vert.read().0, &shaders.figure_frag.read().0, &include_ctx, gfx::state::CullFace::Back, )?; // Construct a pipeline for rendering terrain let terrain_pipeline = create_pipeline( factory, terrain::pipe::new(), &shaders.terrain_vert.read().0, &shaders.terrain_frag.read().0, &include_ctx, gfx::state::CullFace::Back, )?; // Construct a pipeline for rendering fluids let fluid_pipeline = create_pipeline( factory, fluid::pipe::new(), &shaders.fluid_vert.read().0, &match mode.fluid { FluidMode::Cheap => shaders.fluid_frag_cheap, FluidMode::Shiny => shaders.fluid_frag_shiny, } .read() .0, &include_ctx, gfx::state::CullFace::Nothing, )?; // Construct a pipeline for rendering sprites let sprite_pipeline = create_pipeline( factory, sprite::pipe::new(), &shaders.sprite_vert.read().0, &shaders.sprite_frag.read().0, &include_ctx, gfx::state::CullFace::Back, )?; // Construct a pipeline for rendering particles let particle_pipeline = create_pipeline( factory, particle::pipe::new(), &shaders.particle_vert.read().0, &shaders.particle_frag.read().0, &include_ctx, gfx::state::CullFace::Back, )?; // Construct a pipeline for rendering UI elements let ui_pipeline = create_pipeline( factory, ui::pipe::new(), &shaders.ui_vert.read().0, &shaders.ui_frag.read().0, &include_ctx, gfx::state::CullFace::Back, )?; // Construct a pipeline for rendering terrain let lod_terrain_pipeline = create_pipeline( factory, lod_terrain::pipe::new(), &shaders.lod_terrain_vert.read().0, &shaders.lod_terrain_frag.read().0, &include_ctx, gfx::state::CullFace::Back, )?; // Construct a pipeline for rendering our clouds (a kind of post-processing) let clouds_pipeline = create_pipeline( factory, clouds::pipe::new(), &shaders.clouds_vert.read().0, &shaders.clouds_frag.read().0, &include_ctx, gfx::state::CullFace::Back, )?; // Construct a pipeline for rendering our post-processing let postprocess_pipeline = create_pipeline( factory, postprocess::pipe::new(), &shaders.postprocess_vert.read().0, &shaders.postprocess_frag.read().0, &include_ctx, gfx::state::CullFace::Back, )?; // Construct a pipeline for rendering the player silhouette let player_shadow_pipeline = create_pipeline( factory, figure::pipe::Init { tgt_depth_stencil: (gfx::preset::depth::PASS_TEST/*, Stencil::new( Comparison::Equal, 0xff, (StencilOp::Keep, StencilOp::Keep, StencilOp::Keep), ),*/), ..figure::pipe::new() }, &shaders.figure_vert.read().0, &shaders.player_shadow_frag.read().0, &include_ctx, gfx::state::CullFace::Back, )?; // Construct a pipeline for rendering point light terrain shadow maps. let point_shadow_pipeline = match create_shadow_pipeline( factory, shadow::pipe::new(), &shaders.terrain_point_shadow_vert.read().0, Some(&shaders.light_shadows_geom.read().0), &shaders.light_shadows_frag.read().0, &include_ctx, gfx::state::CullFace::Back, None, // Some(gfx::state::Offset(2, 0)) ) { Ok(pipe) => Some(pipe), Err(err) => { warn!("Could not load point shadow map pipeline: {:?}", err); None }, }; // Construct a pipeline for rendering directional light terrain shadow maps. let terrain_directed_shadow_pipeline = match create_shadow_pipeline( factory, shadow::pipe::new(), &shaders.terrain_directed_shadow_vert.read().0, None, &shaders.directed_shadow_frag.read().0, &include_ctx, gfx::state::CullFace::Back, None, // Some(gfx::state::Offset(2, 1)) ) { Ok(pipe) => Some(pipe), Err(err) => { warn!( "Could not load directed terrain shadow map pipeline: {:?}", err ); None }, }; // Construct a pipeline for rendering directional light figure shadow maps. let figure_directed_shadow_pipeline = match create_shadow_pipeline( factory, shadow::figure_pipe::new(), &shaders.figure_directed_shadow_vert.read().0, None, &shaders.directed_shadow_frag.read().0, &include_ctx, gfx::state::CullFace::Back, None, // Some(gfx::state::Offset(2, 1)) ) { Ok(pipe) => Some(pipe), Err(err) => { warn!( "Could not load directed figure shadow map pipeline: {:?}", err ); None }, }; Ok(( skybox_pipeline, figure_pipeline, terrain_pipeline, fluid_pipeline, sprite_pipeline, particle_pipeline, ui_pipeline, lod_terrain_pipeline, clouds_pipeline, postprocess_pipeline, player_shadow_pipeline, point_shadow_pipeline, terrain_directed_shadow_pipeline, figure_directed_shadow_pipeline, )) } /// Create a new pipeline from the provided vertex shader and fragment shader. fn create_pipeline( factory: &mut gfx_backend::Factory, pipe: P, vs: &str, fs: &str, ctx: &IncludeContext, cull_face: gfx::state::CullFace, ) -> Result, RenderError> { let vs = ctx.expand(vs)?; let fs = ctx.expand(fs)?; let program = factory.link_program(vs.as_bytes(), fs.as_bytes())?; let result = Ok(GfxPipeline { pso: factory.create_pipeline_from_program( &program, gfx::Primitive::TriangleList, gfx::state::Rasterizer { front_face: gfx::state::FrontFace::CounterClockwise, cull_face, method: gfx::state::RasterMethod::Fill, offset: None, samples: Some(gfx::state::MultiSample), }, pipe, )?, }); result } /// Create a new shadow map pipeline. fn create_shadow_pipeline( factory: &mut gfx_backend::Factory, pipe: P, vs: &str, gs: Option<&str>, fs: &str, ctx: &IncludeContext, cull_face: gfx::state::CullFace, offset: Option, ) -> Result, RenderError> { let vs = ctx.expand(vs)?; let gs = gs.map(|gs| ctx.expand(gs)).transpose()?; let fs = ctx.expand(fs)?; let shader_set = if let Some(gs) = gs { factory.create_shader_set_geometry(vs.as_bytes(), gs.as_bytes(), fs.as_bytes())? } else { factory.create_shader_set(vs.as_bytes(), fs.as_bytes())? }; Ok(GfxPipeline { pso: factory.create_pipeline_state( &shader_set, gfx::Primitive::TriangleList, gfx::state::Rasterizer { front_face: gfx::state::FrontFace::CounterClockwise, // Second-depth shadow mapping: should help reduce z-fighting provided all objects // are "watertight" (every triangle edge is shared with at most one other // triangle); this *should* be true for Veloren. cull_face: match cull_face { gfx::state::CullFace::Front => gfx::state::CullFace::Back, gfx::state::CullFace::Back => gfx::state::CullFace::Front, gfx::state::CullFace::Nothing => gfx::state::CullFace::Nothing, }, method: gfx::state::RasterMethod::Fill, offset, samples: None, }, pipe, )?, }) }