mod binding; pub(super) mod drawer; // Consts and bind groups for post-process and clouds mod locals; mod pipeline_creation; mod screenshot; mod shaders; mod shadow_map; use locals::Locals; use pipeline_creation::{ IngameAndShadowPipelines, InterfacePipelines, PipelineCreation, Pipelines, ShadowPipelines, }; use shaders::Shaders; use shadow_map::{ShadowMap, ShadowMapRenderer}; use super::{ buffer::Buffer, consts::Consts, instances::Instances, mesh::Mesh, model::{DynamicModel, Model}, pipelines::{ blit, bloom, clouds, debug, figure, postprocess, shadow, sprite, terrain, ui, GlobalsBindGroup, GlobalsLayouts, ShadowTexturesBindGroup, }, texture::Texture, AaMode, AddressMode, FilterMode, OtherModes, PipelineModes, RenderError, RenderMode, ShadowMapMode, ShadowMode, Vertex, }; use common::assets::{self, AssetExt, AssetHandle, ReloadWatcher}; use common_base::span; use core::convert::TryFrom; #[cfg(feature = "egui-ui")] use egui_wgpu_backend::wgpu::TextureFormat; use std::sync::Arc; use tracing::{error, info, warn}; use vek::*; // TODO: yeet this somewhere else /// A type representing data that can be converted to an immutable texture map /// of ColLight data (used for texture atlases created during greedy meshing). // TODO: revert to u16 pub type ColLightInfo = (Vec<[u8; 4]>, Vec2); const QUAD_INDEX_BUFFER_U16_START_VERT_LEN: u16 = 3000; const QUAD_INDEX_BUFFER_U32_START_VERT_LEN: u32 = 3000; /// A type that stores all the layouts associated with this renderer that never /// change when the RenderMode is modified. struct ImmutableLayouts { global: GlobalsLayouts, debug: debug::DebugLayout, figure: figure::FigureLayout, shadow: shadow::ShadowLayout, sprite: sprite::SpriteLayout, terrain: terrain::TerrainLayout, clouds: clouds::CloudsLayout, bloom: bloom::BloomLayout, ui: ui::UiLayout, blit: blit::BlitLayout, } /// A type that stores all the layouts associated with this renderer. struct Layouts { immutable: Arc, postprocess: Arc, } impl core::ops::Deref for Layouts { type Target = ImmutableLayouts; fn deref(&self) -> &Self::Target { &self.immutable } } /// Render target views struct Views { // NOTE: unused for now, maybe... we will want it for something _win_depth: wgpu::TextureView, tgt_color: wgpu::TextureView, tgt_depth: wgpu::TextureView, bloom_tgts: Option<[wgpu::TextureView; bloom::NUM_SIZES]>, // TODO: rename tgt_color_pp: wgpu::TextureView, } /// Shadow rendering textures, layouts, pipelines, and bind groups struct Shadow { map: ShadowMap, bind: ShadowTexturesBindGroup, } /// Represent two states of the renderer: /// 1. Only interface pipelines created /// 2. All of the pipelines have been created #[allow(clippy::large_enum_variant)] // They are both pretty large enum State { // NOTE: this is used as a transient placeholder for moving things out of State temporarily Nothing, Interface { pipelines: InterfacePipelines, shadow_views: Option<(Texture, Texture)>, // In progress creation of the remaining pipelines in the background creating: PipelineCreation, }, Complete { pipelines: Pipelines, shadow: Shadow, recreating: Option<( PipelineModes, PipelineCreation< Result< ( Pipelines, ShadowPipelines, Arc, ), RenderError, >, >, )>, }, } /// 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: Arc, queue: wgpu::Queue, surface: wgpu::Surface, swap_chain: wgpu::SwapChain, sc_desc: wgpu::SwapChainDescriptor, sampler: wgpu::Sampler, depth_sampler: wgpu::Sampler, state: State, // Some if there is a pending need to recreate the pipelines (e.g. RenderMode change or shader // hotloading) recreation_pending: Option, layouts: Layouts, // Note: we keep these here since their bind groups need to be updated if we resize the // color/depth textures locals: Locals, views: Views, noise_tex: Texture, quad_index_buffer_u16: Buffer, quad_index_buffer_u32: Buffer, shaders: AssetHandle, shaders_watcher: ReloadWatcher, pipeline_modes: PipelineModes, other_modes: OtherModes, resolution: Vec2, // If this is Some then a screenshot will be taken and passed to the handler here take_screenshot: Option, profiler: wgpu_profiler::GpuProfiler, profile_times: Vec, profiler_features_enabled: bool, #[cfg(feature = "egui-ui")] egui_renderpass: egui_wgpu_backend::RenderPass, // This checks is added because windows resizes the window to 0,0 when // minimizing and this causes a bunch of validation errors is_minimized: bool, // To remember the backend info after initialization for debug purposes graphics_backend: String, } impl Renderer { /// Create a new `Renderer` from a variety of backend-specific components /// and the window targets. pub fn new( window: &winit::window::Window, mode: RenderMode, runtime: &tokio::runtime::Runtime, ) -> Result { let (pipeline_modes, mut other_modes) = mode.split(); // Enable seamless cubemaps globally, where available--they are essentially a // strict improvement on regular cube maps. // // 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); // TODO: fix panic on wayland with opengl? // TODO: fix backend defaulting to opengl on wayland. let backend_bit = std::env::var("WGPU_BACKEND") .ok() .and_then(|backend| match backend.to_lowercase().as_str() { "vulkan" => Some(wgpu::BackendBit::VULKAN), "metal" => Some(wgpu::BackendBit::METAL), "dx12" => Some(wgpu::BackendBit::DX12), "primary" => Some(wgpu::BackendBit::PRIMARY), "opengl" | "gl" => Some(wgpu::BackendBit::GL), "dx11" => Some(wgpu::BackendBit::DX11), "secondary" => Some(wgpu::BackendBit::SECONDARY), "all" => Some(wgpu::BackendBit::all()), _ => None, }) .unwrap_or( (wgpu::BackendBit::PRIMARY | wgpu::BackendBit::SECONDARY) & !wgpu::BackendBit::GL, ); let instance = wgpu::Instance::new(backend_bit); let dims = window.inner_size(); // 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 adapters = instance .enumerate_adapters(backend_bit) .enumerate() .collect::>(); for (i, adapter) in adapters.iter() { let info = adapter.get_info(); info!( ?info.name, ?info.vendor, ?info.backend, ?info.device, ?info.device_type, "graphics device #{}", i, ); } let adapter = match std::env::var("WGPU_ADAPTER").ok() { Some(filter) if !filter.is_empty() => adapters.into_iter().find_map(|(i, adapter)| { let info = adapter.get_info(); let full_name = format!("#{} {} {:?}", i, info.name, info.device_type,); full_name.contains(&filter).then(|| adapter) }), Some(_) | None => { runtime.block_on(instance.request_adapter(&wgpu::RequestAdapterOptionsBase { power_preference: wgpu::PowerPreference::HighPerformance, compatible_surface: Some(&surface), })) }, } .ok_or(RenderError::CouldNotFindAdapter)?; let info = adapter.get_info(); info!( ?info.name, ?info.vendor, ?info.backend, ?info.device, ?info.device_type, "selected graphics device" ); let graphics_backend = format!("{:?}", &info.backend); let limits = wgpu::Limits { max_push_constant_size: 64, ..Default::default() }; let trace_env = std::env::var_os("WGPU_TRACE_DIR"); let trace_path = trace_env.as_ref().map(|v| { let path = std::path::Path::new(v); // We don't want to continue if we can't actually collect the api trace assert!( path.exists(), "WGPU_TRACE_DIR is set to the path \"{}\" which doesn't exist", path.display() ); assert!( path.is_dir(), "WGPU_TRACE_DIR is set to the path \"{}\" which is not a directory", path.display() ); assert!( path.read_dir() .expect("Could not read the directory that is specified by WGPU_TRACE_DIR") .next() .is_none(), "WGPU_TRACE_DIR is set to the path \"{}\" which already contains other files", path.display() ); path }); let (device, queue) = runtime.block_on(adapter.request_device( &wgpu::DeviceDescriptor { // TODO label: None, features: wgpu::Features::DEPTH_CLAMPING | wgpu::Features::ADDRESS_MODE_CLAMP_TO_BORDER | wgpu::Features::PUSH_CONSTANTS | (adapter.features() & wgpu_profiler::GpuProfiler::REQUIRED_WGPU_FEATURES), limits, }, trace_path, ))?; // Set error handler for wgpu errors // This is better for use than their default because it includes the error in // the panic message device.on_uncaptured_error(move |error| { error!("{}", &error); panic!( "wgpu error (handling all wgpu errors as fatal):\n{:?}\n{:?}", &error, &info, ); }); let profiler_features_enabled = device .features() .contains(wgpu_profiler::GpuProfiler::REQUIRED_WGPU_FEATURES); if !profiler_features_enabled { info!( "The features for GPU profiling (timestamp queries) are not available on this \ adapter" ); } let format = adapter .get_swap_chain_preferred_format(&surface) .expect("No supported swap chain format found"); info!("Using {:?} as the swapchain format", format); let sc_desc = wgpu::SwapChainDescriptor { usage: wgpu::TextureUsage::RENDER_ATTACHMENT, format, width: dims.width, height: dims.height, present_mode: other_modes.present_mode.into(), }; let swap_chain = device.create_swap_chain(&surface, &sc_desc); let shadow_views = ShadowMap::create_shadow_views( &device, (dims.width, dims.height), &ShadowMapMode::try_from(pipeline_modes.shadow).unwrap_or_default(), ) .map_err(|err| { warn!("Could not create shadow map views: {:?}", err); }) .ok(); let shaders = Shaders::load_expect(""); let shaders_watcher = shaders.reload_watcher(); let layouts = { let global = GlobalsLayouts::new(&device); let debug = debug::DebugLayout::new(&device); let figure = figure::FigureLayout::new(&device); let shadow = shadow::ShadowLayout::new(&device); let sprite = sprite::SpriteLayout::new(&device); let terrain = terrain::TerrainLayout::new(&device); let clouds = clouds::CloudsLayout::new(&device); let bloom = bloom::BloomLayout::new(&device); let postprocess = Arc::new(postprocess::PostProcessLayout::new( &device, &pipeline_modes, )); let ui = ui::UiLayout::new(&device); let blit = blit::BlitLayout::new(&device); let immutable = Arc::new(ImmutableLayouts { global, debug, figure, shadow, sprite, terrain, clouds, bloom, ui, blit, }); Layouts { immutable, postprocess, } }; // Arcify the device let device = Arc::new(device); let (interface_pipelines, creating) = pipeline_creation::initial_create_pipelines( Arc::clone(&device), Layouts { immutable: Arc::clone(&layouts.immutable), postprocess: Arc::clone(&layouts.postprocess), }, shaders.cloned(), pipeline_modes.clone(), sc_desc.clone(), // Note: cheap clone shadow_views.is_some(), )?; let state = State::Interface { pipelines: interface_pipelines, shadow_views, creating, }; let (views, bloom_sizes) = Self::create_rt_views( &device, (dims.width, dims.height), &pipeline_modes, &other_modes, ); let create_sampler = |filter| { device.create_sampler(&wgpu::SamplerDescriptor { label: None, address_mode_u: wgpu::AddressMode::ClampToEdge, address_mode_v: wgpu::AddressMode::ClampToEdge, address_mode_w: wgpu::AddressMode::ClampToEdge, mag_filter: filter, min_filter: filter, mipmap_filter: wgpu::FilterMode::Nearest, compare: None, ..Default::default() }) }; let sampler = create_sampler(wgpu::FilterMode::Linear); let depth_sampler = create_sampler(wgpu::FilterMode::Nearest); let noise_tex = Texture::new( &device, &queue, &assets::Image::load_expect("voxygen.texture.noise").read().0, Some(wgpu::FilterMode::Linear), Some(wgpu::AddressMode::Repeat), )?; let clouds_locals = Self::create_consts_inner(&device, &queue, &[clouds::Locals::default()]); let postprocess_locals = Self::create_consts_inner(&device, &queue, &[postprocess::Locals::default()]); let locals = Locals::new( &device, &layouts, clouds_locals, postprocess_locals, &views.tgt_color, &views.tgt_depth, views.bloom_tgts.as_ref().map(|tgts| locals::BloomParams { locals: bloom_sizes.map(|size| { Self::create_consts_inner(&device, &queue, &[bloom::Locals::new(size)]) }), src_views: [&views.tgt_color_pp, &tgts[1], &tgts[2], &tgts[3], &tgts[4]], final_tgt_view: &tgts[0], }), &views.tgt_color_pp, &sampler, &depth_sampler, ); let quad_index_buffer_u16 = create_quad_index_buffer_u16(&device, QUAD_INDEX_BUFFER_U16_START_VERT_LEN as usize); let quad_index_buffer_u32 = create_quad_index_buffer_u32(&device, QUAD_INDEX_BUFFER_U32_START_VERT_LEN as usize); let mut profiler = wgpu_profiler::GpuProfiler::new(4, queue.get_timestamp_period()); other_modes.profiler_enabled &= profiler_features_enabled; profiler.enable_timer = other_modes.profiler_enabled; profiler.enable_debug_marker = other_modes.profiler_enabled; #[cfg(feature = "egui-ui")] let egui_renderpass = egui_wgpu_backend::RenderPass::new(&*device, TextureFormat::Bgra8UnormSrgb, 1); Ok(Self { device, queue, surface, swap_chain, sc_desc, state, recreation_pending: None, layouts, locals, views, sampler, depth_sampler, noise_tex, quad_index_buffer_u16, quad_index_buffer_u32, shaders, shaders_watcher, pipeline_modes, other_modes, resolution: Vec2::new(dims.width, dims.height), take_screenshot: None, profiler, profile_times: Vec::new(), profiler_features_enabled, #[cfg(feature = "egui-ui")] egui_renderpass, is_minimized: false, graphics_backend, }) } /// Get the graphics backend being used pub fn graphics_backend(&self) -> &str { &self.graphics_backend } /// Check the status of the intial pipeline creation /// Returns `None` if complete /// Returns `Some((total, complete))` if in progress pub fn pipeline_creation_status(&self) -> Option<(usize, usize)> { if let State::Interface { creating, .. } = &self.state { Some(creating.status()) } else { None } } /// Check the status the pipeline recreation /// Returns `None` if pipelines are currently not being recreated /// Returns `Some((total, complete))` if in progress pub fn pipeline_recreation_status(&self) -> Option<(usize, usize)> { if let State::Complete { recreating, .. } = &self.state { recreating.as_ref().map(|(_, c)| c.status()) } else { None } } /// Change the render mode. pub fn set_render_mode(&mut self, mode: RenderMode) -> Result<(), RenderError> { let (pipeline_modes, other_modes) = mode.split(); if self.other_modes != other_modes { self.other_modes = other_modes; // Update present mode in swap chain descriptor self.sc_desc.present_mode = self.other_modes.present_mode.into(); // Only enable profiling if the wgpu features are enabled self.other_modes.profiler_enabled &= self.profiler_features_enabled; // Enable/disable profiler if !self.other_modes.profiler_enabled { // Clear the times if disabled core::mem::take(&mut self.profile_times); } self.profiler.enable_timer = self.other_modes.profiler_enabled; self.profiler.enable_debug_marker = self.other_modes.profiler_enabled; // Recreate render target self.on_resize(self.resolution); } // We can't cancel the pending recreation even if the new settings are equal // to the current ones becuase the recreation could be triggered by something // else like shader hotloading if self.pipeline_modes != pipeline_modes || self .recreation_pending .as_ref() .map_or(false, |modes| modes != &pipeline_modes) { // Recreate pipelines with new modes self.recreate_pipelines(pipeline_modes); } Ok(()) } /// Get the pipelines mode. pub fn pipeline_modes(&self) -> &PipelineModes { &self.pipeline_modes } /// Get the current profiling times /// Nested timings immediately follow their parent /// Returns Vec<(how nested this timing is, label, length in seconds)> pub fn timings(&self) -> Vec<(u8, &str, f64)> { use wgpu_profiler::GpuTimerScopeResult; fn recursive_collect<'a>( vec: &mut Vec<(u8, &'a str, f64)>, result: &'a GpuTimerScopeResult, nest_level: u8, ) { vec.push(( nest_level, &result.label, result.time.end - result.time.start, )); result .nested_scopes .iter() .for_each(|child| recursive_collect(vec, child, nest_level + 1)); } let mut vec = Vec::new(); self.profile_times .iter() .for_each(|child| recursive_collect(&mut vec, child, 0)); vec } /// Resize internal render targets to match window render target dimensions. pub fn on_resize(&mut self, dims: Vec2) { // Avoid panics when creating texture with w,h of 0,0. if dims.x != 0 && dims.y != 0 { self.is_minimized = false; // Resize swap chain self.resolution = dims; self.sc_desc.width = dims.x; self.sc_desc.height = dims.y; self.swap_chain = self.device.create_swap_chain(&self.surface, &self.sc_desc); // Resize other render targets let (views, bloom_sizes) = Self::create_rt_views( &self.device, (dims.x, dims.y), &self.pipeline_modes, &self.other_modes, ); self.views = views; // appease borrow check (TODO: remove after Rust 2021) let device = &self.device; let queue = &self.queue; let views = &self.views; let bloom_params = self .views .bloom_tgts .as_ref() .map(|tgts| locals::BloomParams { locals: bloom_sizes.map(|size| { Self::create_consts_inner(device, queue, &[bloom::Locals::new(size)]) }), src_views: [&views.tgt_color_pp, &tgts[1], &tgts[2], &tgts[3], &tgts[4]], final_tgt_view: &tgts[0], }); self.locals.rebind( &self.device, &self.layouts, &self.views.tgt_color, &self.views.tgt_depth, bloom_params, &self.views.tgt_color_pp, &self.sampler, &self.depth_sampler, ); // Get mutable reference to shadow views out of the current state let shadow_views = match &mut self.state { State::Interface { shadow_views, .. } => { shadow_views.as_mut().map(|s| (&mut s.0, &mut s.1)) }, State::Complete { shadow: Shadow { map: ShadowMap::Enabled(shadow_map), .. }, .. } => Some((&mut shadow_map.point_depth, &mut shadow_map.directed_depth)), State::Complete { .. } => None, State::Nothing => None, // Should never hit this }; if let (Some((point_depth, directed_depth)), ShadowMode::Map(mode)) = (shadow_views, self.pipeline_modes.shadow) { match ShadowMap::create_shadow_views(&self.device, (dims.x, dims.y), &mode) { Ok((new_point_depth, new_directed_depth)) => { *point_depth = new_point_depth; *directed_depth = new_directed_depth; // Recreate the shadow bind group if needed if let State::Complete { shadow: Shadow { bind, map: ShadowMap::Enabled(shadow_map), .. }, .. } = &mut self.state { *bind = self.layouts.global.bind_shadow_textures( &self.device, &shadow_map.point_depth, &shadow_map.directed_depth, ); } }, Err(err) => { warn!("Could not create shadow map views: {:?}", err); }, } } } else { self.is_minimized = true; } } pub fn maintain(&self) { if self.is_minimized { self.queue.submit(std::iter::empty()); } self.device.poll(wgpu::Maintain::Poll) } /// Create render target views fn create_rt_views( device: &wgpu::Device, size: (u32, u32), pipeline_modes: &PipelineModes, other_modes: &OtherModes, ) -> (Views, [Vec2; bloom::NUM_SIZES]) { let upscaled = Vec2::::from(size) .map(|e| (e as f32 * other_modes.upscale_mode.factor) as u32) .into_tuple(); let (width, height, sample_count) = match pipeline_modes.aa { AaMode::None | AaMode::Fxaa => (upscaled.0, upscaled.1, 1), AaMode::MsaaX4 => (upscaled.0, upscaled.1, 4), AaMode::MsaaX8 => (upscaled.0, upscaled.1, 8), AaMode::MsaaX16 => (upscaled.0, upscaled.1, 16), }; let levels = 1; let color_view = |width, height| { let tex = device.create_texture(&wgpu::TextureDescriptor { label: None, size: wgpu::Extent3d { width, height, depth_or_array_layers: 1, }, mip_level_count: levels, sample_count, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::Rgba16Float, usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::RENDER_ATTACHMENT, }); tex.create_view(&wgpu::TextureViewDescriptor { label: None, format: Some(wgpu::TextureFormat::Rgba16Float), dimension: Some(wgpu::TextureViewDimension::D2), // TODO: why is this not Color? aspect: wgpu::TextureAspect::All, base_mip_level: 0, mip_level_count: None, base_array_layer: 0, array_layer_count: None, }) }; let tgt_color_view = color_view(width, height); let tgt_color_pp_view = color_view(width, height); let mut size_shift = 0; // TODO: skip creating bloom stuff when it is disabled let bloom_sizes = [(); bloom::NUM_SIZES].map(|()| { // .max(1) to ensure we don't create zero sized textures let size = Vec2::new(width, height).map(|e| (e >> size_shift).max(1)); size_shift += 1; size }); let bloom_tgt_views = pipeline_modes .bloom .is_on() .then(|| bloom_sizes.map(|size| color_view(size.x, size.y))); let tgt_depth_tex = device.create_texture(&wgpu::TextureDescriptor { label: None, size: wgpu::Extent3d { width, height, depth_or_array_layers: 1, }, mip_level_count: levels, sample_count, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::Depth32Float, usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::RENDER_ATTACHMENT, }); let tgt_depth_view = tgt_depth_tex.create_view(&wgpu::TextureViewDescriptor { label: None, format: Some(wgpu::TextureFormat::Depth32Float), dimension: Some(wgpu::TextureViewDimension::D2), aspect: wgpu::TextureAspect::DepthOnly, base_mip_level: 0, mip_level_count: None, base_array_layer: 0, array_layer_count: None, }); let win_depth_tex = device.create_texture(&wgpu::TextureDescriptor { label: None, size: wgpu::Extent3d { width: size.0, height: size.1, depth_or_array_layers: 1, }, mip_level_count: levels, sample_count, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::Depth32Float, usage: wgpu::TextureUsage::RENDER_ATTACHMENT, }); // TODO: Consider no depth buffer for the final draw to the window? let win_depth_view = win_depth_tex.create_view(&wgpu::TextureViewDescriptor { label: None, format: Some(wgpu::TextureFormat::Depth32Float), dimension: Some(wgpu::TextureViewDimension::D2), aspect: wgpu::TextureAspect::DepthOnly, base_mip_level: 0, mip_level_count: None, base_array_layer: 0, array_layer_count: None, }); ( Views { tgt_color: tgt_color_view, tgt_depth: tgt_depth_view, bloom_tgts: bloom_tgt_views, tgt_color_pp: tgt_color_pp_view, _win_depth: win_depth_view, }, bloom_sizes.map(|s| s.map(|e| e as f32)), ) } /// Get the resolution of the render target. pub fn resolution(&self) -> Vec2 { self.resolution } /// Get the resolution of the shadow render target. pub fn get_shadow_resolution(&self) -> (Vec2, Vec2) { match &self.state { State::Interface { shadow_views, .. } => shadow_views.as_ref().map(|s| (&s.0, &s.1)), State::Complete { shadow: Shadow { map: ShadowMap::Enabled(shadow_map), .. }, .. } => Some((&shadow_map.point_depth, &shadow_map.directed_depth)), State::Complete { .. } | State::Nothing => None, } .map(|(point, directed)| (point.get_dimensions().xy(), directed.get_dimensions().xy())) .unwrap_or_else(|| (Vec2::new(1, 1), Vec2::new(1, 1))) } // TODO: Seamless is potentially the default with wgpu but we need further // investigation into whether this is actually turned on for the OpenGL // backend // /// 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. //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); // }); // } //} /// Start recording the frame /// When the returned `Drawer` is dropped the recorded draw calls will be /// submitted to the queue /// If there is an intermittent issue with the swap chain then Ok(None) will /// be returned pub fn start_recording_frame<'a>( &'a mut self, globals: &'a GlobalsBindGroup, ) -> Result>, RenderError> { span!( _guard, "start_recording_frame", "Renderer::start_recording_frame" ); if self.is_minimized { return Ok(None); } // Try to get the latest profiling results if self.other_modes.profiler_enabled { // Note: this lags a few frames behind if let Some(profile_times) = self.profiler.process_finished_frame() { self.profile_times = profile_times; } } // Handle polling background pipeline creation/recreation // Temporarily set to nothing and then replace in the statement below let state = core::mem::replace(&mut self.state, State::Nothing); // Indicator for if pipeline recreation finished and we need to recreate bind // groups / render targets (handling defered so that State will be valid // when calling Self::on_resize) let mut trigger_on_resize = false; // If still creating initial pipelines, check if complete self.state = if let State::Interface { pipelines: interface, shadow_views, creating, } = state { match creating.try_complete() { Ok(pipelines) => { let IngameAndShadowPipelines { ingame, shadow } = pipelines; let pipelines = Pipelines::consolidate(interface, ingame); let shadow_map = ShadowMap::new( &self.device, &self.queue, shadow.point, shadow.directed, shadow.figure, shadow_views, ); let shadow_bind = { let (point, directed) = shadow_map.textures(); self.layouts .global .bind_shadow_textures(&self.device, point, directed) }; let shadow = Shadow { map: shadow_map, bind: shadow_bind, }; State::Complete { pipelines, shadow, recreating: None, } }, // Not complete Err(creating) => State::Interface { pipelines: interface, shadow_views, creating, }, } // If recreating the pipelines, check if that is complete } else if let State::Complete { pipelines, mut shadow, recreating: Some((new_pipeline_modes, pipeline_creation)), } = state { match pipeline_creation.try_complete() { Ok(Ok((pipelines, shadow_pipelines, postprocess_layout))) => { if let ( Some(point_pipeline), Some(terrain_directed_pipeline), Some(figure_directed_pipeline), ShadowMap::Enabled(shadow_map), ) = ( shadow_pipelines.point, shadow_pipelines.directed, shadow_pipelines.figure, &mut shadow.map, ) { shadow_map.point_pipeline = point_pipeline; shadow_map.terrain_directed_pipeline = terrain_directed_pipeline; shadow_map.figure_directed_pipeline = figure_directed_pipeline; } self.pipeline_modes = new_pipeline_modes; self.layouts.postprocess = postprocess_layout; // TODO: we have the potential to skip recreating bindings / render targets on // pipeline recreation trigged by shader reloading (would need to ensure new // postprocess_layout is not created...) trigger_on_resize = true; State::Complete { pipelines, shadow, recreating: None, } }, Ok(Err(e)) => { error!(?e, "Could not recreate shaders from assets due to an error"); State::Complete { pipelines, shadow, recreating: None, } }, // Not complete Err(pipeline_creation) => State::Complete { pipelines, shadow, recreating: Some((new_pipeline_modes, pipeline_creation)), }, } } else { state }; // Call on_resize to recreate render targets and their bind groups if the // pipelines were recreated with a new postprocess layout and or changes in the // render modes if trigger_on_resize { self.on_resize(self.resolution); } // If the shaders files were changed attempt to recreate the shaders if self.shaders_watcher.reloaded() { self.recreate_pipelines(self.pipeline_modes.clone()); } // Or if we have a recreation pending if matches!(&self.state, State::Complete { recreating: None, .. }) { if let Some(new_pipeline_modes) = self.recreation_pending.take() { self.recreate_pipelines(new_pipeline_modes); } } let tex = match self.swap_chain.get_current_frame() { Ok(frame) => frame.output, // If lost recreate the swap chain Err(err @ wgpu::SwapChainError::Lost) => { warn!("{}. Recreating swap chain. A frame will be missed", err); self.on_resize(self.resolution); return Ok(None); }, Err(wgpu::SwapChainError::Timeout) => { // This will probably be resolved on the next frame // NOTE: we don't log this because it happens very frequently with // PresentMode::Fifo and unlimited FPS on certain machines return Ok(None); }, Err(err @ wgpu::SwapChainError::Outdated) => { warn!("{}. Recreating the swapchain", err); self.swap_chain = self.device.create_swap_chain(&self.surface, &self.sc_desc); return Ok(None); }, Err(err @ wgpu::SwapChainError::OutOfMemory) => return Err(err.into()), }; let encoder = self .device .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("A render encoder"), }); Ok(Some(drawer::Drawer::new(encoder, self, tex, globals))) } /// Recreate the pipelines fn recreate_pipelines(&mut self, pipeline_modes: PipelineModes) { match &mut self.state { State::Complete { recreating, .. } if recreating.is_some() => { // Defer recreation so that we are not building multiple sets of pipelines in // the background at once self.recreation_pending = Some(pipeline_modes); }, State::Complete { recreating, shadow, .. } => { *recreating = Some(( pipeline_modes.clone(), pipeline_creation::recreate_pipelines( Arc::clone(&self.device), Arc::clone(&self.layouts.immutable), self.shaders.cloned(), pipeline_modes, // NOTE: if present_mode starts to be used to configure pipelines then it // needs to become a part of the pipeline modes // (note here since the present mode is accessible // through the swap chain descriptor) self.sc_desc.clone(), // Note: cheap clone shadow.map.is_enabled(), ), )); }, State::Interface { .. } => { // Defer recreation so that we are not building multiple sets of pipelines in // the background at once self.recreation_pending = Some(pipeline_modes); }, State::Nothing => {}, } } /// Create a new set of constants with the provided values. pub fn create_consts(&mut self, vals: &[T]) -> Consts { Self::create_consts_inner(&self.device, &self.queue, vals) } pub fn create_consts_inner( device: &wgpu::Device, queue: &wgpu::Queue, vals: &[T], ) -> Consts { let mut consts = Consts::new(device, vals.len()); consts.update(queue, vals, 0); consts } /// Update a set of constants with the provided values. pub fn update_consts(&self, consts: &mut Consts, vals: &[T]) { consts.update(&self.queue, vals, 0) } pub fn update_clouds_locals(&mut self, new_val: clouds::Locals) { self.locals.clouds.update(&self.queue, &[new_val], 0) } pub fn update_postprocess_locals(&mut self, new_val: postprocess::Locals) { self.locals.postprocess.update(&self.queue, &[new_val], 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(&self.device, vals.len()); instances.update(&self.queue, vals, 0); Ok(instances) } /// Ensure that the quad index buffer is large enough for a quad vertex /// buffer with this many vertices pub(super) fn ensure_sufficient_index_length( &mut self, // Length of the vert buffer with 4 verts per quad vert_length: usize, ) { let quad_index_length = vert_length / 4 * 6; match V::QUADS_INDEX { Some(wgpu::IndexFormat::Uint16) => { // Make sure the global quad index buffer is large enough if self.quad_index_buffer_u16.len() < quad_index_length { // Make sure we aren't over the max if vert_length > u16::MAX as usize { panic!( "Vertex type: {} needs to use a larger index type, length: {}", core::any::type_name::(), vert_length ); } self.quad_index_buffer_u16 = create_quad_index_buffer_u16(&self.device, vert_length); } }, Some(wgpu::IndexFormat::Uint32) => { // Make sure the global quad index buffer is large enough if self.quad_index_buffer_u32.len() < quad_index_length { // Make sure we aren't over the max if vert_length > u32::MAX as usize { panic!( "More than u32::MAX({}) verts({}) for type({}) using an index buffer!", u32::MAX, vert_length, core::any::type_name::() ); } self.quad_index_buffer_u32 = create_quad_index_buffer_u32(&self.device, vert_length); } }, None => {}, } } pub fn create_sprite_verts(&mut self, mesh: Mesh) -> sprite::SpriteVerts { self.ensure_sufficient_index_length::(sprite::VERT_PAGE_SIZE as usize); sprite::create_verts_buffer(&self.device, mesh) } /// Create a new model from the provided mesh. /// If the provided mesh is empty this returns None pub fn create_model(&mut self, mesh: &Mesh) -> Option> { self.ensure_sufficient_index_length::(mesh.vertices().len()); Model::new(&self.device, mesh) } /// Create a new dynamic model with the specified size. pub fn create_dynamic_model(&mut self, size: usize) -> DynamicModel { DynamicModel::new(&self.device, size) } /// Update a dynamic model with a mesh and a offset. pub fn update_model(&self, model: &DynamicModel, mesh: &Mesh, offset: usize) { model.update(&self.queue, mesh, offset) } /// Return the maximum supported texture size. pub fn max_texture_size(&self) -> u32 { Self::max_texture_size_raw(&self.device) } /// Return the maximum supported texture size from the factory. fn max_texture_size_raw(_device: &wgpu::Device) -> u32 { // 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. /// # Panics /// If the provided data doesn't completely fill the texture this function /// will panic. pub fn create_texture_with_data_raw( &mut self, texture_info: &wgpu::TextureDescriptor, view_info: &wgpu::TextureViewDescriptor, sampler_info: &wgpu::SamplerDescriptor, data: &[u8], ) -> Texture { let tex = Texture::new_raw(&self.device, texture_info, view_info, sampler_info); let size = texture_info.size; let block_size = texture_info.format.describe().block_size; assert_eq!( size.width as usize * size.height as usize * size.depth_or_array_layers as usize * block_size as usize, data.len(), "Provided data length {} does not fill the provided texture size {:?}", data.len(), size, ); tex.update( &self.queue, [0; 2], [texture_info.size.width, texture_info.size.height], data, ); tex } /// Create a new raw texture. pub fn create_texture_raw( &mut self, texture_info: &wgpu::TextureDescriptor, view_info: &wgpu::TextureViewDescriptor, sampler_info: &wgpu::SamplerDescriptor, ) -> Texture { let texture = Texture::new_raw(&self.device, texture_info, view_info, sampler_info); texture.clear(&self.queue); // Needs to be fully initialized for partial writes to work on Dx12 AMD texture } /// Create a new texture from the provided image. /// /// Currently only supports Rgba8Srgb pub fn create_texture( &mut self, image: &image::DynamicImage, filter_method: Option, address_mode: Option, ) -> Result { Texture::new( &self.device, &self.queue, image, filter_method, address_mode, ) } /// Create a new dynamic texture with the /// specified dimensions. /// /// Currently only supports Rgba8Srgb pub fn create_dynamic_texture(&mut self, dims: Vec2) -> Texture { Texture::new_dynamic(&self.device, &self.queue, dims.x, dims.y) } /// Update a texture with the provided offset, size, and data. /// /// Currently only supports Rgba8Srgb pub fn update_texture( &mut self, texture: &Texture, /* */ offset: [u32; 2], size: [u32; 2], // TODO: be generic over pixel type data: &[[u8; 4]], ) { texture.update(&self.queue, offset, size, bytemuck::cast_slice(data)) } /// Queue to obtain a screenshot on the next frame render pub fn create_screenshot( &mut self, screenshot_handler: impl FnOnce(Result) + Send + 'static, ) { // Queue screenshot self.take_screenshot = Some(Box::new(screenshot_handler)); // Take profiler snapshot if self.other_modes.profiler_enabled { let file_name = format!( "frame-trace_{}.json", std::time::SystemTime::now() .duration_since(std::time::SystemTime::UNIX_EPOCH) .map(|d| d.as_millis()) .unwrap_or(0) ); if let Err(err) = wgpu_profiler::chrometrace::write_chrometrace( std::path::Path::new(&file_name), &self.profile_times, ) { error!(?err, "Failed to save GPU timing snapshot"); } else { info!("Saved GPU timing snapshot as: {}", file_name); } } } // Consider reenabling at some time // // /// 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: // (self.tgt_depth_view.clone()/* , (0, 0) */), }, // ); */ // } } fn create_quad_index_buffer_u16(device: &wgpu::Device, vert_length: usize) -> Buffer { assert!(vert_length <= u16::MAX as usize); let indices = [0, 1, 2, 2, 1, 3] .iter() .cycle() .copied() .take(vert_length / 4 * 6) .enumerate() .map(|(i, b)| (i / 6 * 4 + b) as u16) .collect::>(); Buffer::new(device, wgpu::BufferUsage::INDEX, &indices) } fn create_quad_index_buffer_u32(device: &wgpu::Device, vert_length: usize) -> Buffer { assert!(vert_length <= u32::MAX as usize); let indices = [0, 1, 2, 2, 1, 3] .iter() .cycle() .copied() .take(vert_length / 4 * 6) .enumerate() .map(|(i, b)| (i / 6 * 4 + b) as u32) .collect::>(); Buffer::new(device, wgpu::BufferUsage::INDEX, &indices) }