use anyhow::Result; use bytemuck; use galactica_system::{data::ShipState, phys::PhysSimShipHandle}; use glyphon::Resolution; use nalgebra::Point2; use std::iter; use wgpu; use crate::{globaluniform::GlobalDataContent, vertexbuffer::consts::SPRITE_INDICES, RenderInput}; impl<'a> super::GPUState { /// Render routines while player is flying fn render_flying(&'a mut self, input: &RenderInput) -> Result<()> { let output = self.surface.get_current_texture()?; let view = output.texture.create_view(&Default::default()); let mut encoder = self .device .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("render encoder"), }); let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("render pass"), color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: &view, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.0, g: 0.0, b: 0.0, a: 1.0, }), store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, occlusion_query_set: None, timestamp_writes: None, }); // Create sprite instances // Game coordinates (relative to camera) of ne and sw corners of screen. // Used to skip off-screen sprites. let clip_ne = Point2::new(-self.state.window_aspect, 1.0) * input.camera_zoom; let clip_sw = Point2::new(self.state.window_aspect, -1.0) * input.camera_zoom; // Order matters, it determines what is drawn on top. // The order inside ships and projectiles doesn't matter, // but ships should always be under projectiles. self.phys_push_system(&input, (clip_ne, clip_sw)); self.phys_push_ships(&input, (clip_ne, clip_sw)); self.phys_push_projectiles(&input, (clip_ne, clip_sw)); self.phys_push_effects(&input, (clip_ne, clip_sw)); self.ui.draw(&input, &mut self.state); // These should match the indices in each shader, // and should each have a corresponding bind group layout. render_pass.set_bind_group(0, &self.texture_array.bind_group, &[]); render_pass.set_bind_group(1, &self.state.global_uniform.bind_group, &[]); // Starfield pipeline self.state .vertex_buffers .get_starfield() .set_in_pass(&mut render_pass); render_pass.set_pipeline(&self.starfield_pipeline); render_pass.draw_indexed( 0..SPRITE_INDICES.len() as u32, 0, 0..self.state.get_starfield_counter(), ); // Sprite pipeline self.state .vertex_buffers .get_object() .set_in_pass(&mut render_pass); render_pass.set_pipeline(&self.object_pipeline); render_pass.draw_indexed( 0..SPRITE_INDICES.len() as u32, 0, 0..self.state.get_object_counter(), ); // Ui pipeline self.state .vertex_buffers .get_ui() .set_in_pass(&mut render_pass); render_pass.set_pipeline(&self.ui_pipeline); render_pass.draw_indexed( 0..SPRITE_INDICES.len() as u32, 0, 0..self.state.get_ui_counter(), ); // Radial progress bars // TODO: do we need to do this every time? self.state .vertex_buffers .get_radialbar() .set_in_pass(&mut render_pass); render_pass.set_pipeline(&self.radialbar_pipeline); render_pass.draw_indexed( 0..SPRITE_INDICES.len() as u32, 0, 0..self.state.get_radialbar_counter(), ); let textareas = self.ui.get_textareas(input, &self.state); self.state .text_renderer .prepare( &self.device, &self.state.queue, &mut self.state.text_font_system, &mut self.state.text_atlas, Resolution { width: self.state.window_size.width, height: self.state.window_size.height, }, textareas, &mut self.state.text_cache, ) .unwrap(); self.state .text_renderer .render(&self.state.text_atlas, &mut render_pass) .unwrap(); // begin_render_pass borrows encoder mutably, // so we need to drop it before calling finish. drop(render_pass); self.state.queue.submit(iter::once(encoder.finish())); output.present(); return Ok(()); } /// Render routines while player is landed fn render_landed(&'a mut self, input: &RenderInput) -> Result<()> { let output = self.surface.get_current_texture()?; let view = output.texture.create_view(&Default::default()); let mut encoder = self .device .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("render encoder"), }); let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("render pass"), color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: &view, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.0, g: 0.0, b: 0.0, a: 1.0, }), store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, occlusion_query_set: None, timestamp_writes: None, }); // Create sprite instances self.ui.draw(&input, &mut self.state); // These should match the indices in each shader, // and should each have a corresponding bind group layout. render_pass.set_bind_group(0, &self.texture_array.bind_group, &[]); render_pass.set_bind_group(1, &self.state.global_uniform.bind_group, &[]); // Starfield pipeline self.state .vertex_buffers .get_starfield() .set_in_pass(&mut render_pass); render_pass.set_pipeline(&self.starfield_pipeline); render_pass.draw_indexed( 0..SPRITE_INDICES.len() as u32, 0, 0..self.state.get_starfield_counter(), ); // Ui pipeline self.state .vertex_buffers .get_ui() .set_in_pass(&mut render_pass); render_pass.set_pipeline(&self.ui_pipeline); render_pass.draw_indexed( 0..SPRITE_INDICES.len() as u32, 0, 0..self.state.get_ui_counter(), ); // Radial progress bars self.state .vertex_buffers .get_radialbar() .set_in_pass(&mut render_pass); render_pass.set_pipeline(&self.radialbar_pipeline); render_pass.draw_indexed( 0..SPRITE_INDICES.len() as u32, 0, 0..self.state.get_radialbar_counter(), ); let textareas = self.ui.get_textareas(input, &self.state); self.state .text_renderer .prepare( &self.device, &self.state.queue, &mut self.state.text_font_system, &mut self.state.text_atlas, Resolution { width: self.state.window_size.width, height: self.state.window_size.height, }, textareas, &mut self.state.text_cache, ) .unwrap(); self.state .text_renderer .render(&self.state.text_atlas, &mut render_pass) .unwrap(); // begin_render_pass borrows encoder mutably, // so we need to drop it before calling finish. drop(render_pass); self.state.queue.submit(iter::once(encoder.finish())); output.present(); return Ok(()); } /// 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: input.camera_pos.into(), camera_zoom: [input.camera_zoom, 0.0], camera_zoom_limits: [ input.ct.get_config().zoom_min, input.ct.get_config().zoom_max, ], window_size: [ self.state.window_size.width as f32, self.state.window_size.height as f32, ], window_scale: [self.state.window.scale_factor() as f32, 0.0], window_aspect: [self.state.window_aspect, 0.0], starfield_sprite: [input.ct.get_config().starfield_texture.into(), 0], starfield_tile_size: [input.ct.get_config().starfield_size, 0.0], starfield_size_limits: [ input.ct.get_config().starfield_min_size, input.ct.get_config().starfield_max_size, ], current_time: [input.current_time, 0.0], }]), ); self.state.frame_reset(); self.ui.update_state(&input, &mut self.state); match input .phys_img .get_ship(&PhysSimShipHandle(input.player.ship.unwrap())) .unwrap() .ship .get_data() .get_state() { ShipState::Collapsing | ShipState::Dead | ShipState::Landing { .. } | ShipState::UnLanding { .. } | ShipState::Flying { .. } => { self.render_flying(&input).unwrap(); } ShipState::Landed { .. } => { self.render_landed(&input).unwrap(); } } return Ok(()); } }