use std::rc::Rc; use anyhow::Result; use bytemuck; use galactica_content::Content; use glyphon::{FontSystem, SwashCache, TextAtlas, TextRenderer}; use log::debug; use wgpu; use winit; use crate::{ globaluniform::{GlobalDataContent, GlobalUniform}, pipeline::PipelineBuilder, renderscene::{LandedScene, RenderScene, SystemScene}, shaderprocessor::preprocess_shader, starfield::Starfield, texturearray::TextureArray, ui::{UiManager, UiScene}, RenderInput, RenderScenes, RenderState, VertexBuffers, }; /// A high-level GPU wrapper. Reads game state (via RenderInput), produces pretty pictures. pub struct GPUState { pub(crate) device: wgpu::Device, pub(crate) config: wgpu::SurfaceConfiguration, pub(crate) surface: wgpu::Surface, pub(crate) object_pipeline: wgpu::RenderPipeline, pub(crate) starfield_pipeline: wgpu::RenderPipeline, pub(crate) ui_pipeline: wgpu::RenderPipeline, pub(crate) radialbar_pipeline: wgpu::RenderPipeline, pub(crate) starfield: Starfield, pub(crate) texture_array: TextureArray, pub(crate) state: RenderState, pub(crate) ui: UiManager, pub(crate) scene: RenderScenes, } impl GPUState { /// Make a new GPUState that draws on `window` pub async fn new(window: winit::window::Window, ct: Rc) -> Result { let window_size = window.inner_size(); let window_aspect = window_size.width as f32 / window_size.height as f32; let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { backends: wgpu::Backends::all(), ..Default::default() }); let surface = unsafe { instance.create_surface(&window) }.unwrap(); // Basic setup let device; let queue; let config; { let adapter = instance .request_adapter(&wgpu::RequestAdapterOptions { power_preference: wgpu::PowerPreference::default(), compatible_surface: Some(&surface), force_fallback_adapter: false, }) .await .unwrap(); (device, queue) = adapter .request_device( &wgpu::DeviceDescriptor { // TODO: remove nonuniform sampled textures features: wgpu::Features::TEXTURE_BINDING_ARRAY | wgpu::Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING, // We may need limits if we compile for wasm limits: wgpu::Limits::default(), label: Some("gpu device"), }, None, ) .await .unwrap(); // Assume sRGB let surface_caps = surface.get_capabilities(&adapter); let surface_format = surface_caps .formats .iter() .copied() .filter(|f| f.is_srgb()) .filter(|f| f.has_stencil_aspect()) .next() .unwrap_or(surface_caps.formats[0]); config = wgpu::SurfaceConfiguration { usage: wgpu::TextureUsages::RENDER_ATTACHMENT, format: surface_format, width: window_size.width, height: window_size.height, present_mode: surface_caps.present_modes[0], alpha_mode: surface_caps.alpha_modes[0], view_formats: vec![], }; surface.configure(&device, &config); } let vertex_buffers = VertexBuffers::new(&device, &ct); // Load uniforms let global_uniform = GlobalUniform::new(&device); let texture_array = TextureArray::new(&device, &queue, &ct)?; // Make sure these match the indices in each shader let bind_group_layouts = &[ &texture_array.bind_group_layout, &global_uniform.bind_group_layout, ]; // Text renderer let mut text_atlas = TextAtlas::new(&device, &queue, wgpu::TextureFormat::Bgra8UnormSrgb); let mut text_font_system = FontSystem::new_with_locale_and_db( "en-US".to_string(), glyphon::fontdb::Database::new(), ); let conf = ct.get_config(); for font in &conf.font_files { text_font_system.db_mut().load_font_file(font)?; } // TODO: nice error if no family with this name is found text_font_system .db_mut() .set_sans_serif_family(conf.font_sans.clone()); text_font_system .db_mut() .set_serif_family(conf.font_serif.clone()); text_font_system .db_mut() .set_monospace_family(conf.font_mono.clone()); //text_font_system // .db_mut() // .set_cursive_family(conf.font_cursive.clone()); //text_font_system // .db_mut() // .set_fantasy_family(conf.font_fantasy.clone()); let text_cache = SwashCache::new(); let text_renderer = TextRenderer::new( &mut text_atlas, &device, wgpu::MultisampleState::default(), None, ); // Create render pipelines let object_pipeline = PipelineBuilder::new("object", &device) .set_shader(&preprocess_shader( &include_str!(concat!( env!("CARGO_MANIFEST_DIR"), "/shaders/", "object.wgsl" )), &global_uniform, 1, )) .set_format(config.format) .set_triangle(true) .set_vertex_buffer(vertex_buffers.get_object()) .set_bind_group_layouts(bind_group_layouts) .build(); let starfield_pipeline = PipelineBuilder::new("starfield", &device) .set_shader(&preprocess_shader( &include_str!(concat!( env!("CARGO_MANIFEST_DIR"), "/shaders/", "starfield.wgsl" )), &global_uniform, 1, )) .set_format(config.format) .set_triangle(true) .set_vertex_buffer(vertex_buffers.get_starfield()) .set_bind_group_layouts(bind_group_layouts) .build(); let ui_pipeline = PipelineBuilder::new("ui", &device) .set_shader(&preprocess_shader( &include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/shaders/", "ui.wgsl")), &global_uniform, 1, )) .set_format(config.format) .set_triangle(true) .set_vertex_buffer(vertex_buffers.get_ui()) .set_bind_group_layouts(bind_group_layouts) .build(); let radialbar_pipeline = PipelineBuilder::new("radialbar", &device) .set_shader(&preprocess_shader( &include_str!(concat!( env!("CARGO_MANIFEST_DIR"), "/shaders/", "radialbar.wgsl" )), &global_uniform, 1, )) .set_format(config.format) .set_triangle(true) .set_vertex_buffer(vertex_buffers.get_radialbar()) .set_bind_group_layouts(bind_group_layouts) .build(); let mut starfield = Starfield::new(); starfield.regenerate(&ct); return Ok(Self { ui: UiManager::new(ct), device, config, surface, starfield, texture_array, object_pipeline, starfield_pipeline, ui_pipeline, radialbar_pipeline, scene: RenderScenes::Landed, state: RenderState { queue, window, window_size, window_aspect, global_uniform, vertex_buffers, text_atlas, text_cache, text_font_system, text_renderer, }, }); } } impl GPUState { /// Get the window we are attached to pub fn window(&self) -> &winit::window::Window { &self.state.window } /// Change the current scenection pub fn set_scene(&mut self, scene: RenderScenes) { debug!("switching to {:?}", scene); match scene { RenderScenes::Landed => self.ui.set_scene(&mut self.state, UiScene::Landed).unwrap(), RenderScenes::System => self.ui.set_scene(&mut self.state, UiScene::Flying).unwrap(), }; self.scene = scene; } /// Update window size. /// This should be called whenever our window is resized. pub fn resize(&mut self, ct: &Content) { let new_size = self.state.window.inner_size(); if new_size.width > 0 && new_size.height > 0 { self.state.window_size = new_size; self.state.window_aspect = new_size.width as f32 / new_size.height as f32; self.config.width = new_size.width; self.config.height = new_size.height; self.surface.configure(&self.device, &self.config); } self.starfield.update_buffer(ct, &mut self.state); } /// Initialize the rendering engine pub fn init(&mut self, ct: &Content) { // Update global values self.state.queue.write_buffer( &self.state.global_uniform.atlas_buffer, 0, bytemuck::cast_slice(&[self.texture_array.texture_atlas]), ); self.starfield.update_buffer(ct, &mut self.state); } /// Main render function. Draws sprites on a window. pub fn render(&mut self, input: RenderInput) -> Result<(), wgpu::SurfaceError> { // Update global values self.state.queue.write_buffer( &self.state.global_uniform.data_buffer, 0, bytemuck::cast_slice(&[GlobalDataContent { camera_position_x: input.camera_pos.x, camera_position_y: input.camera_pos.y, camera_zoom: input.camera_zoom, camera_zoom_min: input.ct.get_config().zoom_min, camera_zoom_max: input.ct.get_config().zoom_max, window_size_w: self.state.window_size.width as f32, window_size_h: self.state.window_size.height as f32, window_scale: self.state.window.scale_factor() as f32, window_aspect: self.state.window_aspect, starfield_sprite: input.ct.get_config().starfield_texture.into(), starfield_tile_size: input.ct.get_config().starfield_size, starfield_size_min: input.ct.get_config().starfield_min_size, starfield_size_max: input.ct.get_config().starfield_max_size, }]), ); self.state.frame_reset(); match self.scene { RenderScenes::System => SystemScene::render(self, &input).unwrap(), RenderScenes::Landed => LandedScene::render(self, &input).unwrap(), } return Ok(()); } }