Compare commits

..

No commits in common. "14722967665246754f9ee73447b82fd16da15975" and "25661817a45e2cabcad6de0deab0e7985c0f2e9d" have entirely different histories.

18 changed files with 479 additions and 520 deletions

View File

@ -178,7 +178,6 @@
- All angle adjustments happen in content & shaders - All angle adjustments happen in content & shaders
- Reserved texture: index zero - Reserved texture: index zero
- Errors: lowercase, no punctuation - Errors: lowercase, no punctuation
- When to use what logging level
## Ideas ## Ideas

View File

@ -4,7 +4,7 @@ use anyhow::{bail, Result};
use clap::Parser; use clap::Parser;
use galactica_content::{Content, SystemHandle}; use galactica_content::{Content, SystemHandle};
use galactica_playeragent::{PlayerAgent, PlayerStatus}; use galactica_playeragent::{PlayerAgent, PlayerStatus};
use galactica_render::{RenderInput, RenderScenes}; use galactica_render::RenderInput;
use galactica_system::{ use galactica_system::{
data::ShipState, data::ShipState,
phys::{PhysImage, PhysSimShipHandle}, phys::{PhysImage, PhysSimShipHandle},
@ -120,11 +120,7 @@ fn try_main() -> Result<()> {
let event_loop = EventLoop::new(); let event_loop = EventLoop::new();
let window = WindowBuilder::new().build(&event_loop).unwrap(); let window = WindowBuilder::new().build(&event_loop).unwrap();
let mut gpu = pollster::block_on(galactica_render::GPUState::new( let mut gpu = pollster::block_on(galactica_render::GPUState::new(window, &content))?;
window,
&content,
RenderScenes::System,
))?;
gpu.init(&content); gpu.init(&content);
// TODO: don't clone content // TODO: don't clone content
@ -138,7 +134,6 @@ fn try_main() -> Result<()> {
let mut phys_img = PhysImage::new(); let mut phys_img = PhysImage::new();
let mut last_run = Instant::now(); let mut last_run = Instant::now();
let mut was_landed = false;
event_loop.run(move |event, _, control_flow| { event_loop.run(move |event, _, control_flow| {
match event { match event {
@ -179,21 +174,9 @@ fn try_main() -> Result<()> {
ShipState::Landing { .. } ShipState::Landing { .. }
| ShipState::UnLanding { .. } | ShipState::UnLanding { .. }
| ShipState::Collapsing { .. } | ShipState::Collapsing { .. }
| ShipState::Flying { .. } => { | ShipState::Flying { .. } => Some(*o.rigidbody.translation()),
if was_landed {
was_landed = false;
gpu.set_scene(&content, RenderScenes::System);
}
Some(*o.rigidbody.translation())
}
ShipState::Landed { target } => { ShipState::Landed { target } => {
if !was_landed {
was_landed = true;
gpu.set_scene(&content, RenderScenes::Landed);
}
let b = content.get_system_object(*target); let b = content.get_system_object(*target);
Some(Vector2::new(b.pos.x, b.pos.y)) Some(Vector2::new(b.pos.x, b.pos.y))
} }

View File

@ -0,0 +1,64 @@
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);
}
}

View File

@ -1,48 +1,16 @@
use anyhow::Result; use anyhow::Result;
use bytemuck;
use galactica_content::Content; use galactica_content::Content;
use glyphon::{FontSystem, SwashCache, TextAtlas, TextRenderer}; use glyphon::{FontSystem, SwashCache, TextAtlas, TextRenderer};
use log::debug;
use wgpu;
use winit;
use crate::{ use crate::{
globaluniform::{GlobalDataContent, GlobalUniform}, globaluniform::GlobalUniform, pipeline::PipelineBuilder, shaderprocessor::preprocess_shader,
pipeline::PipelineBuilder, starfield::Starfield, texturearray::TextureArray, ui::UiManager, GPUState, RenderState,
renderscene::{LandedScene, RenderScene, SystemScene}, VertexBuffers,
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 { impl GPUState {
/// Make a new GPUState that draws on `window` /// Make a new GPUState that draws on `window`
pub async fn new( pub async fn new(window: winit::window::Window, ct: &Content) -> Result<Self> {
window: winit::window::Window,
ct: &Content,
scene: RenderScenes,
) -> Result<Self> {
let window_size = window.inner_size(); let window_size = window.inner_size();
let window_aspect = window_size.width as f32 / window_size.height as f32; let window_aspect = window_size.width as f32 / window_size.height as f32;
@ -221,11 +189,14 @@ impl GPUState {
let mut state = RenderState { let mut state = RenderState {
queue, queue,
window, window,
window_size, window_size,
window_aspect, window_aspect,
global_uniform, global_uniform,
vertex_buffers, vertex_buffers,
text_atlas, text_atlas,
text_cache, text_cache,
text_font_system, text_font_system,
@ -243,90 +214,8 @@ impl GPUState {
starfield_pipeline, starfield_pipeline,
ui_pipeline, ui_pipeline,
radialbar_pipeline, radialbar_pipeline,
scene,
state, 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(());
}
}

View File

@ -1,3 +1,5 @@
//! GPUState routines for drawing objects in a system
use bytemuck; use bytemuck;
use galactica_system::data::ShipState; use galactica_system::data::ShipState;
use galactica_util::to_radians; use galactica_util::to_radians;
@ -7,11 +9,9 @@ use crate::{
globaluniform::ObjectData, vertexbuffer::types::ObjectInstance, GPUState, RenderInput, globaluniform::ObjectData, vertexbuffer::types::ObjectInstance, GPUState, RenderInput,
}; };
use super::SystemScene; impl GPUState {
pub(super) fn phys_push_ships(
impl SystemScene { &mut self,
pub(super) fn push_ships(
g: &mut GPUState,
input: &RenderInput, input: &RenderInput,
// NE and SW corners of screen // NE and SW corners of screen
screen_clip: (Point2<f32>, Point2<f32>), screen_clip: (Point2<f32>, Point2<f32>),
@ -65,10 +65,10 @@ impl SystemScene {
continue; continue;
} }
let idx = g.state.get_object_counter(); let idx = self.state.get_object_counter();
// Write this object's location data // Write this object's location data
g.state.queue.write_buffer( self.state.queue.write_buffer(
&g.state.global_uniform.object_buffer, &self.state.global_uniform.object_buffer,
ObjectData::SIZE * idx as u64, ObjectData::SIZE * idx as u64,
bytemuck::cast_slice(&[ObjectData { bytemuck::cast_slice(&[ObjectData {
xpos: ship_pos.x, xpos: ship_pos.x,
@ -84,7 +84,7 @@ impl SystemScene {
// Push this object's instance // Push this object's instance
let anim_state = s.ship.get_anim_state(); let anim_state = s.ship.get_anim_state();
g.state.push_object_buffer(ObjectInstance { self.state.push_object_buffer(ObjectInstance {
texture_index: anim_state.texture_index(), texture_index: anim_state.texture_index(),
texture_fade: anim_state.fade, texture_fade: anim_state.fade,
object_index: idx as u32, object_index: idx as u32,
@ -101,9 +101,9 @@ impl SystemScene {
is_flying is_flying
} { } {
for (engine_point, anim) in s.ship.iter_engine_anim() { for (engine_point, anim) in s.ship.iter_engine_anim() {
g.state.queue.write_buffer( self.state.queue.write_buffer(
&g.state.global_uniform.object_buffer, &self.state.global_uniform.object_buffer,
ObjectData::SIZE * g.state.get_object_counter() as u64, ObjectData::SIZE * self.state.get_object_counter() as u64,
bytemuck::cast_slice(&[ObjectData { bytemuck::cast_slice(&[ObjectData {
// Note that we adjust the y-coordinate for half-height, // Note that we adjust the y-coordinate for half-height,
// not the x-coordinate, even though our ships point east // not the x-coordinate, even though our ships point east
@ -124,10 +124,10 @@ impl SystemScene {
); );
let anim_state = anim.get_texture_idx(); let anim_state = anim.get_texture_idx();
g.state.push_object_buffer(ObjectInstance { self.state.push_object_buffer(ObjectInstance {
texture_index: anim_state.texture_index(), texture_index: anim_state.texture_index(),
texture_fade: anim_state.fade, texture_fade: anim_state.fade,
object_index: g.state.get_object_counter() as u32, object_index: self.state.get_object_counter() as u32,
color: [1.0, 1.0, 1.0, 1.0], color: [1.0, 1.0, 1.0, 1.0],
}); });
} }
@ -135,8 +135,8 @@ impl SystemScene {
} }
} }
pub(super) fn push_projectiles( pub(super) fn phys_push_projectiles(
g: &mut GPUState, &mut self,
input: &RenderInput, input: &RenderInput,
// NE and SW corners of screen // NE and SW corners of screen
screen_clip: (Point2<f32>, Point2<f32>), screen_clip: (Point2<f32>, Point2<f32>),
@ -169,10 +169,10 @@ impl SystemScene {
continue; continue;
} }
let idx = g.state.get_object_counter(); let idx = self.state.get_object_counter();
// Write this object's location data // Write this object's location data
g.state.queue.write_buffer( self.state.queue.write_buffer(
&g.state.global_uniform.object_buffer, &self.state.global_uniform.object_buffer,
ObjectData::SIZE * idx as u64, ObjectData::SIZE * idx as u64,
bytemuck::cast_slice(&[ObjectData { bytemuck::cast_slice(&[ObjectData {
xpos: proj_pos.x, xpos: proj_pos.x,
@ -187,7 +187,7 @@ impl SystemScene {
); );
let anim_state = p.projectile.get_anim_state(); let anim_state = p.projectile.get_anim_state();
g.state.push_object_buffer(ObjectInstance { self.state.push_object_buffer(ObjectInstance {
texture_index: anim_state.texture_index(), texture_index: anim_state.texture_index(),
texture_fade: anim_state.fade, texture_fade: anim_state.fade,
object_index: idx as u32, object_index: idx as u32,
@ -196,8 +196,8 @@ impl SystemScene {
} }
} }
pub(super) fn push_system( pub(super) fn phys_push_system(
g: &mut GPUState, &mut self,
input: &RenderInput, input: &RenderInput,
// NE and SW corners of screen // NE and SW corners of screen
screen_clip: (Point2<f32>, Point2<f32>), screen_clip: (Point2<f32>, Point2<f32>),
@ -224,10 +224,10 @@ impl SystemScene {
continue; continue;
} }
let idx = g.state.get_object_counter(); let idx = self.state.get_object_counter();
// Write this object's location data // Write this object's location data
g.state.queue.write_buffer( self.state.queue.write_buffer(
&g.state.global_uniform.object_buffer, &self.state.global_uniform.object_buffer,
ObjectData::SIZE * idx as u64, ObjectData::SIZE * idx as u64,
bytemuck::cast_slice(&[ObjectData { bytemuck::cast_slice(&[ObjectData {
xpos: o.pos.x, xpos: o.pos.x,
@ -245,7 +245,7 @@ impl SystemScene {
let texture_a = sprite.get_first_frame(); // ANIMATE let texture_a = sprite.get_first_frame(); // ANIMATE
// Push this object's instance // Push this object's instance
g.state.push_object_buffer(ObjectInstance { self.state.push_object_buffer(ObjectInstance {
texture_index: [texture_a, texture_a], texture_index: [texture_a, texture_a],
texture_fade: 1.0, texture_fade: 1.0,
object_index: idx as u32, object_index: idx as u32,
@ -254,8 +254,8 @@ impl SystemScene {
} }
} }
pub(super) fn push_effects( pub(super) fn phys_push_effects(
g: &mut GPUState, &mut self,
input: &RenderInput, input: &RenderInput,
// NE and SW corners of screen // NE and SW corners of screen
screen_clip: (Point2<f32>, Point2<f32>), screen_clip: (Point2<f32>, Point2<f32>),
@ -292,10 +292,10 @@ impl SystemScene {
continue; continue;
} }
let idx = g.state.get_object_counter(); let idx = self.state.get_object_counter();
// Write this object's location data // Write this object's location data
g.state.queue.write_buffer( self.state.queue.write_buffer(
&g.state.global_uniform.object_buffer, &self.state.global_uniform.object_buffer,
ObjectData::SIZE * idx as u64, ObjectData::SIZE * idx as u64,
bytemuck::cast_slice(&[ObjectData { bytemuck::cast_slice(&[ObjectData {
xpos: pos.x, xpos: pos.x,
@ -310,7 +310,7 @@ impl SystemScene {
); );
let anim_state = p.effect.anim.get_texture_idx(); let anim_state = p.effect.anim.get_texture_idx();
g.state.push_object_buffer(ObjectInstance { self.state.push_object_buffer(ObjectInstance {
texture_index: anim_state.texture_index(), texture_index: anim_state.texture_index(),
texture_fade: anim_state.fade, texture_fade: anim_state.fade,
object_index: idx as u32, object_index: idx as u32,

View File

@ -0,0 +1,303 @@
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(());
}
}

View File

@ -12,7 +12,6 @@ mod gpustate;
mod pipeline; mod pipeline;
mod positionanchor; mod positionanchor;
mod renderinput; mod renderinput;
mod renderscene;
mod renderstate; mod renderstate;
mod shaderprocessor; mod shaderprocessor;
mod starfield; mod starfield;
@ -20,11 +19,11 @@ mod texturearray;
mod ui; mod ui;
mod vertexbuffer; mod vertexbuffer;
use renderstate::*;
pub use gpustate::GPUState; pub use gpustate::GPUState;
pub use positionanchor::PositionAnchor; pub use positionanchor::PositionAnchor;
pub use renderinput::RenderInput; pub use renderinput::RenderInput;
pub use renderscene::RenderScenes;
use renderstate::*;
use nalgebra::Matrix4; use nalgebra::Matrix4;

View File

@ -1,117 +0,0 @@
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(());
}
}

View File

@ -1,32 +0,0 @@
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"),
}
}
}

View File

@ -1,4 +0,0 @@
mod phys;
mod system;
pub use system::SystemScene;

View File

@ -1,144 +0,0 @@
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(());
}
}

View File

@ -1,10 +1,11 @@
use std::fmt::Debug; use std::fmt::Debug;
use galactica_content::Content; use galactica_content::Content;
use galactica_system::{data::ShipState, phys::PhysSimShipHandle};
use glyphon::TextArea; use glyphon::TextArea;
use log::debug; use log::info;
use super::scenes::{UiFlyingScene, UiLandedScene, UiOutfitterScene}; use super::scenes::{FlyingScene, LandedScene, OutfitterScene};
use crate::{RenderInput, RenderState}; use crate::{RenderInput, RenderState};
/// Output from a ui scene step /// Output from a ui scene step
@ -24,7 +25,6 @@ where
/// Handles clicks, keys, etc. /// Handles clicks, keys, etc.
fn step(&mut self, input: &RenderInput, state: &mut RenderState) -> UiSceneStepResult; fn step(&mut self, input: &RenderInput, state: &mut RenderState) -> UiSceneStepResult;
/// Add all textareas in this scene to `v`
fn get_textareas( fn get_textareas(
&'this self, &'this self,
v: &mut Vec<TextArea<'this>>, v: &mut Vec<TextArea<'this>>,
@ -33,10 +33,10 @@ where
); );
} }
pub(crate) enum UiScenes { pub(super) enum UiScenes {
Landed(UiLandedScene), Landed(LandedScene),
Flying(UiFlyingScene), Flying(FlyingScene),
Outfitter(UiOutfitterScene), Outfitter(OutfitterScene),
} }
impl Debug for UiScenes { impl Debug for UiScenes {
@ -49,7 +49,6 @@ impl Debug for UiScenes {
} }
} }
/*
impl UiScenes { impl UiScenes {
fn is_flying(&self) -> bool { fn is_flying(&self) -> bool {
match self { match self {
@ -72,7 +71,6 @@ impl UiScenes {
} }
} }
} }
*/
impl<'a> UiScene<'a> for UiScenes { impl<'a> UiScene<'a> for UiScenes {
fn draw(&mut self, input: &RenderInput, state: &mut RenderState) { fn draw(&mut self, input: &RenderInput, state: &mut RenderState) {
@ -112,23 +110,45 @@ pub struct UiManager {
impl UiManager { impl UiManager {
pub fn new(ct: &Content, state: &mut RenderState) -> Self { pub fn new(ct: &Content, state: &mut RenderState) -> Self {
Self { Self {
current_scene: UiScenes::Flying(UiFlyingScene::new(ct, state)), current_scene: UiScenes::Flying(FlyingScene::new(ct, state)),
} }
} }
/// Change the current scene // TODO: remove this.
pub fn set_scene(&mut self, scene: UiScenes) { pub fn update_state(&mut self, input: &RenderInput, state: &mut RenderState) {
debug!("switching to {:?}", scene); let ship_handle = input.player.ship.unwrap();
self.current_scene = scene; 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))
}
}
}
} }
/// Draw all ui elements /// Draw all ui elements
pub fn draw(&mut self, input: &RenderInput, state: &mut RenderState) { pub fn draw(&mut self, input: &RenderInput, state: &mut RenderState) {
loop { loop {
let r = self.current_scene.step(input, state); let r = self.current_scene.step(input, state);
if let Some(new_scene) = r.new_scene { if let Some(new_state) = r.new_scene {
debug!("{:?} changed scene", self.current_scene); info!("switching to {:?}", new_state);
self.set_scene(new_scene) self.current_scene = new_state;
} else { } else {
break; break;
} }

View File

@ -1,6 +1,5 @@
mod manager; mod manager;
pub(crate) mod scenes; mod scenes;
mod util; mod util;
pub use manager::UiManager; pub use manager::UiManager;
pub(crate) use manager::UiScenes;

View File

@ -3,4 +3,4 @@ mod radar;
mod scene; mod scene;
mod status; mod status;
pub use scene::UiFlyingScene; pub use scene::FlyingScene;

View File

@ -7,13 +7,13 @@ use crate::{
RenderInput, RenderState, RenderInput, RenderState,
}; };
pub struct UiFlyingScene { pub struct FlyingScene {
radar: Radar, radar: Radar,
status: Status, status: Status,
fps: FpsIndicator, fps: FpsIndicator,
} }
impl UiFlyingScene { impl FlyingScene {
pub fn new(_ct: &Content, state: &mut RenderState) -> Self { pub fn new(_ct: &Content, state: &mut RenderState) -> Self {
Self { Self {
radar: Radar::new(), radar: Radar::new(),
@ -23,7 +23,7 @@ impl UiFlyingScene {
} }
} }
impl<'this> UiScene<'this> for UiFlyingScene { impl<'this> UiScene<'this> for FlyingScene {
fn step(&mut self, input: &RenderInput, state: &mut RenderState) -> UiSceneStepResult { fn step(&mut self, input: &RenderInput, state: &mut RenderState) -> UiSceneStepResult {
self.fps.update(input, state); self.fps.update(input, state);
return UiSceneStepResult { new_scene: None }; return UiSceneStepResult { new_scene: None };

View File

@ -11,9 +11,9 @@ use crate::{
RenderInput, RenderState, RenderInput, RenderState,
}; };
use super::UiOutfitterScene; use super::OutfitterScene;
pub struct UiLandedScene { pub struct LandedScene {
// UI elements // UI elements
description: UiTextArea, description: UiTextArea,
title: UiTextArea, title: UiTextArea,
@ -30,7 +30,7 @@ pub struct UiLandedScene {
leftclick_down: bool, leftclick_down: bool,
} }
impl UiLandedScene { impl LandedScene {
pub fn new(ct: &Content, state: &mut RenderState) -> Self { pub fn new(ct: &Content, state: &mut RenderState) -> Self {
let frame = UiSprite::from(ct, &ct.get_ui().landed_frame); let frame = UiSprite::from(ct, &ct.get_ui().landed_frame);
let button = UiSprite::from(ct, &ct.get_ui().landed_button); let button = UiSprite::from(ct, &ct.get_ui().landed_button);
@ -103,7 +103,7 @@ impl UiLandedScene {
} }
} }
impl<'this> UiScene<'this> for UiLandedScene { impl<'this> UiScene<'this> for LandedScene {
fn step(&mut self, input: &RenderInput, state: &mut RenderState) -> UiSceneStepResult { fn step(&mut self, input: &RenderInput, state: &mut RenderState) -> UiSceneStepResult {
let frame_rect = Some(self.frame.get_rect(input)); let frame_rect = Some(self.frame.get_rect(input));
self.button.step(input, state, frame_rect); self.button.step(input, state, frame_rect);
@ -114,7 +114,7 @@ impl<'this> UiScene<'this> for UiLandedScene {
if input.player.input.pressed_leftclick() && !self.leftclick_down { if input.player.input.pressed_leftclick() && !self.leftclick_down {
self.leftclick_down = true; self.leftclick_down = true;
if self.button.contains_mouse(input, state, frame_rect) { if self.button.contains_mouse(input, state, frame_rect) {
new_scene = Some(UiScenes::Outfitter(UiOutfitterScene::new(input.ct, state))); new_scene = Some(UiScenes::Outfitter(OutfitterScene::new(input.ct, state)));
} }
} else if !input.player.input.pressed_leftclick() { } else if !input.player.input.pressed_leftclick() {
self.leftclick_down = false; self.leftclick_down = false;

View File

@ -2,6 +2,6 @@ mod flying;
mod landed; mod landed;
mod outfitter; mod outfitter;
pub use flying::UiFlyingScene; pub use flying::FlyingScene;
pub use landed::UiLandedScene; pub use landed::LandedScene;
pub use outfitter::UiOutfitterScene; pub use outfitter::OutfitterScene;

View File

@ -11,9 +11,9 @@ use crate::{
RenderInput, RenderState, RenderInput, RenderState,
}; };
use super::UiLandedScene; use super::LandedScene;
pub struct UiOutfitterScene { pub struct OutfitterScene {
// UI elements // UI elements
description: UiTextArea, description: UiTextArea,
landscape: UiSprite, landscape: UiSprite,
@ -28,7 +28,7 @@ pub struct UiOutfitterScene {
leftclick_down: bool, leftclick_down: bool,
} }
impl UiOutfitterScene { impl OutfitterScene {
pub fn new(ct: &Content, state: &mut RenderState) -> Self { pub fn new(ct: &Content, state: &mut RenderState) -> Self {
let button = UiSprite::new( let button = UiSprite::new(
ct, ct,
@ -88,7 +88,7 @@ impl UiOutfitterScene {
} }
} }
impl<'this> UiScene<'this> for UiOutfitterScene { impl<'this> UiScene<'this> for OutfitterScene {
fn step(&mut self, input: &RenderInput, state: &mut RenderState) -> UiSceneStepResult { fn step(&mut self, input: &RenderInput, state: &mut RenderState) -> UiSceneStepResult {
self.button.step(input, state, None); self.button.step(input, state, None);
self.landscape.step(input, state, None); self.landscape.step(input, state, None);
@ -97,7 +97,7 @@ impl<'this> UiScene<'this> for UiOutfitterScene {
if input.player.input.pressed_leftclick() && !self.leftclick_down { if input.player.input.pressed_leftclick() && !self.leftclick_down {
self.leftclick_down = true; self.leftclick_down = true;
if self.button.contains_mouse(input, state, None) { if self.button.contains_mouse(input, state, None) {
new_scene = Some(UiScenes::Landed(UiLandedScene::new(input.ct, state))); new_scene = Some(UiScenes::Landed(LandedScene::new(input.ct, state)));
} }
} else if !input.player.input.pressed_leftclick() { } else if !input.player.input.pressed_leftclick() {
self.leftclick_down = false; self.leftclick_down = false;