2024-01-10 17:53:27 -08:00
|
|
|
use anyhow::Result;
|
2024-01-27 15:45:37 -08:00
|
|
|
use bytemuck;
|
2024-01-10 17:53:27 -08:00
|
|
|
use galactica_content::Content;
|
2024-02-07 15:58:14 -08:00
|
|
|
use galactica_system::{data::ShipState, phys::PhysSimShipHandle, PlayerDirective};
|
2024-02-03 11:24:17 -08:00
|
|
|
use galactica_util::to_radians;
|
|
|
|
use glyphon::{FontSystem, Resolution, SwashCache, TextAtlas, TextRenderer};
|
2024-02-07 15:58:14 -08:00
|
|
|
use nalgebra::{Point2, Point3, Vector2};
|
2024-02-04 11:45:49 -08:00
|
|
|
use std::{cell::RefCell, iter, rc::Rc, sync::Arc};
|
2024-01-27 15:45:37 -08:00
|
|
|
use wgpu;
|
2024-02-07 15:58:14 -08:00
|
|
|
use winit::{self};
|
2024-01-10 17:53:27 -08:00
|
|
|
|
|
|
|
use crate::{
|
2024-02-03 11:24:17 -08:00
|
|
|
globaluniform::{GlobalDataContent, GlobalUniform, ObjectData},
|
2024-01-27 15:45:37 -08:00
|
|
|
pipeline::PipelineBuilder,
|
|
|
|
shaderprocessor::preprocess_shader,
|
|
|
|
starfield::Starfield,
|
|
|
|
texturearray::TextureArray,
|
2024-02-04 11:45:49 -08:00
|
|
|
ui::UiScriptExecutor,
|
2024-02-03 11:24:17 -08:00
|
|
|
vertexbuffer::{consts::SPRITE_INDICES, types::ObjectInstance},
|
2024-02-07 15:58:14 -08:00
|
|
|
InputEvent, RenderInput, RenderState, VertexBuffers,
|
2024-01-10 17:53:27 -08:00
|
|
|
};
|
|
|
|
|
2024-01-27 15:45:37 -08:00
|
|
|
/// 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,
|
2024-02-04 11:45:49 -08:00
|
|
|
pub(crate) ui: UiScriptExecutor,
|
2024-01-27 15:45:37 -08:00
|
|
|
}
|
|
|
|
|
2024-01-10 17:53:27 -08:00
|
|
|
impl GPUState {
|
|
|
|
/// Make a new GPUState that draws on `window`
|
2024-02-03 21:52:42 -08:00
|
|
|
pub async fn new(window: winit::window::Window, ct: Arc<Content>) -> Result<Self> {
|
2024-01-10 17:53:27 -08:00
|
|
|
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
|
2024-01-27 15:45:37 -08:00
|
|
|
.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();
|
2024-01-10 17:53:27 -08:00
|
|
|
|
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
|
2024-02-02 22:32:03 -08:00
|
|
|
let vertex_buffers = VertexBuffers::new(&device, &ct);
|
2024-01-10 17:53:27 -08:00
|
|
|
|
|
|
|
// Load uniforms
|
|
|
|
let global_uniform = GlobalUniform::new(&device);
|
2024-02-02 22:32:03 -08:00
|
|
|
let texture_array = TextureArray::new(&device, &queue, &ct)?;
|
2024-01-10 17:53:27 -08:00
|
|
|
|
|
|
|
// 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);
|
2024-01-10 22:44:22 -08:00
|
|
|
let mut text_font_system = FontSystem::new_with_locale_and_db(
|
|
|
|
"en-US".to_string(),
|
|
|
|
glyphon::fontdb::Database::new(),
|
|
|
|
);
|
|
|
|
|
2024-02-05 18:29:05 -08:00
|
|
|
for font in &ct.config.font_files {
|
2024-01-10 22:44:22 -08:00
|
|
|
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()
|
2024-02-05 18:29:05 -08:00
|
|
|
.set_sans_serif_family(ct.config.font_sans.clone());
|
2024-01-10 22:44:22 -08:00
|
|
|
text_font_system
|
|
|
|
.db_mut()
|
2024-02-05 18:29:05 -08:00
|
|
|
.set_serif_family(ct.config.font_serif.clone());
|
2024-01-10 22:44:22 -08:00
|
|
|
text_font_system
|
|
|
|
.db_mut()
|
2024-02-05 18:29:05 -08:00
|
|
|
.set_monospace_family(ct.config.font_mono.clone());
|
2024-01-10 22:44:22 -08:00
|
|
|
//text_font_system
|
|
|
|
// .db_mut()
|
|
|
|
// .set_cursive_family(conf.font_cursive.clone());
|
|
|
|
//text_font_system
|
|
|
|
// .db_mut()
|
|
|
|
// .set_fantasy_family(conf.font_fantasy.clone());
|
2024-01-10 17:53:27 -08:00
|
|
|
|
|
|
|
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)
|
2024-01-17 10:17:18 -08:00
|
|
|
.set_vertex_buffer(vertex_buffers.get_object())
|
2024-01-10 17:53:27 -08:00
|
|
|
.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)
|
2024-01-17 10:17:18 -08:00
|
|
|
.set_vertex_buffer(vertex_buffers.get_starfield())
|
2024-01-10 17:53:27 -08:00
|
|
|
.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)
|
2024-01-17 10:17:18 -08:00
|
|
|
.set_vertex_buffer(vertex_buffers.get_ui())
|
2024-01-10 17:53:27 -08:00
|
|
|
.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)
|
2024-01-17 10:17:18 -08:00
|
|
|
.set_vertex_buffer(vertex_buffers.get_radialbar())
|
2024-01-10 17:53:27 -08:00
|
|
|
.set_bind_group_layouts(bind_group_layouts)
|
|
|
|
.build();
|
|
|
|
|
|
|
|
let mut starfield = Starfield::new();
|
2024-02-02 22:32:03 -08:00
|
|
|
starfield.regenerate(&ct);
|
2024-01-10 17:53:27 -08:00
|
|
|
|
2024-02-03 07:47:19 -08:00
|
|
|
let mut state = RenderState {
|
|
|
|
queue,
|
|
|
|
window,
|
|
|
|
window_size,
|
|
|
|
window_aspect,
|
|
|
|
global_uniform,
|
|
|
|
vertex_buffers,
|
2024-02-04 11:45:49 -08:00
|
|
|
text_renderer,
|
2024-02-03 07:47:19 -08:00
|
|
|
text_atlas,
|
|
|
|
text_cache,
|
2024-02-04 11:45:49 -08:00
|
|
|
text_font_system: Rc::new(RefCell::new(text_font_system)),
|
2024-02-03 07:47:19 -08:00
|
|
|
};
|
|
|
|
|
2024-01-10 18:53:19 -08:00
|
|
|
return Ok(Self {
|
2024-02-04 11:45:49 -08:00
|
|
|
ui: UiScriptExecutor::new(ct, &mut state),
|
2024-01-10 17:53:27 -08:00
|
|
|
device,
|
|
|
|
config,
|
|
|
|
surface,
|
|
|
|
starfield,
|
|
|
|
texture_array,
|
|
|
|
object_pipeline,
|
|
|
|
starfield_pipeline,
|
|
|
|
ui_pipeline,
|
|
|
|
radialbar_pipeline,
|
2024-02-03 07:47:19 -08:00
|
|
|
state,
|
2024-01-10 17:53:27 -08:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2024-01-27 15:45:37 -08:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
2024-02-07 15:58:14 -08:00
|
|
|
|
|
|
|
// TODO: this takes a long time. fix!
|
2024-01-27 15:45:37 -08:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2024-02-07 15:58:14 -08:00
|
|
|
/// Handle user input
|
|
|
|
pub fn process_input(
|
|
|
|
&mut self,
|
2024-02-08 20:40:12 -08:00
|
|
|
input: &Arc<RenderInput>,
|
2024-02-07 15:58:14 -08:00
|
|
|
event: InputEvent,
|
|
|
|
) -> Result<PlayerDirective> {
|
|
|
|
self.ui.process_input(&mut self.state, input, event)
|
|
|
|
}
|
|
|
|
|
2024-01-27 15:45:37 -08:00
|
|
|
/// Main render function. Draws sprites on a window.
|
2024-02-08 20:40:12 -08:00
|
|
|
pub fn render(&mut self, input: &Arc<RenderInput>) -> Result<(), wgpu::SurfaceError> {
|
2024-02-07 15:58:14 -08:00
|
|
|
if let Some(ship) = input.player.ship {
|
|
|
|
let o = input.phys_img.get_ship(&PhysSimShipHandle(ship));
|
|
|
|
if let Some(o) = o {
|
|
|
|
match o.ship.get_data().get_state() {
|
|
|
|
ShipState::Landing { .. }
|
|
|
|
| ShipState::UnLanding { .. }
|
|
|
|
| ShipState::Collapsing { .. }
|
|
|
|
| ShipState::Flying { .. } => self
|
|
|
|
.ui
|
|
|
|
.state
|
|
|
|
.borrow_mut()
|
|
|
|
.camera
|
|
|
|
.set_pos(*o.rigidbody.translation()),
|
|
|
|
|
|
|
|
ShipState::Landed { target } => self
|
|
|
|
.ui
|
|
|
|
.state
|
|
|
|
.borrow_mut()
|
|
|
|
.camera
|
|
|
|
.set_pos(Vector2::new(target.pos.x, target.pos.y)),
|
|
|
|
|
|
|
|
ShipState::Dead => {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-27 15:45:37 -08:00
|
|
|
// Update global values
|
|
|
|
self.state.queue.write_buffer(
|
|
|
|
&self.state.global_uniform.data_buffer,
|
|
|
|
0,
|
|
|
|
bytemuck::cast_slice(&[GlobalDataContent {
|
2024-02-07 15:58:14 -08:00
|
|
|
camera_position_x: self.ui.state.borrow().camera.get_pos().x,
|
|
|
|
camera_position_y: self.ui.state.borrow().camera.get_pos().y,
|
|
|
|
camera_zoom: self.ui.state.borrow().camera.get_zoom(),
|
2024-02-05 18:29:05 -08:00
|
|
|
camera_zoom_min: input.ct.config.zoom_min,
|
|
|
|
camera_zoom_max: input.ct.config.zoom_max,
|
2024-01-27 15:45:37 -08:00
|
|
|
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,
|
2024-02-05 18:29:05 -08:00
|
|
|
starfield_sprite: input.ct.config.starfield_texture.into(),
|
|
|
|
starfield_tile_size: input.ct.config.starfield_size,
|
|
|
|
starfield_size_min: input.ct.config.starfield_min_size,
|
|
|
|
starfield_size_max: input.ct.config.starfield_max_size,
|
2024-01-27 15:45:37 -08:00
|
|
|
}]),
|
|
|
|
);
|
|
|
|
|
|
|
|
self.state.frame_reset();
|
|
|
|
|
2024-02-03 11:24:17 -08:00
|
|
|
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,
|
|
|
|
});
|
|
|
|
|
2024-02-04 11:45:49 -08:00
|
|
|
let config = self.ui.get_config();
|
|
|
|
|
|
|
|
if config.show_phys {
|
2024-02-03 11:24:17 -08:00
|
|
|
// Create sprite instances
|
|
|
|
|
|
|
|
// Game coordinates (relative to camera) of ne and sw corners of screen.
|
|
|
|
// Used to skip off-screen sprites.
|
2024-02-07 15:58:14 -08:00
|
|
|
let clip_ne = Point2::new(-self.state.window_aspect, 1.0)
|
|
|
|
* self.ui.state.borrow().camera.get_zoom();
|
|
|
|
let clip_sw = Point2::new(self.state.window_aspect, -1.0)
|
|
|
|
* self.ui.state.borrow().camera.get_zoom();
|
2024-02-03 11:24:17 -08:00
|
|
|
|
|
|
|
// 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(&input, (clip_ne, clip_sw));
|
|
|
|
self.push_ships(&input, (clip_ne, clip_sw));
|
|
|
|
self.push_projectiles(&input, (clip_ne, clip_sw));
|
|
|
|
self.push_effects(&input, (clip_ne, clip_sw));
|
2024-01-27 15:45:37 -08:00
|
|
|
}
|
|
|
|
|
2024-02-08 20:40:12 -08:00
|
|
|
self.ui.draw(&mut self.state, input).unwrap();
|
2024-02-03 11:24:17 -08:00
|
|
|
|
|
|
|
// 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, &[]);
|
|
|
|
|
2024-02-04 11:45:49 -08:00
|
|
|
if config.show_starfield {
|
2024-02-03 11:24:17 -08:00
|
|
|
// 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(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2024-02-04 11:45:49 -08:00
|
|
|
if config.show_phys {
|
2024-02-03 11:24:17 -08:00
|
|
|
// 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(),
|
|
|
|
);
|
|
|
|
|
2024-02-04 11:45:49 -08:00
|
|
|
{
|
|
|
|
self.state
|
|
|
|
.text_renderer
|
|
|
|
.prepare(
|
|
|
|
&self.device,
|
|
|
|
&self.state.queue,
|
|
|
|
&mut self.state.text_font_system.borrow_mut(),
|
|
|
|
&mut self.state.text_atlas,
|
|
|
|
Resolution {
|
|
|
|
width: self.state.window_size.width,
|
|
|
|
height: self.state.window_size.height,
|
|
|
|
},
|
|
|
|
(*self.ui.state)
|
|
|
|
.borrow_mut()
|
2024-02-07 15:58:14 -08:00
|
|
|
.get_textareas(&input, &self.state.window)
|
|
|
|
.iter()
|
|
|
|
.map(|x| x.get_textarea()),
|
2024-02-04 11:45:49 -08:00
|
|
|
&mut self.state.text_cache,
|
|
|
|
)
|
|
|
|
.unwrap();
|
2024-02-03 11:24:17 -08:00
|
|
|
|
2024-02-04 11:45:49 -08:00
|
|
|
self.state
|
|
|
|
.text_renderer
|
|
|
|
.render(&mut self.state.text_atlas, &mut render_pass)
|
|
|
|
.unwrap();
|
|
|
|
}
|
2024-02-03 11:24:17 -08:00
|
|
|
|
|
|
|
// 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();
|
|
|
|
|
2024-01-27 15:45:37 -08:00
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
}
|
2024-02-03 11:24:17 -08:00
|
|
|
|
2024-02-03 12:01:12 -08:00
|
|
|
// Render utilities
|
2024-02-03 11:24:17 -08:00
|
|
|
impl GPUState {
|
|
|
|
fn push_ships(
|
|
|
|
&mut self,
|
|
|
|
input: &RenderInput,
|
|
|
|
// NE and SW corners of screen
|
|
|
|
screen_clip: (Point2<f32>, Point2<f32>),
|
|
|
|
) {
|
|
|
|
for s in input.phys_img.iter_ships() {
|
|
|
|
let ship_pos;
|
|
|
|
let ship_ang;
|
|
|
|
let ship_cnt;
|
|
|
|
match s.ship.get_data().get_state() {
|
|
|
|
ShipState::Dead | ShipState::Landed { .. } => continue,
|
|
|
|
|
|
|
|
ShipState::Collapsing { .. } | ShipState::Flying { .. } => {
|
|
|
|
let r = &s.rigidbody;
|
|
|
|
let pos = *r.translation();
|
|
|
|
ship_pos = Point3::new(pos.x, pos.y, 1.0);
|
|
|
|
let ship_rot = r.rotation();
|
|
|
|
ship_ang = ship_rot.angle();
|
2024-02-05 18:29:05 -08:00
|
|
|
ship_cnt = s.ship.get_data().get_content();
|
2024-02-03 11:24:17 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
ShipState::UnLanding { current_z, .. } | ShipState::Landing { current_z, .. } => {
|
|
|
|
let r = &s.rigidbody;
|
|
|
|
let pos = *r.translation();
|
|
|
|
ship_pos = Point3::new(pos.x, pos.y, *current_z);
|
|
|
|
let ship_rot = r.rotation();
|
|
|
|
ship_ang = ship_rot.angle();
|
2024-02-05 18:29:05 -08:00
|
|
|
ship_cnt = s.ship.get_data().get_content();
|
2024-02-03 11:24:17 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Position adjusted for parallax
|
|
|
|
// TODO: adjust parallax for zoom?
|
|
|
|
// 1.0 is z-coordinate, which is constant for ships
|
2024-02-07 15:58:14 -08:00
|
|
|
let pos: Point2<f32> = (Point2::new(ship_pos.x, ship_pos.y)
|
|
|
|
- self.ui.state.borrow().camera.get_pos())
|
|
|
|
/ ship_pos.z;
|
2024-02-03 11:24:17 -08:00
|
|
|
|
|
|
|
// Game dimensions of this sprite post-scale.
|
|
|
|
// Post-scale width or height, whichever is larger.
|
|
|
|
// This is in game units.
|
|
|
|
//
|
|
|
|
// We take the maximum to account for rotated sprites.
|
2024-02-05 18:29:05 -08:00
|
|
|
let m = (ship_cnt.size / ship_pos.z) * ship_cnt.sprite.aspect.max(1.0);
|
2024-02-03 11:24:17 -08:00
|
|
|
|
|
|
|
// Don't draw sprites that are off the screen
|
|
|
|
if pos.x < screen_clip.0.x - m
|
|
|
|
|| pos.y > screen_clip.0.y + m
|
|
|
|
|| pos.x > screen_clip.1.x + m
|
|
|
|
|| pos.y < screen_clip.1.y - m
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
let idx = self.state.get_object_counter();
|
|
|
|
// Write this object's location data
|
|
|
|
self.state.queue.write_buffer(
|
|
|
|
&self.state.global_uniform.object_buffer,
|
|
|
|
ObjectData::SIZE * idx as u64,
|
|
|
|
bytemuck::cast_slice(&[ObjectData {
|
|
|
|
xpos: ship_pos.x,
|
|
|
|
ypos: ship_pos.y,
|
|
|
|
zpos: ship_pos.z,
|
|
|
|
angle: ship_ang,
|
|
|
|
size: ship_cnt.size,
|
|
|
|
parent: 0,
|
|
|
|
is_child: 0,
|
|
|
|
_padding: Default::default(),
|
|
|
|
}]),
|
|
|
|
);
|
|
|
|
|
|
|
|
// Push this object's instance
|
|
|
|
let anim_state = s.ship.get_anim_state();
|
|
|
|
self.state.push_object_buffer(ObjectInstance {
|
|
|
|
texture_index: anim_state.texture_index(),
|
|
|
|
texture_fade: anim_state.fade,
|
|
|
|
object_index: idx as u32,
|
|
|
|
color: [1.0, 1.0, 1.0, 1.0],
|
|
|
|
});
|
|
|
|
|
|
|
|
if {
|
|
|
|
let is_flying = match s.ship.get_data().get_state() {
|
|
|
|
ShipState::Flying { .. }
|
|
|
|
| ShipState::UnLanding { .. }
|
|
|
|
| ShipState::Landing { .. } => true,
|
|
|
|
_ => false,
|
|
|
|
};
|
|
|
|
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,
|
|
|
|
bytemuck::cast_slice(&[ObjectData {
|
|
|
|
// Note that we adjust the y-coordinate for half-height,
|
|
|
|
// not the x-coordinate, even though our ships point east
|
|
|
|
// at 0 degrees. This is because this is placed pre-rotation,
|
|
|
|
// and the parent rotation adjustment in our object shader
|
|
|
|
// automatically accounts for this.
|
|
|
|
xpos: engine_point.pos.x,
|
|
|
|
ypos: engine_point.pos.y - engine_point.size / 2.0,
|
|
|
|
zpos: 1.0,
|
|
|
|
// We still need an adjustment here, though,
|
|
|
|
// since engine sprites point north (with exhaust towards the south)
|
|
|
|
angle: to_radians(90.0),
|
|
|
|
size: engine_point.size,
|
|
|
|
parent: idx as u32,
|
|
|
|
is_child: 1,
|
|
|
|
_padding: Default::default(),
|
|
|
|
}]),
|
|
|
|
);
|
|
|
|
|
|
|
|
let anim_state = anim.get_texture_idx();
|
|
|
|
self.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,
|
|
|
|
color: [1.0, 1.0, 1.0, 1.0],
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn push_projectiles(
|
|
|
|
&mut self,
|
|
|
|
input: &RenderInput,
|
|
|
|
// NE and SW corners of screen
|
|
|
|
screen_clip: (Point2<f32>, Point2<f32>),
|
|
|
|
) {
|
|
|
|
for p in input.phys_img.iter_projectiles() {
|
|
|
|
let r = &p.rigidbody;
|
|
|
|
let proj_pos = *r.translation();
|
|
|
|
let proj_rot = r.rotation();
|
|
|
|
let proj_ang = proj_rot.angle();
|
|
|
|
let proj_cnt = &p.projectile.content; // TODO: don't clone this?
|
|
|
|
|
|
|
|
// Position adjusted for parallax
|
|
|
|
// TODO: adjust parallax for zoom?
|
|
|
|
// 1.0 is z-coordinate, which is constant for projectiles
|
2024-02-07 15:58:14 -08:00
|
|
|
let pos = (proj_pos - self.ui.state.borrow().camera.get_pos()) / 1.0;
|
2024-02-03 11:24:17 -08:00
|
|
|
|
|
|
|
// Game dimensions of this sprite post-scale.
|
|
|
|
// Post-scale width or height, whichever is larger.
|
|
|
|
// This is in game units.
|
|
|
|
//
|
|
|
|
// We take the maximum to account for rotated sprites.
|
2024-02-05 18:29:05 -08:00
|
|
|
let m = (proj_cnt.size / 1.0) * proj_cnt.sprite.aspect.max(1.0);
|
2024-02-03 11:24:17 -08:00
|
|
|
|
|
|
|
// Don't draw sprites that are off the screen
|
|
|
|
if pos.x < screen_clip.0.x - m
|
|
|
|
|| pos.y > screen_clip.0.y + m
|
|
|
|
|| pos.x > screen_clip.1.x + m
|
|
|
|
|| pos.y < screen_clip.1.y - m
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
let idx = self.state.get_object_counter();
|
|
|
|
// Write this object's location data
|
|
|
|
self.state.queue.write_buffer(
|
|
|
|
&self.state.global_uniform.object_buffer,
|
|
|
|
ObjectData::SIZE * idx as u64,
|
|
|
|
bytemuck::cast_slice(&[ObjectData {
|
|
|
|
xpos: proj_pos.x,
|
|
|
|
ypos: proj_pos.y,
|
|
|
|
zpos: 1.0,
|
|
|
|
angle: proj_ang,
|
|
|
|
size: 0f32.max(proj_cnt.size + p.projectile.size_rng),
|
|
|
|
parent: 0,
|
|
|
|
is_child: 0,
|
|
|
|
_padding: Default::default(),
|
|
|
|
}]),
|
|
|
|
);
|
|
|
|
|
|
|
|
let anim_state = p.projectile.get_anim_state();
|
|
|
|
self.state.push_object_buffer(ObjectInstance {
|
|
|
|
texture_index: anim_state.texture_index(),
|
|
|
|
texture_fade: anim_state.fade,
|
|
|
|
object_index: idx as u32,
|
|
|
|
color: [1.0, 1.0, 1.0, 1.0],
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn push_system(
|
|
|
|
&mut self,
|
|
|
|
input: &RenderInput,
|
|
|
|
// NE and SW corners of screen
|
|
|
|
screen_clip: (Point2<f32>, Point2<f32>),
|
|
|
|
) {
|
2024-02-05 18:29:05 -08:00
|
|
|
// TODO: sort once, give objects state
|
2024-02-03 11:24:17 -08:00
|
|
|
|
2024-02-05 18:29:05 -08:00
|
|
|
// Sort by z-distance. This is important, since these are
|
|
|
|
// rendered in this order. We need far objects to be behind
|
|
|
|
// near objects!
|
|
|
|
let mut v: Vec<_> = input.current_system.objects.values().collect();
|
|
|
|
v.sort_by(|a, b| b.pos.z.total_cmp(&a.pos.z));
|
|
|
|
|
|
|
|
for o in v {
|
2024-02-03 11:24:17 -08:00
|
|
|
// Position adjusted for parallax
|
2024-02-07 15:58:14 -08:00
|
|
|
let pos: Point2<f32> =
|
|
|
|
(Point2::new(o.pos.x, o.pos.y) - self.ui.state.borrow().camera.get_pos()) / o.pos.z;
|
2024-02-03 11:24:17 -08:00
|
|
|
|
|
|
|
// Game dimensions of this sprite post-scale.
|
|
|
|
// Post-scale width or height, whichever is larger.
|
|
|
|
// This is in game units.
|
|
|
|
//
|
|
|
|
// We take the maximum to account for rotated sprites.
|
2024-02-05 18:29:05 -08:00
|
|
|
let m = (o.size / o.pos.z) * o.sprite.aspect.max(1.0);
|
2024-02-03 11:24:17 -08:00
|
|
|
|
|
|
|
// Don't draw sprites that are off the screen
|
|
|
|
if pos.x < screen_clip.0.x - m
|
|
|
|
|| pos.y > screen_clip.0.y + m
|
|
|
|
|| pos.x > screen_clip.1.x + m
|
|
|
|
|| pos.y < screen_clip.1.y - m
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
let idx = self.state.get_object_counter();
|
|
|
|
// Write this object's location data
|
|
|
|
self.state.queue.write_buffer(
|
|
|
|
&self.state.global_uniform.object_buffer,
|
|
|
|
ObjectData::SIZE * idx as u64,
|
|
|
|
bytemuck::cast_slice(&[ObjectData {
|
|
|
|
xpos: o.pos.x,
|
|
|
|
ypos: o.pos.y,
|
|
|
|
zpos: o.pos.z,
|
|
|
|
angle: o.angle,
|
|
|
|
size: o.size,
|
|
|
|
parent: 0,
|
|
|
|
is_child: 0,
|
|
|
|
_padding: Default::default(),
|
|
|
|
}]),
|
|
|
|
);
|
|
|
|
|
2024-02-05 18:29:05 -08:00
|
|
|
let texture_a = o.sprite.get_first_frame(); // ANIMATE
|
2024-02-03 11:24:17 -08:00
|
|
|
|
|
|
|
// Push this object's instance
|
|
|
|
self.state.push_object_buffer(ObjectInstance {
|
|
|
|
texture_index: [texture_a, texture_a],
|
|
|
|
texture_fade: 1.0,
|
|
|
|
object_index: idx as u32,
|
|
|
|
color: [1.0, 1.0, 1.0, 1.0],
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn push_effects(
|
|
|
|
&mut self,
|
|
|
|
input: &RenderInput,
|
|
|
|
// NE and SW corners of screen
|
|
|
|
screen_clip: (Point2<f32>, Point2<f32>),
|
|
|
|
) {
|
|
|
|
for p in input.phys_img.iter_effects() {
|
|
|
|
let r = &p.rigidbody;
|
|
|
|
let pos = *r.translation();
|
|
|
|
let rot = r.rotation();
|
|
|
|
let ang = rot.angle();
|
|
|
|
|
|
|
|
// Position adjusted for parallax
|
|
|
|
// TODO: adjust parallax for zoom?
|
|
|
|
// 1.0 is z-coordinate, which is constant for projectiles
|
2024-02-07 15:58:14 -08:00
|
|
|
let adjusted_pos = (pos - self.ui.state.borrow().camera.get_pos()) / 1.0;
|
2024-02-03 11:24:17 -08:00
|
|
|
|
|
|
|
// Game dimensions of this sprite post-scale.
|
|
|
|
// Post-scale width or height, whichever is larger.
|
|
|
|
// This is in game units.
|
|
|
|
//
|
|
|
|
// We take the maximum to account for rotated sprites.
|
2024-02-05 18:29:05 -08:00
|
|
|
let m = (p.effect.size / 1.0) * p.effect.anim.get_sprite().aspect.max(1.0);
|
2024-02-03 11:24:17 -08:00
|
|
|
|
|
|
|
// Don't draw sprites that are off the screen
|
|
|
|
if adjusted_pos.x < screen_clip.0.x - m
|
|
|
|
|| adjusted_pos.y > screen_clip.0.y + m
|
|
|
|
|| adjusted_pos.x > screen_clip.1.x + m
|
|
|
|
|| adjusted_pos.y < screen_clip.1.y - m
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
let idx = self.state.get_object_counter();
|
|
|
|
// Write this object's location data
|
|
|
|
self.state.queue.write_buffer(
|
|
|
|
&self.state.global_uniform.object_buffer,
|
|
|
|
ObjectData::SIZE * idx as u64,
|
|
|
|
bytemuck::cast_slice(&[ObjectData {
|
|
|
|
xpos: pos.x,
|
|
|
|
ypos: pos.y,
|
|
|
|
zpos: 1.0,
|
|
|
|
angle: ang,
|
|
|
|
size: p.effect.size,
|
|
|
|
parent: 0,
|
|
|
|
is_child: 0,
|
|
|
|
_padding: Default::default(),
|
|
|
|
}]),
|
|
|
|
);
|
|
|
|
|
|
|
|
let anim_state = p.effect.anim.get_texture_idx();
|
|
|
|
self.state.push_object_buffer(ObjectInstance {
|
|
|
|
texture_index: anim_state.texture_index(),
|
|
|
|
texture_fade: anim_state.fade,
|
|
|
|
object_index: idx as u32,
|
|
|
|
color: [1.0, 1.0, 1.0, p.get_fade()],
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|