use super::{ consts::Consts, gfx_backend, instances::Instances, mesh::Mesh, model::{DynamicModel, Model}, pipelines::{ clouds, figure, fluid, lod_terrain, particle, postprocess, shadow, skybox, sprite, terrain, ui, GlobalModel, Globals, }, texture::Texture, AaMode, CloudMode, FilterMethod, FluidMode, LightingMode, Pipeline, RenderError, RenderMode, ShadowMapMode, ShadowMode, WrapMode, }; use common::{ assets::{self, AssetExt, AssetHandle}, span, }; use core::convert::TryFrom; use gfx::{ self, handle::Sampler, state::Comparison, traits::{Device, Factory, FactoryExt}, }; use glsl_include::Context as IncludeContext; use tracing::{error, 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 { const EXTENSION: &'static str = "glsl"; type Loader = assets::LoadFrom; } 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 { 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: ShadowDepthStencilView, directed_res: ShadowResourceView, directed_sampler: Sampler, point_depth_stencil_view: ShadowDepthStencilView, point_res: ShadowResourceView, point_sampler: Sampler, point_pipeline: GfxPipeline>, terrain_directed_pipeline: GfxPipeline>, figure_directed_pipeline: GfxPipeline>, } /// 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: gfx_backend::Device, encoder: gfx::Encoder, factory: gfx_backend::Factory, win_color_view: WinColorView, win_depth_view: WinDepthView, tgt_color_view: TgtColorView, tgt_depth_stencil_view: TgtDepthStencilView, tgt_color_view_pp: TgtColorView, tgt_color_res: TgtColorRes, tgt_depth_res: TgtDepthRes, tgt_color_res_pp: TgtColorRes, sampler: Sampler, shadow_map: Option, skybox_pipeline: GfxPipeline>, figure_pipeline: GfxPipeline>, terrain_pipeline: GfxPipeline>, fluid_pipeline: GfxPipeline>, sprite_pipeline: GfxPipeline>, particle_pipeline: GfxPipeline>, ui_pipeline: GfxPipeline>, lod_terrain_pipeline: GfxPipeline>, clouds_pipeline: GfxPipeline>, postprocess_pipeline: GfxPipeline>, player_shadow_pipeline: GfxPipeline>, shaders: AssetHandle, noise_tex: Texture<(gfx::format::R8, gfx::format::Unorm)>, mode: RenderMode, } impl Renderer { /// Create a new `Renderer` from a variety of backend-specific components /// and the window targets. pub fn new( mut device: gfx_backend::Device, mut factory: gfx_backend::Factory, win_color_view: WinColorView, win_depth_view: WinDepthView, 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 = win_color_view.get_dimensions(); let shadow_views = Self::create_shadow_views( &mut factory, (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( &mut factory, &shaders.read(), &mode, shadow_views.is_some(), )?; let ( tgt_color_view, tgt_depth_stencil_view, tgt_color_view_pp, tgt_color_res, tgt_depth_res, tgt_color_res_pp, ) = Self::create_rt_views(&mut factory, (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 { // point_encoder: factory.create_command_buffer().into(), // directed_encoder: factory.create_command_buffer().into(), point_depth_stencil_view, point_res, point_sampler, directed_depth_stencil_view, directed_res, directed_sampler, point_pipeline, terrain_directed_pipeline, figure_directed_pipeline, }) } else { None }; let sampler = factory.create_sampler(gfx::texture::SamplerInfo::new( gfx::texture::FilterMethod::Bilinear, gfx::texture::WrapMode::Clamp, )); let noise_tex = Texture::new( &mut factory, &assets::Image::load_expect("voxygen.texture.noise").read().0, Some(gfx::texture::FilterMethod::Trilinear), Some(gfx::texture::WrapMode::Tile), None, )?; Ok(Self { device, encoder: factory.create_command_buffer().into(), factory, win_color_view, win_depth_view, tgt_color_view, tgt_depth_stencil_view, tgt_color_view_pp, tgt_color_res, tgt_depth_res, tgt_color_res_pp, 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) -> (&TgtColorView, &TgtDepthStencilView) { (&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) -> (&WinColorView, &WinDepthView) { (&self.win_color_view, &self.win_depth_view) } /// Get mutable references to the internal render target views that get /// rendered to before post-processing. #[allow(dead_code)] pub fn tgt_views_mut(&mut self) -> (&mut TgtColorView, &mut TgtDepthStencilView) { (&mut self.tgt_color_view, &mut self.tgt_depth_stencil_view) } /// Get mutable references to the internal render target views that get /// displayed directly by the window. #[allow(dead_code)] pub fn win_views_mut(&mut self) -> (&mut WinColorView, &mut WinDepthView) { (&mut self.win_color_view, &mut 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_view_pp, 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_view_pp = tgt_color_view_pp; 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( factory: &mut gfx_device_gl::Factory, size: (u16, u16), mode: &RenderMode, ) -> Result< ( TgtColorView, TgtDepthStencilView, TgtColorView, TgtColorRes, TgtDepthRes, TgtColorRes, ), RenderError, > { let upscaled = Vec2::from(size) .map(|e: u16| (e as f32 * mode.upscale_mode.factor) as u16) .into_tuple(); let kind = match mode.aa { AaMode::None | AaMode::Fxaa => { gfx::texture::Kind::D2(upscaled.0, upscaled.1, gfx::texture::AaMode::Single) }, // TODO: Ensure sampling in the shader is exactly between the 4 texels AaMode::MsaaX4 => { gfx::texture::Kind::D2(upscaled.0, upscaled.1, gfx::texture::AaMode::Multi(4)) }, AaMode::MsaaX8 => { gfx::texture::Kind::D2(upscaled.0, upscaled.1, gfx::texture::AaMode::Multi(8)) }, AaMode::MsaaX16 => { gfx::texture::Kind::D2(upscaled.0, upscaled.1, gfx::texture::AaMode::Multi(16)) }, }; let levels = 1; let color_cty = <::Channel as gfx::format::ChannelTyped >::get_channel_type(); let mut color_tex = || { factory.create_texture( kind, levels, gfx::memory::Bind::SHADER_RESOURCE | gfx::memory::Bind::RENDER_TARGET, gfx::memory::Usage::Data, Some(color_cty), ) }; let tgt_color_tex = color_tex()?; let tgt_color_tex_pp = color_tex()?; let mut color_res = |tex| { factory.view_texture_as_shader_resource::( tex, (0, levels - 1), gfx::format::Swizzle::new(), ) }; let tgt_color_res = color_res(&tgt_color_tex)?; let tgt_color_res_pp = color_res(&tgt_color_tex_pp)?; let tgt_color_view = factory.view_texture_as_render_target(&tgt_color_tex, 0, None)?; let tgt_color_view_pp = factory.view_texture_as_render_target(&tgt_color_tex_pp, 0, None)?; let depth_stencil_cty = <::Channel as gfx::format::ChannelTyped>::get_channel_type(); let tgt_depth_stencil_tex = factory.create_texture( kind, levels, gfx::memory::Bind::SHADER_RESOURCE | gfx::memory::Bind::DEPTH_STENCIL, gfx::memory::Usage::Data, Some(depth_stencil_cty), )?; let tgt_depth_res = factory.view_texture_as_shader_resource::( &tgt_depth_stencil_tex, (0, levels - 1), gfx::format::Swizzle::new(), )?; let tgt_depth_stencil_view = factory.view_texture_as_depth_stencil_trivial(&tgt_depth_stencil_tex)?; Ok(( tgt_color_view, tgt_depth_stencil_view, tgt_color_view_pp, tgt_color_res, tgt_depth_res, tgt_color_res_pp, )) } /// 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( factory: &mut gfx_device_gl::Factory, size: (u16, u16), mode: &ShadowMapMode, ) -> Result< ( ShadowDepthStencilView, ShadowResourceView, Sampler, ShadowDepthStencilView, ShadowResourceView, 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(factory); // 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 depth_stencil_cty = <::Channel as gfx::format::ChannelTyped>::get_channel_type(); let point_shadow_tex = factory .create_texture( gfx::texture::Kind::Cube(diag_two_size / 4), levels as gfx::texture::Level, gfx::memory::Bind::SHADER_RESOURCE | gfx::memory::Bind::DEPTH_STENCIL, gfx::memory::Usage::Data, Some(depth_stencil_cty), ) .map_err(|err| RenderError::CombinedError(gfx::CombinedError::Texture(err)))?; let point_tgt_shadow_view = factory .view_texture_as_depth_stencil::( &point_shadow_tex, 0, None, gfx::texture::DepthStencilFlags::empty(), )?; let point_tgt_shadow_res = factory .view_texture_as_shader_resource::( &point_shadow_tex, (0, levels - 1), gfx::format::Swizzle::new(), )?; let directed_shadow_tex = factory .create_texture( gfx::texture::Kind::D2(diag_two_size, diag_two_size, gfx::texture::AaMode::Single), levels as gfx::texture::Level, gfx::memory::Bind::SHADER_RESOURCE | gfx::memory::Bind::DEPTH_STENCIL, gfx::memory::Usage::Data, Some(depth_stencil_cty), ) .map_err(|err| RenderError::CombinedError(gfx::CombinedError::Texture(err)))?; let directed_tgt_shadow_view = factory .view_texture_as_depth_stencil::( &directed_shadow_tex, 0, None, gfx::texture::DepthStencilFlags::empty(), )?; let directed_tgt_shadow_res = factory .view_texture_as_shader_resource::( &directed_shadow_tex, (0, levels - 1), gfx::format::Swizzle::new(), )?; let mut sampler_info = gfx::texture::SamplerInfo::new( gfx::texture::FilterMethod::Bilinear, // Lights should always be assumed to flood areas we can't see. gfx::texture::WrapMode::Border, ); sampler_info.comparison = Some(Comparison::LessEqual); sampler_info.border = [1.0; 4].into(); let point_shadow_tex_sampler = factory.create_sampler(sampler_info); let directed_shadow_tex_sampler = factory.create_sampler(sampler_info); Ok(( point_tgt_shadow_view, point_tgt_shadow_res, point_shadow_tex_sampler, directed_tgt_shadow_view, directed_tgt_shadow_res, 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(device: &mut gfx_backend::Device) { 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); }); } } /// NOTE: Supported by all but a handful of mobile GPUs /// (see https://github.com/gpuweb/gpuweb/issues/480) /// so wgpu should support it too. #[allow(unsafe_code)] fn set_depth_clamp(device: &mut gfx_backend::Device, depth_clamp: bool) { unsafe { // NOTE: Currently just fail silently rather than complain if the computer is on // a version lower than 3.3, though we probably will complain // elsewhere regardless, since shadow mapping is an optional feature // and having depth clamping disabled won't cause undefined // behavior, just incorrect shadowing from objects behind the viewer. if !device.get_info().is_version_supported(3, 3) { return; } // NOTE: Safe because glDepthClamp is (I believe) supported by // OpenGL 3.3, so we shouldn't have to check for other OpenGL versions which // may use different extensions. Also, enabling depth clamping should // essentially always be safe regardless of the state of the OpenGL // context, so no further checks are needed. device.with_gl(|gl| { if depth_clamp { gl.Enable(gfx_gl::DEPTH_CLAMP); } else { gl.Disable(gfx_gl::DEPTH_CLAMP); } }); } } /// 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> { DynamicModel::new(&mut self.factory, size) } /// Update a dynamic model with a mesh and a offset. pub fn update_model( &mut self, model: &DynamicModel

, 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(factory: &gfx_backend::Factory) -> u16 { /// NOTE: OpenGL requirement. const MAX_TEXTURE_SIZE_MIN: u16 = 1024; #[cfg(target_os = "macos")] /// NOTE: Because Macs lie about their max supported texture size. const MAX_TEXTURE_SIZE_MAX: u16 = 8192; #[cfg(not(target_os = "macos"))] /// NOTE: Apparently Macs aren't the only machines that lie. /// /// TODO: Find a way to let graphics cards that don't lie do better. const MAX_TEXTURE_SIZE_MAX: u16 = 8192; // NOTE: Many APIs for textures require coordinates to fit in u16, which is why // we perform this conversion. u16::try_from(factory.get_capabilities().max_texture_size) .unwrap_or(MAX_TEXTURE_SIZE_MIN) .min(MAX_TEXTURE_SIZE_MAX) } /// Create a new immutable texture from the provided image. pub fn create_texture_immutable_raw( &mut self, kind: gfx::texture::Kind, mipmap: gfx::texture::Mipmap, data: &[&[::DataType]], sampler_info: gfx::texture::SamplerInfo, ) -> Result, RenderError> where F::Surface: gfx::format::TextureSurface, F::Channel: gfx::format::TextureChannel, ::DataType: Copy, { Texture::new_immutable_raw(&mut self.factory, kind, mipmap, data, sampler_info) } /// Create a new raw texture. pub fn create_texture_raw( &mut self, kind: gfx::texture::Kind, max_levels: u8, bind: gfx::memory::Bind, usage: gfx::memory::Usage, levels: (u8, u8), swizzle: gfx::format::Swizzle, sampler_info: gfx::texture::SamplerInfo, ) -> Result, RenderError> where F::Surface: gfx::format::TextureSurface, F::Channel: gfx::format::TextureChannel, ::DataType: Copy, { Texture::new_raw( &mut self.device, &mut self.factory, kind, max_levels, bind, usage, levels, swizzle, sampler_info, ) } /// Create a new texture from the provided image. pub fn create_texture( &mut self, image: &image::DynamicImage, filter_method: Option, wrap_mode: Option, border: Option, ) -> Result, RenderError> where F::Surface: gfx::format::TextureSurface, F::Channel: gfx::format::TextureChannel, ::DataType: Copy, { Texture::new(&mut self.factory, image, filter_method, wrap_mode, border) } /// Create a new dynamic texture (gfx::memory::Usage::Dynamic) with the /// specified dimensions. pub fn create_dynamic_texture(&mut self, dims: Vec2) -> Result { 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], 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) } /// 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 { let (width, height) = self.get_resolution().into_tuple(); use gfx::{ format::{Formatted, SurfaceTyped}, memory::Typed, }; type WinSurfaceData = <::Surface as SurfaceTyped>::DataType; let download = self .factory .create_download_buffer::(width as usize * height as usize)?; 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_view_pp.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(), }, ) } } struct GfxPipeline { pso: gfx::pso::PipelineState, } /// 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< ( GfxPipeline>, GfxPipeline>, GfxPipeline>, GfxPipeline>, GfxPipeline>, GfxPipeline>, GfxPipeline>, GfxPipeline>, GfxPipeline>, GfxPipeline>, GfxPipeline>, 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, )?, }) }