From 14722967665246754f9ee73447b82fd16da15975 Mon Sep 17 00:00:00 2001 From: Mark Date: Sat, 27 Jan 2024 15:45:37 -0800 Subject: [PATCH] Added render scenes, cleanup --- crates/galactica/src/main.rs | 23 +- .../src/{gpustate/new.rs => gpustate.rs} | 151 +++++++-- crates/render/src/gpustate/mod.rs | 64 ---- crates/render/src/gpustate/render.rs | 303 ------------------ crates/render/src/lib.rs | 5 +- crates/render/src/renderscene/landed.rs | 117 +++++++ crates/render/src/renderscene/mod.rs | 32 ++ crates/render/src/renderscene/system/mod.rs | 4 + .../{gpustate => renderscene/system}/phys.rs | 64 ++-- .../render/src/renderscene/system/system.rs | 144 +++++++++ crates/render/src/ui/manager.rs | 54 +--- crates/render/src/ui/mod.rs | 3 +- crates/render/src/ui/scenes/flying/mod.rs | 2 +- crates/render/src/ui/scenes/flying/scene.rs | 6 +- crates/render/src/ui/scenes/landed.rs | 10 +- crates/render/src/ui/scenes/mod.rs | 6 +- crates/render/src/ui/scenes/outfitter.rs | 10 +- 17 files changed, 519 insertions(+), 479 deletions(-) rename crates/render/src/{gpustate/new.rs => gpustate.rs} (54%) delete mode 100644 crates/render/src/gpustate/mod.rs delete mode 100644 crates/render/src/gpustate/render.rs create mode 100644 crates/render/src/renderscene/landed.rs create mode 100644 crates/render/src/renderscene/mod.rs create mode 100644 crates/render/src/renderscene/system/mod.rs rename crates/render/src/{gpustate => renderscene/system}/phys.rs (87%) create mode 100644 crates/render/src/renderscene/system/system.rs diff --git a/crates/galactica/src/main.rs b/crates/galactica/src/main.rs index bd78918..575c3e5 100644 --- a/crates/galactica/src/main.rs +++ b/crates/galactica/src/main.rs @@ -4,7 +4,7 @@ use anyhow::{bail, Result}; use clap::Parser; use galactica_content::{Content, SystemHandle}; use galactica_playeragent::{PlayerAgent, PlayerStatus}; -use galactica_render::RenderInput; +use galactica_render::{RenderInput, RenderScenes}; use galactica_system::{ data::ShipState, phys::{PhysImage, PhysSimShipHandle}, @@ -120,7 +120,11 @@ fn try_main() -> Result<()> { let event_loop = EventLoop::new(); let window = WindowBuilder::new().build(&event_loop).unwrap(); - let mut gpu = pollster::block_on(galactica_render::GPUState::new(window, &content))?; + let mut gpu = pollster::block_on(galactica_render::GPUState::new( + window, + &content, + RenderScenes::System, + ))?; gpu.init(&content); // TODO: don't clone content @@ -134,6 +138,7 @@ fn try_main() -> Result<()> { let mut phys_img = PhysImage::new(); let mut last_run = Instant::now(); + let mut was_landed = false; event_loop.run(move |event, _, control_flow| { match event { @@ -174,9 +179,21 @@ fn try_main() -> Result<()> { ShipState::Landing { .. } | ShipState::UnLanding { .. } | ShipState::Collapsing { .. } - | ShipState::Flying { .. } => Some(*o.rigidbody.translation()), + | ShipState::Flying { .. } => { + if was_landed { + was_landed = false; + gpu.set_scene(&content, RenderScenes::System); + } + + Some(*o.rigidbody.translation()) + } ShipState::Landed { target } => { + if !was_landed { + was_landed = true; + gpu.set_scene(&content, RenderScenes::Landed); + } + let b = content.get_system_object(*target); Some(Vector2::new(b.pos.x, b.pos.y)) } diff --git a/crates/render/src/gpustate/new.rs b/crates/render/src/gpustate.rs similarity index 54% rename from crates/render/src/gpustate/new.rs rename to crates/render/src/gpustate.rs index d8469f7..3aa3220 100644 --- a/crates/render/src/gpustate/new.rs +++ b/crates/render/src/gpustate.rs @@ -1,16 +1,48 @@ 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::GlobalUniform, pipeline::PipelineBuilder, shaderprocessor::preprocess_shader, - starfield::Starfield, texturearray::TextureArray, ui::UiManager, GPUState, RenderState, - VertexBuffers, + globaluniform::{GlobalDataContent, GlobalUniform}, + pipeline::PipelineBuilder, + renderscene::{LandedScene, RenderScene, SystemScene}, + shaderprocessor::preprocess_shader, + starfield::Starfield, + texturearray::TextureArray, + ui::{ + scenes::{UiFlyingScene, UiLandedScene}, + UiManager, UiScenes, + }, + 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: &Content) -> Result { + pub async fn new( + window: winit::window::Window, + ct: &Content, + scene: RenderScenes, + ) -> Result { let window_size = window.inner_size(); let window_aspect = window_size.width as f32 / window_size.height as f32; @@ -37,18 +69,18 @@ impl GPUState { .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(); + .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); @@ -189,14 +221,11 @@ impl GPUState { let mut state = RenderState { queue, - window, window_size, window_aspect, global_uniform, - vertex_buffers, - text_atlas, text_cache, text_font_system, @@ -214,8 +243,90 @@ impl GPUState { starfield_pipeline, ui_pipeline, radialbar_pipeline, - + scene, state, }); } } + +impl GPUState { + /// Get the window we are attached to + pub fn window(&self) -> &winit::window::Window { + &self.state.window + } + + /// Change the current scene + pub fn set_scene(&mut self, ct: &Content, scene: RenderScenes) { + debug!("switching to {:?}", scene); + + match scene { + RenderScenes::Landed => self + .ui + .set_scene(UiScenes::Landed(UiLandedScene::new(ct, &mut self.state))), + RenderScenes::System => self + .ui + .set_scene(UiScenes::Flying(UiFlyingScene::new(ct, &mut self.state))), + } + + 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(()); + } +} diff --git a/crates/render/src/gpustate/mod.rs b/crates/render/src/gpustate/mod.rs deleted file mode 100644 index 89f6e7d..0000000 --- a/crates/render/src/gpustate/mod.rs +++ /dev/null @@ -1,64 +0,0 @@ -use bytemuck; -use galactica_content::Content; -use wgpu; -use winit; - -use crate::{starfield::Starfield, texturearray::TextureArray, ui::UiManager, RenderState}; - -/// GPUState is very big, so its methods have been split -/// among the following files. -mod new; -mod phys; -mod render; - -/// A high-level GPU wrapper. Reads game state (via RenderInput), -/// produces pretty pictures. -pub struct GPUState { - device: wgpu::Device, - config: wgpu::SurfaceConfiguration, - surface: wgpu::Surface, - - object_pipeline: wgpu::RenderPipeline, - starfield_pipeline: wgpu::RenderPipeline, - ui_pipeline: wgpu::RenderPipeline, - radialbar_pipeline: wgpu::RenderPipeline, - - starfield: Starfield, - texture_array: TextureArray, - - state: RenderState, - ui: UiManager, -} - -impl GPUState { - /// Get the window we are attached to - pub fn window(&self) -> &winit::window::Window { - &self.state.window - } - - /// 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); - } -} diff --git a/crates/render/src/gpustate/render.rs b/crates/render/src/gpustate/render.rs deleted file mode 100644 index 93849ff..0000000 --- a/crates/render/src/gpustate/render.rs +++ /dev/null @@ -1,303 +0,0 @@ -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_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(); - 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(()); - } -} diff --git a/crates/render/src/lib.rs b/crates/render/src/lib.rs index 60a5ad3..3cd8af3 100644 --- a/crates/render/src/lib.rs +++ b/crates/render/src/lib.rs @@ -12,6 +12,7 @@ mod gpustate; mod pipeline; mod positionanchor; mod renderinput; +mod renderscene; mod renderstate; mod shaderprocessor; mod starfield; @@ -19,11 +20,11 @@ mod texturearray; mod ui; mod vertexbuffer; -use renderstate::*; - pub use gpustate::GPUState; pub use positionanchor::PositionAnchor; pub use renderinput::RenderInput; +pub use renderscene::RenderScenes; +use renderstate::*; use nalgebra::Matrix4; diff --git a/crates/render/src/renderscene/landed.rs b/crates/render/src/renderscene/landed.rs new file mode 100644 index 0000000..746b6c0 --- /dev/null +++ b/crates/render/src/renderscene/landed.rs @@ -0,0 +1,117 @@ +use anyhow::Result; +use glyphon::Resolution; +use std::iter; + +use super::RenderScene; +use crate::vertexbuffer::consts::SPRITE_INDICES; + +pub struct LandedScene {} + +impl RenderScene for LandedScene { + fn render(g: &mut crate::GPUState, input: &crate::RenderInput) -> Result<()> { + let output = g.surface.get_current_texture()?; + let view = output.texture.create_view(&Default::default()); + + let mut encoder = g + .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 + g.ui.draw(&input, &mut g.state); + + // These should match the indices in each shader, + // and should each have a corresponding bind group layout. + render_pass.set_bind_group(0, &g.texture_array.bind_group, &[]); + render_pass.set_bind_group(1, &g.state.global_uniform.bind_group, &[]); + + // Starfield pipeline + g.state + .vertex_buffers + .get_starfield() + .set_in_pass(&mut render_pass); + render_pass.set_pipeline(&g.starfield_pipeline); + render_pass.draw_indexed( + 0..SPRITE_INDICES.len() as u32, + 0, + 0..g.state.get_starfield_counter(), + ); + + // Ui pipeline + g.state + .vertex_buffers + .get_ui() + .set_in_pass(&mut render_pass); + render_pass.set_pipeline(&g.ui_pipeline); + render_pass.draw_indexed( + 0..SPRITE_INDICES.len() as u32, + 0, + 0..g.state.get_ui_counter(), + ); + + // Radial progress bars + g.state + .vertex_buffers + .get_radialbar() + .set_in_pass(&mut render_pass); + render_pass.set_pipeline(&g.radialbar_pipeline); + render_pass.draw_indexed( + 0..SPRITE_INDICES.len() as u32, + 0, + 0..g.state.get_radialbar_counter(), + ); + + let textareas = g.ui.get_textareas(input, &g.state); + g.state + .text_renderer + .prepare( + &g.device, + &g.state.queue, + &mut g.state.text_font_system, + &mut g.state.text_atlas, + Resolution { + width: g.state.window_size.width, + height: g.state.window_size.height, + }, + textareas, + &mut g.state.text_cache, + ) + .unwrap(); + + g.state + .text_renderer + .render(&g.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); + + g.state.queue.submit(iter::once(encoder.finish())); + output.present(); + + return Ok(()); + } +} diff --git a/crates/render/src/renderscene/mod.rs b/crates/render/src/renderscene/mod.rs new file mode 100644 index 0000000..cb30de5 --- /dev/null +++ b/crates/render/src/renderscene/mod.rs @@ -0,0 +1,32 @@ +mod landed; +mod system; + +use std::fmt::Debug; + +pub use landed::LandedScene; +pub use system::SystemScene; + +use crate::{GPUState, RenderInput}; +use anyhow::Result; + +pub trait RenderScene { + fn render(g: &mut GPUState, input: &RenderInput) -> Result<()>; +} + +/// What render routine to run +pub enum RenderScenes { + /// Draw the system we're in + System, + + /// Draw the landed UI + Landed, +} + +impl Debug for RenderScenes { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Landed => write!(f, "RenderScenes::Landed"), + Self::System => write!(f, "RenderScenes::System"), + } + } +} diff --git a/crates/render/src/renderscene/system/mod.rs b/crates/render/src/renderscene/system/mod.rs new file mode 100644 index 0000000..466f2f0 --- /dev/null +++ b/crates/render/src/renderscene/system/mod.rs @@ -0,0 +1,4 @@ +mod phys; +mod system; + +pub use system::SystemScene; diff --git a/crates/render/src/gpustate/phys.rs b/crates/render/src/renderscene/system/phys.rs similarity index 87% rename from crates/render/src/gpustate/phys.rs rename to crates/render/src/renderscene/system/phys.rs index 15d2e16..10a5e03 100644 --- a/crates/render/src/gpustate/phys.rs +++ b/crates/render/src/renderscene/system/phys.rs @@ -1,5 +1,3 @@ -//! GPUState routines for drawing objects in a system - use bytemuck; use galactica_system::data::ShipState; use galactica_util::to_radians; @@ -9,9 +7,11 @@ use crate::{ globaluniform::ObjectData, vertexbuffer::types::ObjectInstance, GPUState, RenderInput, }; -impl GPUState { - pub(super) fn phys_push_ships( - &mut self, +use super::SystemScene; + +impl SystemScene { + pub(super) fn push_ships( + g: &mut GPUState, input: &RenderInput, // NE and SW corners of screen screen_clip: (Point2, Point2), @@ -65,10 +65,10 @@ impl GPUState { continue; } - let idx = self.state.get_object_counter(); + let idx = g.state.get_object_counter(); // Write this object's location data - self.state.queue.write_buffer( - &self.state.global_uniform.object_buffer, + g.state.queue.write_buffer( + &g.state.global_uniform.object_buffer, ObjectData::SIZE * idx as u64, bytemuck::cast_slice(&[ObjectData { xpos: ship_pos.x, @@ -84,7 +84,7 @@ impl GPUState { // Push this object's instance let anim_state = s.ship.get_anim_state(); - self.state.push_object_buffer(ObjectInstance { + g.state.push_object_buffer(ObjectInstance { texture_index: anim_state.texture_index(), texture_fade: anim_state.fade, object_index: idx as u32, @@ -101,9 +101,9 @@ impl GPUState { is_flying } { for (engine_point, anim) in s.ship.iter_engine_anim() { - self.state.queue.write_buffer( - &self.state.global_uniform.object_buffer, - ObjectData::SIZE * self.state.get_object_counter() as u64, + g.state.queue.write_buffer( + &g.state.global_uniform.object_buffer, + ObjectData::SIZE * g.state.get_object_counter() as u64, bytemuck::cast_slice(&[ObjectData { // Note that we adjust the y-coordinate for half-height, // not the x-coordinate, even though our ships point east @@ -124,10 +124,10 @@ impl GPUState { ); let anim_state = anim.get_texture_idx(); - self.state.push_object_buffer(ObjectInstance { + g.state.push_object_buffer(ObjectInstance { texture_index: anim_state.texture_index(), texture_fade: anim_state.fade, - object_index: self.state.get_object_counter() as u32, + object_index: g.state.get_object_counter() as u32, color: [1.0, 1.0, 1.0, 1.0], }); } @@ -135,8 +135,8 @@ impl GPUState { } } - pub(super) fn phys_push_projectiles( - &mut self, + pub(super) fn push_projectiles( + g: &mut GPUState, input: &RenderInput, // NE and SW corners of screen screen_clip: (Point2, Point2), @@ -169,10 +169,10 @@ impl GPUState { continue; } - let idx = self.state.get_object_counter(); + let idx = g.state.get_object_counter(); // Write this object's location data - self.state.queue.write_buffer( - &self.state.global_uniform.object_buffer, + g.state.queue.write_buffer( + &g.state.global_uniform.object_buffer, ObjectData::SIZE * idx as u64, bytemuck::cast_slice(&[ObjectData { xpos: proj_pos.x, @@ -187,7 +187,7 @@ impl GPUState { ); let anim_state = p.projectile.get_anim_state(); - self.state.push_object_buffer(ObjectInstance { + g.state.push_object_buffer(ObjectInstance { texture_index: anim_state.texture_index(), texture_fade: anim_state.fade, object_index: idx as u32, @@ -196,8 +196,8 @@ impl GPUState { } } - pub(super) fn phys_push_system( - &mut self, + pub(super) fn push_system( + g: &mut GPUState, input: &RenderInput, // NE and SW corners of screen screen_clip: (Point2, Point2), @@ -224,10 +224,10 @@ impl GPUState { continue; } - let idx = self.state.get_object_counter(); + let idx = g.state.get_object_counter(); // Write this object's location data - self.state.queue.write_buffer( - &self.state.global_uniform.object_buffer, + g.state.queue.write_buffer( + &g.state.global_uniform.object_buffer, ObjectData::SIZE * idx as u64, bytemuck::cast_slice(&[ObjectData { xpos: o.pos.x, @@ -245,7 +245,7 @@ impl GPUState { let texture_a = sprite.get_first_frame(); // ANIMATE // Push this object's instance - self.state.push_object_buffer(ObjectInstance { + g.state.push_object_buffer(ObjectInstance { texture_index: [texture_a, texture_a], texture_fade: 1.0, object_index: idx as u32, @@ -254,8 +254,8 @@ impl GPUState { } } - pub(super) fn phys_push_effects( - &mut self, + pub(super) fn push_effects( + g: &mut GPUState, input: &RenderInput, // NE and SW corners of screen screen_clip: (Point2, Point2), @@ -292,10 +292,10 @@ impl GPUState { continue; } - let idx = self.state.get_object_counter(); + let idx = g.state.get_object_counter(); // Write this object's location data - self.state.queue.write_buffer( - &self.state.global_uniform.object_buffer, + g.state.queue.write_buffer( + &g.state.global_uniform.object_buffer, ObjectData::SIZE * idx as u64, bytemuck::cast_slice(&[ObjectData { xpos: pos.x, @@ -310,7 +310,7 @@ impl GPUState { ); let anim_state = p.effect.anim.get_texture_idx(); - self.state.push_object_buffer(ObjectInstance { + g.state.push_object_buffer(ObjectInstance { texture_index: anim_state.texture_index(), texture_fade: anim_state.fade, object_index: idx as u32, diff --git a/crates/render/src/renderscene/system/system.rs b/crates/render/src/renderscene/system/system.rs new file mode 100644 index 0000000..d3cea6e --- /dev/null +++ b/crates/render/src/renderscene/system/system.rs @@ -0,0 +1,144 @@ +use anyhow::Result; +use glyphon::Resolution; +use nalgebra::Point2; +use std::iter; + +use super::super::RenderScene; +use crate::vertexbuffer::consts::SPRITE_INDICES; + +pub struct SystemScene {} + +impl RenderScene for SystemScene { + fn render(g: &mut crate::GPUState, input: &crate::RenderInput) -> Result<()> { + let output = g.surface.get_current_texture()?; + let view = output.texture.create_view(&Default::default()); + + let mut encoder = g + .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(-g.state.window_aspect, 1.0) * input.camera_zoom; + let clip_sw = Point2::new(g.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::push_system(g, &input, (clip_ne, clip_sw)); + Self::push_ships(g, &input, (clip_ne, clip_sw)); + Self::push_projectiles(g, &input, (clip_ne, clip_sw)); + Self::push_effects(g, &input, (clip_ne, clip_sw)); + g.ui.draw(&input, &mut g.state); + + // These should match the indices in each shader, + // and should each have a corresponding bind group layout. + render_pass.set_bind_group(0, &g.texture_array.bind_group, &[]); + render_pass.set_bind_group(1, &g.state.global_uniform.bind_group, &[]); + + // Starfield pipeline + g.state + .vertex_buffers + .get_starfield() + .set_in_pass(&mut render_pass); + render_pass.set_pipeline(&g.starfield_pipeline); + render_pass.draw_indexed( + 0..SPRITE_INDICES.len() as u32, + 0, + 0..g.state.get_starfield_counter(), + ); + + // Sprite pipeline + g.state + .vertex_buffers + .get_object() + .set_in_pass(&mut render_pass); + render_pass.set_pipeline(&g.object_pipeline); + render_pass.draw_indexed( + 0..SPRITE_INDICES.len() as u32, + 0, + 0..g.state.get_object_counter(), + ); + + // Ui pipeline + g.state + .vertex_buffers + .get_ui() + .set_in_pass(&mut render_pass); + render_pass.set_pipeline(&g.ui_pipeline); + render_pass.draw_indexed( + 0..SPRITE_INDICES.len() as u32, + 0, + 0..g.state.get_ui_counter(), + ); + + // Radial progress bars + // TODO: do we need to do this every time? + g.state + .vertex_buffers + .get_radialbar() + .set_in_pass(&mut render_pass); + render_pass.set_pipeline(&g.radialbar_pipeline); + render_pass.draw_indexed( + 0..SPRITE_INDICES.len() as u32, + 0, + 0..g.state.get_radialbar_counter(), + ); + + let textareas = g.ui.get_textareas(input, &g.state); + g.state + .text_renderer + .prepare( + &g.device, + &g.state.queue, + &mut g.state.text_font_system, + &mut g.state.text_atlas, + Resolution { + width: g.state.window_size.width, + height: g.state.window_size.height, + }, + textareas, + &mut g.state.text_cache, + ) + .unwrap(); + + g.state + .text_renderer + .render(&g.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); + + g.state.queue.submit(iter::once(encoder.finish())); + output.present(); + + return Ok(()); + } +} diff --git a/crates/render/src/ui/manager.rs b/crates/render/src/ui/manager.rs index 1851a9b..7dce037 100644 --- a/crates/render/src/ui/manager.rs +++ b/crates/render/src/ui/manager.rs @@ -1,11 +1,10 @@ use std::fmt::Debug; use galactica_content::Content; -use galactica_system::{data::ShipState, phys::PhysSimShipHandle}; use glyphon::TextArea; -use log::info; +use log::debug; -use super::scenes::{FlyingScene, LandedScene, OutfitterScene}; +use super::scenes::{UiFlyingScene, UiLandedScene, UiOutfitterScene}; use crate::{RenderInput, RenderState}; /// Output from a ui scene step @@ -25,6 +24,7 @@ where /// Handles clicks, keys, etc. fn step(&mut self, input: &RenderInput, state: &mut RenderState) -> UiSceneStepResult; + /// Add all textareas in this scene to `v` fn get_textareas( &'this self, v: &mut Vec>, @@ -33,10 +33,10 @@ where ); } -pub(super) enum UiScenes { - Landed(LandedScene), - Flying(FlyingScene), - Outfitter(OutfitterScene), +pub(crate) enum UiScenes { + Landed(UiLandedScene), + Flying(UiFlyingScene), + Outfitter(UiOutfitterScene), } impl Debug for UiScenes { @@ -49,6 +49,7 @@ impl Debug for UiScenes { } } +/* impl UiScenes { fn is_flying(&self) -> bool { match self { @@ -71,6 +72,7 @@ impl UiScenes { } } } +*/ impl<'a> UiScene<'a> for UiScenes { fn draw(&mut self, input: &RenderInput, state: &mut RenderState) { @@ -110,45 +112,23 @@ pub struct UiManager { impl UiManager { pub fn new(ct: &Content, state: &mut RenderState) -> Self { Self { - current_scene: UiScenes::Flying(FlyingScene::new(ct, state)), + current_scene: UiScenes::Flying(UiFlyingScene::new(ct, state)), } } - // TODO: remove this. - pub fn update_state(&mut self, input: &RenderInput, state: &mut RenderState) { - let ship_handle = input.player.ship.unwrap(); - let ship = &input - .phys_img - .get_ship(&PhysSimShipHandle(ship_handle)) - .unwrap() - .ship; - - match ship.get_data().get_state() { - ShipState::Collapsing - | ShipState::Dead - | ShipState::Flying { .. } - | ShipState::Landing { .. } - | ShipState::UnLanding { .. } => { - if !self.current_scene.is_flying() { - self.current_scene = UiScenes::Flying(FlyingScene::new(input.ct, state)); - } - } - - ShipState::Landed { .. } => { - if !self.current_scene.is_landed() && !self.current_scene.is_outfitter() { - self.current_scene = UiScenes::Landed(LandedScene::new(input.ct, state)) - } - } - } + /// Change the current scene + pub fn set_scene(&mut self, scene: UiScenes) { + debug!("switching to {:?}", scene); + self.current_scene = scene; } /// Draw all ui elements pub fn draw(&mut self, input: &RenderInput, state: &mut RenderState) { loop { let r = self.current_scene.step(input, state); - if let Some(new_state) = r.new_scene { - info!("switching to {:?}", new_state); - self.current_scene = new_state; + if let Some(new_scene) = r.new_scene { + debug!("{:?} changed scene", self.current_scene); + self.set_scene(new_scene) } else { break; } diff --git a/crates/render/src/ui/mod.rs b/crates/render/src/ui/mod.rs index cdb57a0..1ca2faf 100644 --- a/crates/render/src/ui/mod.rs +++ b/crates/render/src/ui/mod.rs @@ -1,5 +1,6 @@ mod manager; -mod scenes; +pub(crate) mod scenes; mod util; pub use manager::UiManager; +pub(crate) use manager::UiScenes; diff --git a/crates/render/src/ui/scenes/flying/mod.rs b/crates/render/src/ui/scenes/flying/mod.rs index 1cf7e04..56fcbc4 100644 --- a/crates/render/src/ui/scenes/flying/mod.rs +++ b/crates/render/src/ui/scenes/flying/mod.rs @@ -3,4 +3,4 @@ mod radar; mod scene; mod status; -pub use scene::FlyingScene; +pub use scene::UiFlyingScene; diff --git a/crates/render/src/ui/scenes/flying/scene.rs b/crates/render/src/ui/scenes/flying/scene.rs index 18a4475..b552671 100644 --- a/crates/render/src/ui/scenes/flying/scene.rs +++ b/crates/render/src/ui/scenes/flying/scene.rs @@ -7,13 +7,13 @@ use crate::{ RenderInput, RenderState, }; -pub struct FlyingScene { +pub struct UiFlyingScene { radar: Radar, status: Status, fps: FpsIndicator, } -impl FlyingScene { +impl UiFlyingScene { pub fn new(_ct: &Content, state: &mut RenderState) -> Self { Self { radar: Radar::new(), @@ -23,7 +23,7 @@ impl FlyingScene { } } -impl<'this> UiScene<'this> for FlyingScene { +impl<'this> UiScene<'this> for UiFlyingScene { fn step(&mut self, input: &RenderInput, state: &mut RenderState) -> UiSceneStepResult { self.fps.update(input, state); return UiSceneStepResult { new_scene: None }; diff --git a/crates/render/src/ui/scenes/landed.rs b/crates/render/src/ui/scenes/landed.rs index 0518741..c672a4c 100644 --- a/crates/render/src/ui/scenes/landed.rs +++ b/crates/render/src/ui/scenes/landed.rs @@ -11,9 +11,9 @@ use crate::{ RenderInput, RenderState, }; -use super::OutfitterScene; +use super::UiOutfitterScene; -pub struct LandedScene { +pub struct UiLandedScene { // UI elements description: UiTextArea, title: UiTextArea, @@ -30,7 +30,7 @@ pub struct LandedScene { leftclick_down: bool, } -impl LandedScene { +impl UiLandedScene { pub fn new(ct: &Content, state: &mut RenderState) -> Self { let frame = UiSprite::from(ct, &ct.get_ui().landed_frame); let button = UiSprite::from(ct, &ct.get_ui().landed_button); @@ -103,7 +103,7 @@ impl LandedScene { } } -impl<'this> UiScene<'this> for LandedScene { +impl<'this> UiScene<'this> for UiLandedScene { fn step(&mut self, input: &RenderInput, state: &mut RenderState) -> UiSceneStepResult { let frame_rect = Some(self.frame.get_rect(input)); self.button.step(input, state, frame_rect); @@ -114,7 +114,7 @@ impl<'this> UiScene<'this> for LandedScene { if input.player.input.pressed_leftclick() && !self.leftclick_down { self.leftclick_down = true; if self.button.contains_mouse(input, state, frame_rect) { - new_scene = Some(UiScenes::Outfitter(OutfitterScene::new(input.ct, state))); + new_scene = Some(UiScenes::Outfitter(UiOutfitterScene::new(input.ct, state))); } } else if !input.player.input.pressed_leftclick() { self.leftclick_down = false; diff --git a/crates/render/src/ui/scenes/mod.rs b/crates/render/src/ui/scenes/mod.rs index 188c729..1990e6b 100644 --- a/crates/render/src/ui/scenes/mod.rs +++ b/crates/render/src/ui/scenes/mod.rs @@ -2,6 +2,6 @@ mod flying; mod landed; mod outfitter; -pub use flying::FlyingScene; -pub use landed::LandedScene; -pub use outfitter::OutfitterScene; +pub use flying::UiFlyingScene; +pub use landed::UiLandedScene; +pub use outfitter::UiOutfitterScene; diff --git a/crates/render/src/ui/scenes/outfitter.rs b/crates/render/src/ui/scenes/outfitter.rs index 44aeb01..ffc3898 100644 --- a/crates/render/src/ui/scenes/outfitter.rs +++ b/crates/render/src/ui/scenes/outfitter.rs @@ -11,9 +11,9 @@ use crate::{ RenderInput, RenderState, }; -use super::LandedScene; +use super::UiLandedScene; -pub struct OutfitterScene { +pub struct UiOutfitterScene { // UI elements description: UiTextArea, landscape: UiSprite, @@ -28,7 +28,7 @@ pub struct OutfitterScene { leftclick_down: bool, } -impl OutfitterScene { +impl UiOutfitterScene { pub fn new(ct: &Content, state: &mut RenderState) -> Self { let button = UiSprite::new( ct, @@ -88,7 +88,7 @@ impl OutfitterScene { } } -impl<'this> UiScene<'this> for OutfitterScene { +impl<'this> UiScene<'this> for UiOutfitterScene { fn step(&mut self, input: &RenderInput, state: &mut RenderState) -> UiSceneStepResult { self.button.step(input, state, None); self.landscape.step(input, state, None); @@ -97,7 +97,7 @@ impl<'this> UiScene<'this> for OutfitterScene { if input.player.input.pressed_leftclick() && !self.leftclick_down { self.leftclick_down = true; if self.button.contains_mouse(input, state, None) { - new_scene = Some(UiScenes::Landed(LandedScene::new(input.ct, state))); + new_scene = Some(UiScenes::Landed(UiLandedScene::new(input.ct, state))); } } else if !input.player.input.pressed_leftclick() { self.leftclick_down = false;