From 08e9958f2d2dd7097e42d808f70d27e611479c02 Mon Sep 17 00:00:00 2001 From: Mark Date: Wed, 10 Jan 2024 17:53:27 -0800 Subject: [PATCH] Organized renderer --- Cargo.lock | 243 +++++++- Cargo.toml | 3 + crates/galactica/src/game.rs | 6 +- crates/galactica/src/main.rs | 12 +- crates/render/Cargo.toml | 1 + crates/render/src/datastructs.rs | 78 +++ crates/render/src/gpustate/mod.rs | 520 +----------------- crates/render/src/gpustate/new.rs | 284 ++++++++++ crates/render/src/gpustate/render.rs | 234 ++++++++ crates/render/src/gpustate/systemsim.rs | 74 +-- crates/render/src/lib.rs | 6 +- crates/render/src/renderstate.rs | 35 -- crates/render/src/shaderprocessor.rs | 35 ++ crates/render/src/ui/manager.rs | 23 + crates/render/src/ui/mod.rs | 11 + .../src/{gpustate/hud.rs => ui/radar.rs} | 196 +++---- crates/render/src/ui/status.rs | 89 +++ 17 files changed, 1139 insertions(+), 711 deletions(-) create mode 100644 crates/render/src/datastructs.rs create mode 100644 crates/render/src/gpustate/new.rs create mode 100644 crates/render/src/gpustate/render.rs delete mode 100644 crates/render/src/renderstate.rs create mode 100644 crates/render/src/shaderprocessor.rs create mode 100644 crates/render/src/ui/manager.rs create mode 100644 crates/render/src/ui/mod.rs rename crates/render/src/{gpustate/hud.rs => ui/radar.rs} (54%) create mode 100644 crates/render/src/ui/status.rs diff --git a/Cargo.lock b/Cargo.lock index 1d6acb1..d1ea7b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -348,6 +348,27 @@ dependencies = [ "libc", ] +[[package]] +name = "cosmic-text" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75acbfb314aeb4f5210d379af45ed1ec2c98c7f1790bf57b8a4c562ac0c51b71" +dependencies = [ + "fontdb", + "libm", + "log", + "rangemap", + "rustc-hash", + "rustybuzz", + "self_cell", + "swash", + "sys-locale", + "unicode-bidi", + "unicode-linebreak", + "unicode-script", + "unicode-segmentation", +] + [[package]] name = "crc32fast" version = "1.3.2" @@ -472,6 +493,25 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "etagere" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "306960881d6c46bd0dd6b7f07442a441418c08d0d3e63d8d080b0f64c6343e4e" +dependencies = [ + "euclid", + "svg_fmt", +] + +[[package]] +name = "euclid" +version = "0.22.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f253bc5c813ca05792837a0ff4b3a580336b224512d48f7eda1d7dd9210787" +dependencies = [ + "num-traits", +] + [[package]] name = "exr" version = "1.71.0" @@ -519,6 +559,29 @@ dependencies = [ "spin", ] +[[package]] +name = "fontconfig-parser" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "674e258f4b5d2dcd63888c01c68413c51f565e8af99d2f7701c7b81d79ef41c4" +dependencies = [ + "roxmltree", +] + +[[package]] +name = "fontdb" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020e203f177c0fb250fb19455a252e838d2bbbce1f80f25ecc42402aafa8cd38" +dependencies = [ + "fontconfig-parser", + "log", + "memmap2 0.8.0", + "slotmap", + "tinyvec", + "ttf-parser 0.19.2", +] + [[package]] name = "foreign-types" version = "0.3.2" @@ -640,6 +703,7 @@ dependencies = [ "galactica-galaxy", "galactica-packer", "galactica-systemsim", + "glyphon", "image", "rand", "wgpu", @@ -720,6 +784,17 @@ dependencies = [ "gl_generator", ] +[[package]] +name = "glyphon" +version = "0.3.0" +source = "git+https://github.com/grovesNL/glyphon.git?branch=main#b15437b87f4082e7a96a2ba05ed5063a6047cf95" +dependencies = [ + "cosmic-text", + "etagere", + "lru", + "wgpu", +] + [[package]] name = "gpu-alloc" version = "0.6.0" @@ -975,6 +1050,15 @@ version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +[[package]] +name = "lru" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a83fb7698b3643a0e34f9ae6f2e8f0178c0fd42f8b59d493aa271ff3a5bf21" +dependencies = [ + "hashbrown", +] + [[package]] name = "malloc_buf" version = "0.0.6" @@ -1009,6 +1093,15 @@ dependencies = [ "libc", ] +[[package]] +name = "memmap2" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a5a03cefb0d953ec0be133036f14e109412fa594edc2f77227249db66cc3ed" +dependencies = [ + "libc", +] + [[package]] name = "memoffset" version = "0.6.5" @@ -1333,7 +1426,7 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4586edfe4c648c71797a74c84bacb32b52b212eff5dfe2bb9f2c599844023e7" dependencies = [ - "ttf-parser", + "ttf-parser 0.20.0", ] [[package]] @@ -1508,6 +1601,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8a99fddc9f0ba0a85884b8d14e3592853e787d581ca1816c91349b10e4eeab" +[[package]] +name = "rangemap" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "977b1e897f9d764566891689e642653e5ed90c6895106acd005eb4c1d0203991" + [[package]] name = "rapier2d" version = "0.17.2" @@ -1590,6 +1689,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cbf4a6aa5f6d6888f39e980649f3ad6b666acdce1d78e95b8a2cb076e687ae30" +[[package]] +name = "roxmltree" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862340e351ce1b271a378ec53f304a5558f7db87f3769dc655a8f6ecbb68b302" +dependencies = [ + "xmlparser", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -1602,6 +1710,23 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustybuzz" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ee8fe2a8461a0854a37101fe7a1b13998d0cfa987e43248e81d2a5f4570f6fa" +dependencies = [ + "bitflags 1.3.2", + "bytemuck", + "libm", + "smallvec", + "ttf-parser 0.20.0", + "unicode-bidi-mirroring", + "unicode-ccc", + "unicode-properties", + "unicode-script", +] + [[package]] name = "safe_arch" version = "0.7.1" @@ -1640,11 +1765,17 @@ checksum = "cda4e97be1fd174ccc2aae81c8b694e803fa99b34e8fd0f057a9d70698e3ed09" dependencies = [ "ab_glyph", "log", - "memmap2", + "memmap2 0.5.10", "smithay-client-toolkit", "tiny-skia", ] +[[package]] +name = "self_cell" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58bf37232d3bb9a2c4e641ca2a11d83b5062066f88df7fed36c28772046d65ba" + [[package]] name = "serde" version = "1.0.193" @@ -1728,7 +1859,7 @@ dependencies = [ "dlib", "lazy_static", "log", - "memmap2", + "memmap2 0.5.10", "nix 0.24.3", "pkg-config", "wayland-client", @@ -1779,6 +1910,22 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +[[package]] +name = "svg_fmt" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fb1df15f412ee2e9dfc1c504260fa695c1c3f10fe9f4a6ee2d2184d7d6450e2" + +[[package]] +name = "swash" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b7c73c813353c347272919aa1af2885068b05e625e5532b43049e4f641ae77f" +dependencies = [ + "yazi", + "zeno", +] + [[package]] name = "syn" version = "1.0.109" @@ -1801,6 +1948,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sys-locale" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e801cf239ecd6ccd71f03d270d67dd53d13e90aab208bf4b8fe4ad957ea949b0" +dependencies = [ + "libc", +] + [[package]] name = "termcolor" version = "1.4.0" @@ -1866,6 +2022,21 @@ dependencies = [ "strict-num", ] +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "toml" version = "0.8.8" @@ -1911,6 +2082,12 @@ dependencies = [ "winnow", ] +[[package]] +name = "ttf-parser" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49d64318d8311fc2668e48b63969f4343e0a85c4a109aa8460d6672e364b8bd1" + [[package]] name = "ttf-parser" version = "0.20.0" @@ -1923,12 +2100,54 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "unicode-bidi" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" + +[[package]] +name = "unicode-bidi-mirroring" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56d12260fb92d52f9008be7e4bca09f584780eb2266dc8fecc6a192bec561694" + +[[package]] +name = "unicode-ccc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2520efa644f8268dce4dcd3050eaa7fc044fca03961e9998ac7e2e92b77cf1" + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-properties" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f91c8b21fbbaa18853c3d0801c78f4fc94cdb976699bb03e832e75f7fd22f0" + +[[package]] +name = "unicode-script" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d817255e1bed6dfd4ca47258685d14d2bdcfbc64fdc9e3819bd5848057b8ecc" + +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + [[package]] name = "unicode-width" version = "0.1.11" @@ -2491,6 +2710,24 @@ version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a" +[[package]] +name = "xmlparser" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" + +[[package]] +name = "yazi" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c94451ac9513335b5e23d7a8a2b61a7102398b8cca5160829d313e84c9d98be1" + +[[package]] +name = "zeno" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd15f8e0dbb966fd9245e7498c7e9e5055d9e5c8b676b95bd67091cd11a1e697" + [[package]] name = "zerocopy" version = "0.7.32" diff --git a/Cargo.toml b/Cargo.toml index 7d6e05e..6cc6848 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,3 +66,6 @@ cgmath = "0.18.0" rand = "0.8.5" walkdir = "2.4.0" toml = "0.8.8" + +# Glyphon's crates.io release doesn't support wgpu 0.18 yet +glyphon = { git = "https://github.com/grovesNL/glyphon.git", branch = "main" } diff --git a/crates/galactica/src/game.rs b/crates/galactica/src/game.rs index 598de4c..7829fca 100644 --- a/crates/galactica/src/game.rs +++ b/crates/galactica/src/game.rs @@ -7,7 +7,7 @@ use crate::camera::Camera; use crate::inputstatus::InputStatus; use galactica_constants; -use galactica_render::RenderState; +use galactica_render::RenderInput; use galactica_systemsim::{objects::ShipControls, util, ParticleBuilder, StepResources, SystemSim}; pub struct Game { @@ -148,8 +148,8 @@ impl Game { self.last_update = Instant::now(); } - pub fn get_frame_state(&mut self) -> RenderState { - RenderState { + pub fn get_frame_state(&mut self) -> RenderInput { + RenderInput { camera_pos: self.camera.pos, camera_zoom: self.camera.zoom, current_time: self.start_instant.elapsed().as_secs_f32(), diff --git a/crates/galactica/src/main.rs b/crates/galactica/src/main.rs index fbfd2f7..dffee97 100644 --- a/crates/galactica/src/main.rs +++ b/crates/galactica/src/main.rs @@ -40,7 +40,9 @@ fn main() -> Result<()> { let mut game = game::Game::new(content); gpu.update_starfield_buffer(); - game.set_camera_aspect(gpu.window_size.width as f32 / gpu.window_size.height as f32); + game.set_camera_aspect( + gpu.window().inner_size().width as f32 / gpu.window().inner_size().height as f32, + ); event_loop.run(move |event, _, control_flow| { match event { @@ -62,7 +64,7 @@ fn main() -> Result<()> { Event::WindowEvent { ref event, window_id, - } if window_id == gpu.window.id() => match event { + } if window_id == gpu.window().id() => match event { WindowEvent::Focused(state) => { game.set_paused(!state); } @@ -85,13 +87,15 @@ fn main() -> Result<()> { WindowEvent::Resized(_) => { gpu.resize(); game.set_camera_aspect( - gpu.window_size.width as f32 / gpu.window_size.height as f32, + gpu.window().inner_size().width as f32 + / gpu.window().inner_size().height as f32, ); } WindowEvent::ScaleFactorChanged { .. } => { gpu.resize(); game.set_camera_aspect( - gpu.window_size.width as f32 / gpu.window_size.height as f32, + gpu.window().inner_size().width as f32 + / gpu.window().inner_size().height as f32, ); } _ => {} diff --git a/crates/render/Cargo.toml b/crates/render/Cargo.toml index efc9139..e2220d4 100644 --- a/crates/render/Cargo.toml +++ b/crates/render/Cargo.toml @@ -30,3 +30,4 @@ image = { workspace = true } winit = { workspace = true } wgpu = { workspace = true } bytemuck = { workspace = true } +glyphon = { workspace = true } diff --git a/crates/render/src/datastructs.rs b/crates/render/src/datastructs.rs new file mode 100644 index 0000000..959051a --- /dev/null +++ b/crates/render/src/datastructs.rs @@ -0,0 +1,78 @@ +use cgmath::Point2; +use galactica_content::{Content, SystemHandle}; +use galactica_galaxy::{Galaxy, GxShipHandle}; +use galactica_systemsim::{ParticleBuilder, SystemSim}; +use glyphon::{Buffer, FontSystem, SwashCache, TextAtlas, TextRenderer}; +use std::rc::Rc; +use wgpu::BufferAddress; +use winit::window::Window; + +use crate::{globaluniform::GlobalUniform, vertexbuffer::VertexBuffer}; + +/// Bundles parameters passed to a single call to GPUState::render +pub struct RenderInput<'a> { + /// Camera position, in world units + pub camera_pos: Point2, + + /// Player ship data + pub player_data: GxShipHandle, + + /// The system we're currently in + pub current_system: SystemHandle, + + /// Height of screen, in world units + pub camera_zoom: f32, + + /// The world state to render + pub systemsim: &'a SystemSim, + + // TODO: handle overflow. is it a problem? + /// The current time, in seconds + pub current_time: f32, + + /// Game content + pub content: &'a Content, + + /// Game data + pub data: &'a Galaxy, + + /// Particles to spawn during this frame + pub particles: &'a mut Vec, +} + +/// Renderer state. A reference to this struct is often passed to helper functions. +pub(crate) struct RenderState { + pub window: Window, + pub window_size: winit::dpi::PhysicalSize, + pub window_aspect: f32, + + pub queue: wgpu::Queue, + pub global_uniform: GlobalUniform, + pub vertex_buffers: VertexBuffers, + + pub text_font_system: FontSystem, + pub text_cache: SwashCache, + pub text_atlas: TextAtlas, + pub text_renderer: TextRenderer, + pub text_buffer: Buffer, +} + +/// Vertex buffers +pub(crate) struct VertexBuffers { + // Keeps track of length of each buffer + // Most of these are reset on each frame. + // + // The exception is particle_counter, which + // is never reset, and loops back to zero once + // it exceeds buffer length + pub object_counter: BufferAddress, + pub ui_counter: BufferAddress, + pub particle_counter: BufferAddress, + pub radialbar_counter: BufferAddress, + + pub object: Rc, + pub starfield: Rc, + pub ui: Rc, + pub particle: Rc, + pub radialbar: Rc, +} diff --git a/crates/render/src/gpustate/mod.rs b/crates/render/src/gpustate/mod.rs index 4382ccc..c77846e 100644 --- a/crates/render/src/gpustate/mod.rs +++ b/crates/render/src/gpustate/mod.rs @@ -1,49 +1,23 @@ -use anyhow::Result; use bytemuck; -use cgmath::Point2; -use galactica_constants; - -use galactica_content::Content; -use rand::seq::SliceRandom; -use std::{iter, rc::Rc}; -use wgpu::{self, BufferAddress}; -use winit::{self, window::Window}; +use wgpu; +use winit; use crate::{ - globaluniform::{GlobalDataContent, GlobalUniform}, - pipeline::PipelineBuilder, - starfield::Starfield, - texturearray::TextureArray, - vertexbuffer::{ - consts::{SPRITE_INDICES, SPRITE_VERTICES}, - types::{ - ObjectInstance, ParticleInstance, RadialBarInstance, StarfieldInstance, TexturedVertex, - UiInstance, - }, - BufferObject, VertexBuffer, - }, - RenderState, + datastructs::RenderState, starfield::Starfield, texturearray::TextureArray, ui::UiManager, }; -// Additional implementaitons for GPUState -mod hud; +/// GPUState is very big, so its methods have been split +/// among the following files. +mod new; +mod render; mod systemsim; -/// A high-level GPU wrapper. Consumes game state, +/// A high-level GPU wrapper. Reads game state (via RenderInput), /// produces pretty pictures. pub struct GPUState { - /// The window to we draw on - pub window: Window, - - /// The size of the window we draw on - pub window_size: winit::dpi::PhysicalSize, - device: wgpu::Device, config: wgpu::SurfaceConfiguration, surface: wgpu::Surface, - queue: wgpu::Queue, - - window_aspect: f32, object_pipeline: wgpu::RenderPipeline, starfield_pipeline: wgpu::RenderPipeline, @@ -53,296 +27,24 @@ pub struct GPUState { starfield: Starfield, texture_array: TextureArray, - global_uniform: GlobalUniform, - vertex_buffers: VertexBuffers, -} -struct VertexBuffers { - // Keeps track of length of each buffer - pub object_counter: BufferAddress, - pub ui_counter: BufferAddress, - pub particle_counter: BufferAddress, - pub radialbar_counter: BufferAddress, - - object: Rc, - starfield: Rc, - ui: Rc, - - particle: Rc, - radialbar: Rc, -} - -/// Basic wgsl preprocesser -fn preprocess_shader( - shader: &str, - global_uniform: &GlobalUniform, - global_uniform_group: u32, -) -> String { - // Insert dynamically-generated global definitions - let shader = shader.replace( - "// INCLUDE: global uniform header", - &global_uniform.shader_header(global_uniform_group), - ); - - // Insert common functions - let shader = shader.replace( - "// INCLUDE: animate.wgsl", - &include_str!(concat!( - env!("CARGO_MANIFEST_DIR"), - "/shaders/include/", - "animate.wgsl" - )), - ); - - let shader = shader.replace( - "// INCLUDE: anchor.wgsl", - &include_str!(concat!( - env!("CARGO_MANIFEST_DIR"), - "/shaders/include/", - "anchor.wgsl" - )), - ); - - return shader; + state: RenderState, + ui: UiManager, } impl GPUState { - /// Make a new GPUState that draws on `window` - pub async fn new(window: Window, ct: &Content) -> Result { - let window_size = window.inner_size(); - let window_aspect = window_size.width as f32 / window_size.height as f32; - - let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { - backends: wgpu::Backends::all(), - ..Default::default() - }); - - let surface = unsafe { instance.create_surface(&window) }.unwrap(); - - // Basic setup - let device; - let queue; - let config; - - { - let adapter = instance - .request_adapter(&wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::default(), - compatible_surface: Some(&surface), - force_fallback_adapter: false, - }) - .await - .unwrap(); - - (device, queue) = adapter - .request_device( - &wgpu::DeviceDescriptor { - features: wgpu::Features::TEXTURE_BINDING_ARRAY | wgpu::Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING, - // We may need limits if we compile for wasm - limits: wgpu::Limits::default(), - label: Some("gpu device"), - }, - None, - ) - .await - .unwrap(); - - // Assume sRGB - let surface_caps = surface.get_capabilities(&adapter); - let surface_format = surface_caps - .formats - .iter() - .copied() - .filter(|f| f.is_srgb()) - .filter(|f| f.has_stencil_aspect()) - .next() - .unwrap_or(surface_caps.formats[0]); - - config = wgpu::SurfaceConfiguration { - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - format: surface_format, - width: window_size.width, - height: window_size.height, - present_mode: surface_caps.present_modes[0], - alpha_mode: surface_caps.alpha_modes[0], - view_formats: vec![], - }; - - surface.configure(&device, &config); - } - - let vertex_buffers = VertexBuffers { - object_counter: 0, - ui_counter: 0, - particle_counter: 0, - radialbar_counter: 0, - - object: Rc::new(VertexBuffer::new::( - "object", - &device, - Some(SPRITE_VERTICES), - Some(SPRITE_INDICES), - galactica_constants::OBJECT_SPRITE_INSTANCE_LIMIT, - )), - - starfield: Rc::new(VertexBuffer::new::( - "starfield", - &device, - Some(SPRITE_VERTICES), - Some(SPRITE_INDICES), - galactica_constants::STARFIELD_SPRITE_INSTANCE_LIMIT, - )), - - ui: Rc::new(VertexBuffer::new::( - "ui", - &device, - Some(SPRITE_VERTICES), - Some(SPRITE_INDICES), - galactica_constants::UI_SPRITE_INSTANCE_LIMIT, - )), - - particle: Rc::new(VertexBuffer::new::( - "particle", - &device, - Some(SPRITE_VERTICES), - Some(SPRITE_INDICES), - galactica_constants::PARTICLE_SPRITE_INSTANCE_LIMIT, - )), - - radialbar: Rc::new(VertexBuffer::new::( - "radial bar", - &device, - Some(SPRITE_VERTICES), - Some(SPRITE_INDICES), - 10, - )), - }; - - // Load uniforms - let global_uniform = GlobalUniform::new(&device); - let texture_array = TextureArray::new(&device, &queue, ct)?; - - // Make sure these match the indices in each shader - let bind_group_layouts = &[ - &texture_array.bind_group_layout, - &global_uniform.bind_group_layout, - ]; - - // Create render pipelines - let object_pipeline = PipelineBuilder::new("object", &device) - .set_shader(&preprocess_shader( - &include_str!(concat!( - env!("CARGO_MANIFEST_DIR"), - "/shaders/", - "object.wgsl" - )), - &global_uniform, - 1, - )) - .set_format(config.format) - .set_triangle(true) - .set_vertex_buffer(&vertex_buffers.object) - .set_bind_group_layouts(bind_group_layouts) - .build(); - - let starfield_pipeline = PipelineBuilder::new("starfield", &device) - .set_shader(&preprocess_shader( - &include_str!(concat!( - env!("CARGO_MANIFEST_DIR"), - "/shaders/", - "starfield.wgsl" - )), - &global_uniform, - 1, - )) - .set_format(config.format) - .set_triangle(true) - .set_vertex_buffer(&vertex_buffers.starfield) - .set_bind_group_layouts(bind_group_layouts) - .build(); - - let ui_pipeline = PipelineBuilder::new("ui", &device) - .set_shader(&preprocess_shader( - &include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/shaders/", "ui.wgsl")), - &global_uniform, - 1, - )) - .set_format(config.format) - .set_triangle(true) - .set_vertex_buffer(&vertex_buffers.ui) - .set_bind_group_layouts(bind_group_layouts) - .build(); - - let particle_pipeline = PipelineBuilder::new("particle", &device) - .set_shader(&preprocess_shader( - &include_str!(concat!( - env!("CARGO_MANIFEST_DIR"), - "/shaders/", - "particle.wgsl" - )), - &global_uniform, - 1, - )) - .set_format(config.format) - .set_triangle(true) - .set_vertex_buffer(&vertex_buffers.particle) - .set_bind_group_layouts(bind_group_layouts) - .build(); - - let radialbar_pipeline = PipelineBuilder::new("radialbar", &device) - .set_shader(&preprocess_shader( - &include_str!(concat!( - env!("CARGO_MANIFEST_DIR"), - "/shaders/", - "radialbar.wgsl" - )), - &global_uniform, - 1, - )) - .set_format(config.format) - .set_triangle(true) - .set_vertex_buffer(&vertex_buffers.radialbar) - .set_bind_group_layouts(bind_group_layouts) - .build(); - - let mut starfield = Starfield::new(); - starfield.regenerate(); - - return Ok(Self { - device, - config, - surface, - queue, - - window, - window_size, - window_aspect, - - object_pipeline, - starfield_pipeline, - ui_pipeline, - particle_pipeline, - radialbar_pipeline, - - starfield, - texture_array, - global_uniform, - vertex_buffers, - }); - } - - /// Get the window this GPUState is attached to - pub fn window(&self) -> &Window { - &self.window + /// 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) { - let new_size = self.window.inner_size(); + let new_size = self.state.window.inner_size(); if new_size.width > 0 && new_size.height > 0 { - self.window_size = new_size; - self.window_aspect = new_size.width as f32 / new_size.height as f32; + 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); @@ -350,208 +52,32 @@ impl GPUState { self.update_starfield_buffer() } - /// Entrypoint for all vertex buffer builders - pub(super) fn update_all_buffers(&mut self, state: &RenderState) { - // Game coordinates (relative to camera) of ne and sw corners of screen. - // Used to skip off-screen sprites. - let clip_ne = Point2::from((-self.window_aspect, 1.0)) * state.camera_zoom; - let clip_sw = Point2::from((self.window_aspect, -1.0)) * state.camera_zoom; - - // TODO: sorting. We don't need to sort ships, but we do need to sort system objects by z-level - // (which we don't yet draw) - // that should probably be done in iter_system(). - - // 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.sysim_push_system(state, (clip_ne, clip_sw)); - self.sysim_push_ship(state, (clip_ne, clip_sw)); - self.sysim_push_projectile(state, (clip_ne, clip_sw)); - - self.hud_add_radar(state); - self.hud_add_status(state); - } - /// Make a StarfieldInstance for each star that needs to be drawn. /// Will panic if STARFIELD_INSTANCE_LIMIT is exceeded. /// /// Starfield data rarely changes, so this is called only when it's needed. pub fn update_starfield_buffer(&mut self) { - self.queue.write_buffer( - &self.vertex_buffers.starfield.instances, + self.state.queue.write_buffer( + &self.state.vertex_buffers.starfield.instances, 0, - bytemuck::cast_slice(&self.starfield.make_instances(self.window_aspect)), + bytemuck::cast_slice(&self.starfield.make_instances(self.state.window_aspect)), ); } /// Initialize the rendering engine pub fn init(&mut self) { // Update global values - self.queue.write_buffer( - &self.global_uniform.atlas_buffer, + self.state.queue.write_buffer( + &self.state.global_uniform.atlas_buffer, 0, bytemuck::cast_slice(&[self.texture_array.image_locations]), ); - self.queue.write_buffer( - &self.global_uniform.sprite_buffer, + self.state.queue.write_buffer( + &self.state.global_uniform.sprite_buffer, 0, bytemuck::cast_slice(&[self.texture_array.sprite_data]), ); self.update_starfield_buffer(); } - - /// Main render function. Draws sprites on a window. - pub fn render(&mut self, state: RenderState) -> Result<(), wgpu::SurfaceError> { - 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, - }); - - self.vertex_buffers.object_counter = 0; - self.vertex_buffers.ui_counter = 0; - self.vertex_buffers.radialbar_counter = 0; - // Don't reset particle counter, it's special - - let s = state.content.get_starfield_handle(); - - // Update global values - self.queue.write_buffer( - &self.global_uniform.data_buffer, - 0, - bytemuck::cast_slice(&[GlobalDataContent { - camera_position: state.camera_pos.into(), - camera_zoom: [state.camera_zoom, 0.0], - camera_zoom_limits: [galactica_constants::ZOOM_MIN, galactica_constants::ZOOM_MAX], - window_size: [ - self.window_size.width as f32, - self.window_size.height as f32, - ], - window_scale: [self.window.scale_factor() as f32, 0.0], - window_aspect: [self.window_aspect, 0.0], - starfield_sprite: [s.get_index(), 0], - starfield_tile_size: [galactica_constants::STARFIELD_SIZE as f32, 0.0], - starfield_size_limits: [ - galactica_constants::STARFIELD_SIZE_MIN, - galactica_constants::STARFIELD_SIZE_MAX, - ], - current_time: [state.current_time, 0.0], - }]), - ); - - // Write all new particles to GPU buffer - state.particles.shuffle(&mut rand::thread_rng()); - for i in state.particles.iter() { - self.queue.write_buffer( - &self.vertex_buffers.particle.instances, - ParticleInstance::SIZE * self.vertex_buffers.particle_counter, - bytemuck::cast_slice(&[ParticleInstance { - position: [i.pos.x, i.pos.y], - velocity: i.velocity.into(), - angle: i.angle.0, - angvel: i.angvel.0, - size: i.size, - sprite_index: i.sprite.get_index(), - created: state.current_time, - expires: state.current_time + i.lifetime, - fade: i.fade, - }]), - ); - self.vertex_buffers.particle_counter += 1; - if self.vertex_buffers.particle_counter - == galactica_constants::PARTICLE_SPRITE_INSTANCE_LIMIT - { - self.vertex_buffers.particle_counter = 0; - } - } - state.particles.clear(); - - // Create sprite instances - self.update_all_buffers(&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.global_uniform.bind_group, &[]); - - // Starfield pipeline - self.vertex_buffers.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.starfield.instance_count, - ); - - // Sprite pipeline - self.vertex_buffers.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.vertex_buffers.object_counter as _, - ); - - // Particle pipeline - self.vertex_buffers.particle.set_in_pass(&mut render_pass); - render_pass.set_pipeline(&self.particle_pipeline); - render_pass.draw_indexed( - 0..SPRITE_INDICES.len() as u32, - 0, - 0..galactica_constants::PARTICLE_SPRITE_INSTANCE_LIMIT as _, - ); - - // Ui pipeline - self.vertex_buffers.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.vertex_buffers.ui_counter as _, - ); - - // Radial progress bars - // TODO: do we need to do this every time? - self.vertex_buffers.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.vertex_buffers.radialbar_counter as _, - ); - - // begin_render_pass borrows encoder mutably, so we can't call finish() - // without dropping this variable. - drop(render_pass); - - self.queue.submit(iter::once(encoder.finish())); - output.present(); - - Ok(()) - } } diff --git a/crates/render/src/gpustate/new.rs b/crates/render/src/gpustate/new.rs new file mode 100644 index 0000000..73e67e0 --- /dev/null +++ b/crates/render/src/gpustate/new.rs @@ -0,0 +1,284 @@ +use anyhow::Result; +use galactica_content::Content; +use glyphon::{ + Attrs, Buffer, Family, FontSystem, Metrics, Shaping, SwashCache, TextAtlas, TextRenderer, +}; +use std::rc::Rc; + +use crate::{ + datastructs::{RenderState, VertexBuffers}, + globaluniform::GlobalUniform, + pipeline::PipelineBuilder, + shaderprocessor::preprocess_shader, + starfield::Starfield, + texturearray::TextureArray, + ui::UiManager, + vertexbuffer::{ + consts::{SPRITE_INDICES, SPRITE_VERTICES}, + types::{ + ObjectInstance, ParticleInstance, RadialBarInstance, StarfieldInstance, TexturedVertex, + UiInstance, + }, + VertexBuffer, + }, + GPUState, +}; + +impl GPUState { + /// Make a new GPUState that draws on `window` + pub async fn new(window: winit::window::Window, ct: &Content) -> Result { + let window_size = window.inner_size(); + let window_aspect = window_size.width as f32 / window_size.height as f32; + + let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { + backends: wgpu::Backends::all(), + ..Default::default() + }); + + let surface = unsafe { instance.create_surface(&window) }.unwrap(); + + // Basic setup + let device; + let queue; + let config; + + { + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::default(), + compatible_surface: Some(&surface), + force_fallback_adapter: false, + }) + .await + .unwrap(); + + (device, queue) = adapter + .request_device( + &wgpu::DeviceDescriptor { + features: wgpu::Features::TEXTURE_BINDING_ARRAY | wgpu::Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING, + // We may need limits if we compile for wasm + limits: wgpu::Limits::default(), + label: Some("gpu device"), + }, + None, + ) + .await + .unwrap(); + + // Assume sRGB + let surface_caps = surface.get_capabilities(&adapter); + let surface_format = surface_caps + .formats + .iter() + .copied() + .filter(|f| f.is_srgb()) + .filter(|f| f.has_stencil_aspect()) + .next() + .unwrap_or(surface_caps.formats[0]); + + config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: surface_format, + width: window_size.width, + height: window_size.height, + present_mode: surface_caps.present_modes[0], + alpha_mode: surface_caps.alpha_modes[0], + view_formats: vec![], + }; + + surface.configure(&device, &config); + } + + let vertex_buffers = VertexBuffers { + object_counter: 0, + ui_counter: 0, + particle_counter: 0, + radialbar_counter: 0, + + object: Rc::new(VertexBuffer::new::( + "object", + &device, + Some(SPRITE_VERTICES), + Some(SPRITE_INDICES), + galactica_constants::OBJECT_SPRITE_INSTANCE_LIMIT, + )), + + starfield: Rc::new(VertexBuffer::new::( + "starfield", + &device, + Some(SPRITE_VERTICES), + Some(SPRITE_INDICES), + galactica_constants::STARFIELD_SPRITE_INSTANCE_LIMIT, + )), + + ui: Rc::new(VertexBuffer::new::( + "ui", + &device, + Some(SPRITE_VERTICES), + Some(SPRITE_INDICES), + galactica_constants::UI_SPRITE_INSTANCE_LIMIT, + )), + + particle: Rc::new(VertexBuffer::new::( + "particle", + &device, + Some(SPRITE_VERTICES), + Some(SPRITE_INDICES), + galactica_constants::PARTICLE_SPRITE_INSTANCE_LIMIT, + )), + + radialbar: Rc::new(VertexBuffer::new::( + "radial bar", + &device, + Some(SPRITE_VERTICES), + Some(SPRITE_INDICES), + 10, + )), + }; + + // Load uniforms + let global_uniform = GlobalUniform::new(&device); + let texture_array = TextureArray::new(&device, &queue, ct)?; + + // Make sure these match the indices in each shader + let bind_group_layouts = &[ + &texture_array.bind_group_layout, + &global_uniform.bind_group_layout, + ]; + + // Text renderer + let mut text_atlas = TextAtlas::new(&device, &queue, wgpu::TextureFormat::Bgra8UnormSrgb); + let mut text_font_system = FontSystem::new(); + + let text_cache = SwashCache::new(); + let text_renderer = TextRenderer::new( + &mut text_atlas, + &device, + wgpu::MultisampleState::default(), + None, + ); + let text_buffer = { + let mut buffer = Buffer::new(&mut text_font_system, Metrics::new(30.0, 42.0)); + + buffer.set_size( + &mut text_font_system, + window_size.width as f32, + window_size.height as f32, + ); + buffer.set_text(&mut text_font_system, "Hello world! 👋\nThis is rendered with 🦅 glyphon 🦁\nThe text below should be partially clipped.\na b c d e f g h i j k l m n o p q r s t u v w x y z", Attrs::new().family(Family::SansSerif), Shaping::Advanced); + buffer.shape_until_scroll(&mut text_font_system); + buffer + }; + + // Create render pipelines + let object_pipeline = PipelineBuilder::new("object", &device) + .set_shader(&preprocess_shader( + &include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/shaders/", + "object.wgsl" + )), + &global_uniform, + 1, + )) + .set_format(config.format) + .set_triangle(true) + .set_vertex_buffer(&vertex_buffers.object) + .set_bind_group_layouts(bind_group_layouts) + .build(); + + let starfield_pipeline = PipelineBuilder::new("starfield", &device) + .set_shader(&preprocess_shader( + &include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/shaders/", + "starfield.wgsl" + )), + &global_uniform, + 1, + )) + .set_format(config.format) + .set_triangle(true) + .set_vertex_buffer(&vertex_buffers.starfield) + .set_bind_group_layouts(bind_group_layouts) + .build(); + + let ui_pipeline = PipelineBuilder::new("ui", &device) + .set_shader(&preprocess_shader( + &include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/shaders/", "ui.wgsl")), + &global_uniform, + 1, + )) + .set_format(config.format) + .set_triangle(true) + .set_vertex_buffer(&vertex_buffers.ui) + .set_bind_group_layouts(bind_group_layouts) + .build(); + + let particle_pipeline = PipelineBuilder::new("particle", &device) + .set_shader(&preprocess_shader( + &include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/shaders/", + "particle.wgsl" + )), + &global_uniform, + 1, + )) + .set_format(config.format) + .set_triangle(true) + .set_vertex_buffer(&vertex_buffers.particle) + .set_bind_group_layouts(bind_group_layouts) + .build(); + + let radialbar_pipeline = PipelineBuilder::new("radialbar", &device) + .set_shader(&preprocess_shader( + &include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/shaders/", + "radialbar.wgsl" + )), + &global_uniform, + 1, + )) + .set_format(config.format) + .set_triangle(true) + .set_vertex_buffer(&vertex_buffers.radialbar) + .set_bind_group_layouts(bind_group_layouts) + .build(); + + let mut starfield = Starfield::new(); + starfield.regenerate(); + + return Ok(Self { + state: RenderState { + queue, + + window, + window_size, + window_aspect, + global_uniform, + + vertex_buffers, + + text_atlas, + text_buffer, + text_cache, + text_font_system, + text_renderer, + }, + + ui: UiManager::new(), + device, + config, + surface, + starfield, + texture_array, + object_pipeline, + starfield_pipeline, + ui_pipeline, + particle_pipeline, + radialbar_pipeline, + }); + } +} diff --git a/crates/render/src/gpustate/render.rs b/crates/render/src/gpustate/render.rs new file mode 100644 index 0000000..00ae193 --- /dev/null +++ b/crates/render/src/gpustate/render.rs @@ -0,0 +1,234 @@ +use anyhow::Result; +use bytemuck; +use cgmath::Point2; +use galactica_constants; + +use glyphon::{Color, Resolution, TextArea, TextBounds}; +use rand::seq::SliceRandom; +use std::iter; +use wgpu; + +use crate::{ + globaluniform::GlobalDataContent, + vertexbuffer::{consts::SPRITE_INDICES, types::ParticleInstance, BufferObject}, + RenderInput, +}; + +impl super::GPUState { + /// Main render function. Draws sprites on a window. + pub fn render(&mut self, input: RenderInput) -> Result<(), wgpu::SurfaceError> { + // Set up text renderer + + 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, + }); + + self.state.vertex_buffers.object_counter = 0; + self.state.vertex_buffers.ui_counter = 0; + self.state.vertex_buffers.radialbar_counter = 0; + // Don't reset particle counter, it's special + + let s = input.content.get_starfield_handle(); + + // Update global values + self.state.queue.write_buffer( + &self.state.global_uniform.data_buffer, + 0, + bytemuck::cast_slice(&[GlobalDataContent { + camera_position: input.camera_pos.into(), + camera_zoom: [input.camera_zoom, 0.0], + camera_zoom_limits: [galactica_constants::ZOOM_MIN, galactica_constants::ZOOM_MAX], + window_size: [ + self.state.window_size.width as f32, + self.state.window_size.height as f32, + ], + window_scale: [self.state.window.scale_factor() as f32, 0.0], + window_aspect: [self.state.window_aspect, 0.0], + starfield_sprite: [s.get_index(), 0], + starfield_tile_size: [galactica_constants::STARFIELD_SIZE as f32, 0.0], + starfield_size_limits: [ + galactica_constants::STARFIELD_SIZE_MIN, + galactica_constants::STARFIELD_SIZE_MAX, + ], + current_time: [input.current_time, 0.0], + }]), + ); + + // Write all new particles to GPU buffer + input.particles.shuffle(&mut rand::thread_rng()); + for i in input.particles.iter() { + self.state.queue.write_buffer( + &self.state.vertex_buffers.particle.instances, + ParticleInstance::SIZE * self.state.vertex_buffers.particle_counter, + bytemuck::cast_slice(&[ParticleInstance { + position: [i.pos.x, i.pos.y], + velocity: i.velocity.into(), + angle: i.angle.0, + angvel: i.angvel.0, + size: i.size, + sprite_index: i.sprite.get_index(), + created: input.current_time, + expires: input.current_time + i.lifetime, + fade: i.fade, + }]), + ); + self.state.vertex_buffers.particle_counter += 1; + if self.state.vertex_buffers.particle_counter + == galactica_constants::PARTICLE_SPRITE_INSTANCE_LIMIT + { + self.state.vertex_buffers.particle_counter = 0; + } + } + input.particles.clear(); + + // 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::from((-self.state.window_aspect, 1.0)) * input.camera_zoom; + let clip_sw = Point2::from((self.state.window_aspect, -1.0)) * input.camera_zoom; + + // TODO: sorting. We don't need to sort ships, but we do need to sort system objects by z-level + // (which we don't yet draw) + // that should probably be done in iter_system(). + + // 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.sysim_push_system(&input, (clip_ne, clip_sw)); + self.sysim_push_ship(&input, (clip_ne, clip_sw)); + self.sysim_push_projectile(&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 + .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.starfield.instance_count, + ); + + // Sprite pipeline + self.state + .vertex_buffers + .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.vertex_buffers.object_counter as _, + ); + + // Particle pipeline + self.state + .vertex_buffers + .particle + .set_in_pass(&mut render_pass); + render_pass.set_pipeline(&self.particle_pipeline); + render_pass.draw_indexed( + 0..SPRITE_INDICES.len() as u32, + 0, + 0..galactica_constants::PARTICLE_SPRITE_INSTANCE_LIMIT as _, + ); + + // Ui pipeline + self.state.vertex_buffers.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.vertex_buffers.ui_counter as _, + ); + + // Radial progress bars + // TODO: do we need to do this every time? + self.state + .vertex_buffers + .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.vertex_buffers.radialbar_counter as _, + ); + + 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, + }, + [TextArea { + buffer: &self.state.text_buffer, + left: 10.0, + top: 10.0, + scale: 1.0, + bounds: TextBounds { + left: 0, + top: 0, + right: 600, + bottom: 160, + }, + default_color: Color::rgb(255, 255, 255), + }], + &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 can't call finish() + // without dropping this variable. + drop(render_pass); + + self.state.queue.submit(iter::once(encoder.finish())); + output.present(); + + Ok(()) + } +} diff --git a/crates/render/src/gpustate/systemsim.rs b/crates/render/src/gpustate/systemsim.rs index 0e1ad4f..a01a247 100644 --- a/crates/render/src/gpustate/systemsim.rs +++ b/crates/render/src/gpustate/systemsim.rs @@ -7,13 +7,13 @@ use galactica_systemsim::util; use crate::{ globaluniform::ObjectData, vertexbuffer::{types::ObjectInstance, BufferObject}, - GPUState, RenderState, + GPUState, RenderInput, }; impl GPUState { pub(super) fn sysim_push_ship( &mut self, - state: &RenderState, + state: &RenderInput, // NE and SW corners of screen screen_clip: (Point2, Point2), ) { @@ -45,10 +45,10 @@ impl GPUState { continue; } - let idx = self.vertex_buffers.object_counter; + let idx = self.state.vertex_buffers.object_counter; // Write this object's location data - self.queue.write_buffer( - &self.global_uniform.object_buffer, + self.state.queue.write_buffer( + &self.state.global_uniform.object_buffer, ObjectData::SIZE * idx as u64, bytemuck::cast_slice(&[ObjectData { xpos: ship_pos.x, @@ -63,7 +63,7 @@ impl GPUState { ); // Enforce buffer limit - if self.vertex_buffers.object_counter as u64 + if self.state.vertex_buffers.object_counter as u64 > galactica_constants::OBJECT_SPRITE_INSTANCE_LIMIT { // TODO: no panic, handle this better. @@ -71,15 +71,15 @@ impl GPUState { } // Push this object's instance - self.queue.write_buffer( - &self.vertex_buffers.object.instances, - ObjectInstance::SIZE * self.vertex_buffers.object_counter, + self.state.queue.write_buffer( + &self.state.vertex_buffers.object.instances, + ObjectInstance::SIZE * self.state.vertex_buffers.object_counter, bytemuck::cast_slice(&[ObjectInstance { sprite_index: ship_cnt.sprite.get_index(), object_index: idx as u32, }]), ); - self.vertex_buffers.object_counter += 1; + self.state.vertex_buffers.object_counter += 1; // This will be None if this ship is dead. // (physics object stays around to complete the death animation) @@ -92,9 +92,9 @@ impl GPUState { let flare = ship.get_outfits().get_flare_sprite(state.content); if s.get_controls().thrust && flare.is_some() { for engine_point in &ship_cnt.engines { - self.queue.write_buffer( - &self.global_uniform.object_buffer, - ObjectData::SIZE * self.vertex_buffers.object_counter as u64, + self.state.queue.write_buffer( + &self.state.global_uniform.object_buffer, + ObjectData::SIZE * self.state.vertex_buffers.object_counter as u64, bytemuck::cast_slice(&[ObjectData { xpos: engine_point.pos.x, ypos: engine_point.pos.y - engine_point.size / 2.0, @@ -108,22 +108,22 @@ impl GPUState { ); // Enforce buffer limit - if self.vertex_buffers.object_counter as u64 + if self.state.vertex_buffers.object_counter as u64 > galactica_constants::OBJECT_SPRITE_INSTANCE_LIMIT { // TODO: no panic, handle this better. panic!("Sprite limit exceeded!") } - self.queue.write_buffer( - &self.vertex_buffers.object.instances, - ObjectInstance::SIZE * self.vertex_buffers.object_counter, + self.state.queue.write_buffer( + &self.state.vertex_buffers.object.instances, + ObjectInstance::SIZE * self.state.vertex_buffers.object_counter, bytemuck::cast_slice(&[ObjectInstance { sprite_index: flare.unwrap().get_index(), - object_index: self.vertex_buffers.object_counter as u32, + object_index: self.state.vertex_buffers.object_counter as u32, }]), ); - self.vertex_buffers.object_counter += 1; + self.state.vertex_buffers.object_counter += 1; } } } @@ -131,7 +131,7 @@ impl GPUState { pub(super) fn sysim_push_projectile( &mut self, - state: &RenderState, + state: &RenderInput, // NE and SW corners of screen screen_clip: (Point2, Point2), ) { @@ -163,10 +163,10 @@ impl GPUState { continue; } - let idx = self.vertex_buffers.object_counter; + let idx = self.state.vertex_buffers.object_counter; // Write this object's location data - self.queue.write_buffer( - &self.global_uniform.object_buffer, + self.state.queue.write_buffer( + &self.state.global_uniform.object_buffer, ObjectData::SIZE * idx as u64, bytemuck::cast_slice(&[ObjectData { xpos: proj_pos.x, @@ -181,7 +181,7 @@ impl GPUState { ); // Enforce buffer limit - if self.vertex_buffers.object_counter as u64 + if self.state.vertex_buffers.object_counter as u64 > galactica_constants::OBJECT_SPRITE_INSTANCE_LIMIT { // TODO: no panic, handle this better. @@ -189,21 +189,21 @@ impl GPUState { } // Push this object's instance - self.queue.write_buffer( - &self.vertex_buffers.object.instances, - ObjectInstance::SIZE * self.vertex_buffers.object_counter, + self.state.queue.write_buffer( + &self.state.vertex_buffers.object.instances, + ObjectInstance::SIZE * self.state.vertex_buffers.object_counter, bytemuck::cast_slice(&[ObjectInstance { sprite_index: proj_cnt.sprite.get_index(), object_index: idx as u32, }]), ); - self.vertex_buffers.object_counter += 1; + self.state.vertex_buffers.object_counter += 1; } } pub(super) fn sysim_push_system( &mut self, - state: &RenderState, + state: &RenderInput, // NE and SW corners of screen screen_clip: (Point2, Point2), ) { @@ -233,10 +233,10 @@ impl GPUState { continue; } - let idx = self.vertex_buffers.object_counter; + let idx = self.state.vertex_buffers.object_counter; // Write this object's location data - self.queue.write_buffer( - &self.global_uniform.object_buffer, + self.state.queue.write_buffer( + &self.state.global_uniform.object_buffer, ObjectData::SIZE * idx as u64, bytemuck::cast_slice(&[ObjectData { xpos: o.pos.x, @@ -251,7 +251,7 @@ impl GPUState { ); // Enforce buffer limit - if self.vertex_buffers.object_counter as u64 + if self.state.vertex_buffers.object_counter as u64 > galactica_constants::OBJECT_SPRITE_INSTANCE_LIMIT { // TODO: no panic, handle this better. @@ -259,15 +259,15 @@ impl GPUState { } // Push this object's instance - self.queue.write_buffer( - &self.vertex_buffers.object.instances, - ObjectInstance::SIZE * self.vertex_buffers.object_counter, + self.state.queue.write_buffer( + &self.state.vertex_buffers.object.instances, + ObjectInstance::SIZE * self.state.vertex_buffers.object_counter, bytemuck::cast_slice(&[ObjectInstance { sprite_index: o.sprite.get_index(), object_index: idx as u32, }]), ); - self.vertex_buffers.object_counter += 1; + self.state.vertex_buffers.object_counter += 1; } } } diff --git a/crates/render/src/lib.rs b/crates/render/src/lib.rs index 85f2c4c..5d0cb4d 100644 --- a/crates/render/src/lib.rs +++ b/crates/render/src/lib.rs @@ -8,17 +8,19 @@ //! (Excluding data structs, like [`ObjectSprite`]) mod anchoredposition; +mod datastructs; mod globaluniform; mod gpustate; mod pipeline; -mod renderstate; +mod shaderprocessor; mod starfield; mod texturearray; +mod ui; mod vertexbuffer; pub use anchoredposition::PositionAnchor; +pub use datastructs::RenderInput; pub use gpustate::GPUState; -pub use renderstate::RenderState; use cgmath::Matrix4; diff --git a/crates/render/src/renderstate.rs b/crates/render/src/renderstate.rs deleted file mode 100644 index 797cbf2..0000000 --- a/crates/render/src/renderstate.rs +++ /dev/null @@ -1,35 +0,0 @@ -use cgmath::Point2; -use galactica_content::{Content, SystemHandle}; -use galactica_galaxy::{Galaxy, GxShipHandle}; -use galactica_systemsim::{ParticleBuilder, SystemSim}; - -/// Bundles parameters passed to a single call to GPUState::render -pub struct RenderState<'a> { - /// Camera position, in world units - pub camera_pos: Point2, - - /// Player ship data - pub player_data: GxShipHandle, - - /// The system we're currently in - pub current_system: SystemHandle, - - /// Height of screen, in world units - pub camera_zoom: f32, - - /// The world state to render - pub systemsim: &'a SystemSim, - - // TODO: handle overflow. is it a problem? - /// The current time, in seconds - pub current_time: f32, - - /// Game content - pub content: &'a Content, - - /// Game data - pub data: &'a Galaxy, - - /// Particles to spawn during this frame - pub particles: &'a mut Vec, -} diff --git a/crates/render/src/shaderprocessor.rs b/crates/render/src/shaderprocessor.rs new file mode 100644 index 0000000..5889a7a --- /dev/null +++ b/crates/render/src/shaderprocessor.rs @@ -0,0 +1,35 @@ +use crate::globaluniform::GlobalUniform; + +/// Basic wgsl preprocesser +pub(crate) fn preprocess_shader( + shader: &str, + global_uniform: &GlobalUniform, + global_uniform_group: u32, +) -> String { + // Insert dynamically-generated global definitions + let shader = shader.replace( + "// INCLUDE: global uniform header", + &global_uniform.shader_header(global_uniform_group), + ); + + // Insert common functions + let shader = shader.replace( + "// INCLUDE: animate.wgsl", + &include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/shaders/include/", + "animate.wgsl" + )), + ); + + let shader = shader.replace( + "// INCLUDE: anchor.wgsl", + &include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/shaders/include/", + "anchor.wgsl" + )), + ); + + return shader; +} diff --git a/crates/render/src/ui/manager.rs b/crates/render/src/ui/manager.rs new file mode 100644 index 0000000..7b78515 --- /dev/null +++ b/crates/render/src/ui/manager.rs @@ -0,0 +1,23 @@ +use crate::{datastructs::RenderState, RenderInput}; + +use super::{radar::Radar, status::Status, UiElement}; + +pub struct UiManager { + radar: Radar, + status: Status, +} + +impl UiManager { + pub fn new() -> Self { + Self { + radar: Radar::new(), + status: Status::new(), + } + } + + /// Draw all ui elements + pub fn draw(&mut self, input: &RenderInput, state: &mut RenderState) { + self.radar.draw(input, state); + self.status.draw(input, state); + } +} diff --git a/crates/render/src/ui/mod.rs b/crates/render/src/ui/mod.rs new file mode 100644 index 0000000..0cd403e --- /dev/null +++ b/crates/render/src/ui/mod.rs @@ -0,0 +1,11 @@ +use crate::{datastructs::RenderState, RenderInput}; + +mod manager; +mod radar; +mod status; + +pub use manager::UiManager; + +trait UiElement { + fn draw(&mut self, input: &RenderInput, state: &mut RenderState); +} diff --git a/crates/render/src/gpustate/hud.rs b/crates/render/src/ui/radar.rs similarity index 54% rename from crates/render/src/gpustate/hud.rs rename to crates/render/src/ui/radar.rs index 312c5df..41dd239 100644 --- a/crates/render/src/gpustate/hud.rs +++ b/crates/render/src/ui/radar.rs @@ -1,20 +1,23 @@ -//! GPUState routines for drawing HUD elements - -use std::f32::consts::TAU; - use cgmath::{Deg, InnerSpace, Point2, Rad, Vector2}; use galactica_systemsim::util; use crate::{ - vertexbuffer::{ - types::{RadialBarInstance, UiInstance}, - BufferObject, - }, - GPUState, PositionAnchor, RenderState, + vertexbuffer::{types::UiInstance, BufferObject}, + PositionAnchor, RenderInput, }; -impl GPUState { - pub(super) fn hud_add_radar(&mut self, state: &RenderState) { +use super::RenderState; + +pub(super) struct Radar {} + +impl Radar { + pub fn new() -> Self { + Self {} + } +} + +impl super::UiElement for Radar { + fn draw(&mut self, input: &RenderInput, state: &mut RenderState) { let radar_range = 4000.0; let radar_size = 300.0; let hide_range = 0.85; @@ -22,40 +25,40 @@ impl GPUState { let system_object_scale = 1.0 / 600.0; let ship_scale = 1.0 / 10.0; - let player_world_object = state.systemsim.get_ship(state.player_data).unwrap(); - let player_body = state + let player_world_object = input.systemsim.get_ship(input.player_data).unwrap(); + let player_body = input .systemsim .get_rigid_body(player_world_object.rigid_body) .unwrap(); let player_position = util::rigidbody_position(player_body); - let planet_sprite = state.content.get_sprite_handle("ui::planetblip"); - let ship_sprite = state.content.get_sprite_handle("ui::shipblip"); - let arrow_sprite = state.content.get_sprite_handle("ui::centerarrow"); + let planet_sprite = input.content.get_sprite_handle("ui::planetblip"); + let ship_sprite = input.content.get_sprite_handle("ui::shipblip"); + let arrow_sprite = input.content.get_sprite_handle("ui::centerarrow"); // Enforce buffer limit - if self.vertex_buffers.ui_counter as u64 > galactica_constants::UI_SPRITE_INSTANCE_LIMIT { + if state.vertex_buffers.ui_counter as u64 > galactica_constants::UI_SPRITE_INSTANCE_LIMIT { // TODO: no panic, handle this better. panic!("UI limit exceeded!") } // Push this object's instance - self.queue.write_buffer( - &self.vertex_buffers.ui.instances, - UiInstance::SIZE * self.vertex_buffers.ui_counter, + state.queue.write_buffer( + &state.vertex_buffers.ui.instances, + UiInstance::SIZE * state.vertex_buffers.ui_counter, bytemuck::cast_slice(&[UiInstance { anchor: PositionAnchor::NwNw.to_int(), position: [10.0, -10.0], angle: 0.0, size: radar_size, color: [1.0, 1.0, 1.0, 1.0], - sprite_index: state.content.get_sprite_handle("ui::radar").get_index(), + sprite_index: input.content.get_sprite_handle("ui::radar").get_index(), }]), ); - self.vertex_buffers.ui_counter += 1; + state.vertex_buffers.ui_counter += 1; // Draw system objects - let system = state.content.get_system(state.current_system); + let system = input.content.get_system(input.current_system); for o in &system.objects { let size = (o.size / o.pos.z) / (radar_range * system_object_scale); let p = Point2 { @@ -74,7 +77,7 @@ impl GPUState { } // Enforce buffer limit - if self.vertex_buffers.ui_counter as u64 + if state.vertex_buffers.ui_counter as u64 > galactica_constants::UI_SPRITE_INSTANCE_LIMIT { // TODO: no panic, handle this better. @@ -82,9 +85,9 @@ impl GPUState { } // Push this object's instance - self.queue.write_buffer( - &self.vertex_buffers.ui.instances, - UiInstance::SIZE * self.vertex_buffers.ui_counter, + state.queue.write_buffer( + &state.vertex_buffers.ui.instances, + UiInstance::SIZE * state.vertex_buffers.ui_counter, bytemuck::cast_slice(&[UiInstance { anchor: PositionAnchor::NwC.to_int(), position: (Point2 { @@ -98,25 +101,25 @@ impl GPUState { sprite_index: planet_sprite.get_index(), }]), ); - self.vertex_buffers.ui_counter += 1; + state.vertex_buffers.ui_counter += 1; } } // Draw ships - for (s, r) in state.systemsim.iter_ship_body() { + for (s, r) in input.systemsim.iter_ship_body() { // This will be None if this ship is dead. // Stays around while the physics system runs a collapse sequence - let color = match state.data.get_ship(s.data_handle) { + let color = match input.data.get_ship(s.data_handle) { None => { // TODO: configurable [0.2, 0.2, 0.2, 1.0] } Some(data) => { - let c = state.content.get_faction(data.get_faction()).color; + let c = input.content.get_faction(data.get_faction()).color; [c[0], c[1], c[2], 1.0] } }; - let ship = state.content.get_ship(s.data_handle.content_handle()); + let ship = input.content.get_ship(s.data_handle.content_handle()); let size = (ship.size * ship.sprite.aspect) * ship_scale; let p = util::rigidbody_position(r); let d = (p - player_position) / radar_range; @@ -135,7 +138,7 @@ impl GPUState { // Enforce buffer limit // TODO: cleaner solution. don't do this everywhere. - if self.vertex_buffers.ui_counter as u64 + if state.vertex_buffers.ui_counter as u64 > galactica_constants::UI_SPRITE_INSTANCE_LIMIT { // TODO: no panic, handle this better. @@ -143,9 +146,9 @@ impl GPUState { } // Push this object's instance - self.queue.write_buffer( - &self.vertex_buffers.ui.instances, - UiInstance::SIZE * self.vertex_buffers.ui_counter, + state.queue.write_buffer( + &state.vertex_buffers.ui.instances, + UiInstance::SIZE * state.vertex_buffers.ui_counter, bytemuck::cast_slice(&[UiInstance { anchor: PositionAnchor::NwC.to_int(), position: position.into(), @@ -155,33 +158,33 @@ impl GPUState { sprite_index: ship_sprite.get_index(), }]), ); - self.vertex_buffers.ui_counter += 1; + state.vertex_buffers.ui_counter += 1; } } // Draw viewport frame let d = Vector2 { - x: (state.camera_zoom / 2.0) * self.window_aspect, - y: state.camera_zoom / 2.0, + x: (input.camera_zoom / 2.0) * state.window_aspect, + y: input.camera_zoom / 2.0, } / radar_range; let m = d.magnitude(); let d = d * (radar_size / 2.0); let color = [0.3, 0.3, 0.3, 1.0]; if m < 0.8 { - let sprite = state.content.get_sprite_handle("ui::radarframe"); + let sprite = input.content.get_sprite_handle("ui::radarframe"); let size = 7.0f32.min((0.8 - m) * 70.0); // Enforce buffer limit (this section adds four items) - if self.vertex_buffers.ui_counter as u64 + 4 + if state.vertex_buffers.ui_counter as u64 + 4 > galactica_constants::UI_SPRITE_INSTANCE_LIMIT { // TODO: no panic, handle this better. panic!("UI limit exceeded!") } - self.queue.write_buffer( - &self.vertex_buffers.ui.instances, - UiInstance::SIZE * self.vertex_buffers.ui_counter, + state.queue.write_buffer( + &state.vertex_buffers.ui.instances, + UiInstance::SIZE * state.vertex_buffers.ui_counter, bytemuck::cast_slice(&[UiInstance { anchor: PositionAnchor::NwNw.to_int(), position: Point2 { @@ -195,11 +198,11 @@ impl GPUState { sprite_index: sprite.get_index(), }]), ); - self.vertex_buffers.ui_counter += 1; + state.vertex_buffers.ui_counter += 1; - self.queue.write_buffer( - &self.vertex_buffers.ui.instances, - UiInstance::SIZE * self.vertex_buffers.ui_counter, + state.queue.write_buffer( + &state.vertex_buffers.ui.instances, + UiInstance::SIZE * state.vertex_buffers.ui_counter, bytemuck::cast_slice(&[UiInstance { anchor: PositionAnchor::NwSw.to_int(), position: Point2 { @@ -213,11 +216,11 @@ impl GPUState { sprite_index: sprite.get_index(), }]), ); - self.vertex_buffers.ui_counter += 1; + state.vertex_buffers.ui_counter += 1; - self.queue.write_buffer( - &self.vertex_buffers.ui.instances, - UiInstance::SIZE * self.vertex_buffers.ui_counter, + state.queue.write_buffer( + &state.vertex_buffers.ui.instances, + UiInstance::SIZE * state.vertex_buffers.ui_counter, bytemuck::cast_slice(&[UiInstance { anchor: PositionAnchor::NwSe.to_int(), position: Point2 { @@ -231,11 +234,11 @@ impl GPUState { sprite_index: sprite.get_index(), }]), ); - self.vertex_buffers.ui_counter += 1; + state.vertex_buffers.ui_counter += 1; - self.queue.write_buffer( - &self.vertex_buffers.ui.instances, - UiInstance::SIZE * self.vertex_buffers.ui_counter, + state.queue.write_buffer( + &state.vertex_buffers.ui.instances, + UiInstance::SIZE * state.vertex_buffers.ui_counter, bytemuck::cast_slice(&[UiInstance { anchor: PositionAnchor::NwNe.to_int(), position: Point2 { @@ -249,7 +252,7 @@ impl GPUState { sprite_index: sprite.get_index(), }]), ); - self.vertex_buffers.ui_counter += 1; + state.vertex_buffers.ui_counter += 1; } // Arrow to center of system @@ -263,15 +266,16 @@ impl GPUState { y: radar_size / -2.0 - 10.0, } + ((q.normalize() * 0.865) * (radar_size / 2.0)); - if self.vertex_buffers.ui_counter as u64 > galactica_constants::UI_SPRITE_INSTANCE_LIMIT + if state.vertex_buffers.ui_counter as u64 + > galactica_constants::UI_SPRITE_INSTANCE_LIMIT { // TODO: no panic, handle this better. panic!("UI limit exceeded!") } - self.queue.write_buffer( - &self.vertex_buffers.ui.instances, - UiInstance::SIZE * self.vertex_buffers.ui_counter, + state.queue.write_buffer( + &state.vertex_buffers.ui.instances, + UiInstance::SIZE * state.vertex_buffers.ui_counter, bytemuck::cast_slice(&[UiInstance { anchor: PositionAnchor::NwC.to_int(), position: position.into(), @@ -281,75 +285,7 @@ impl GPUState { sprite_index: arrow_sprite.get_index(), }]), ); - self.vertex_buffers.ui_counter += 1; + state.vertex_buffers.ui_counter += 1; } } - - pub(super) fn hud_add_status(&mut self, state: &RenderState) { - if self.vertex_buffers.ui_counter as u64 > galactica_constants::UI_SPRITE_INSTANCE_LIMIT { - // TODO: no panic, handle this better. - panic!("UI limit exceeded!") - } - - let player_world_object = state.systemsim.get_ship(state.player_data).unwrap(); - - let data = state - .data - .get_ship(player_world_object.data_handle) - .unwrap(); - let max_shields = data.get_outfits().get_shield_strength(); - let current_shields = data.get_shields(); - let current_hull = data.get_hull(); - let max_hull = state.content.get_ship(data.get_content()).hull; - - self.queue.write_buffer( - &self.vertex_buffers.ui.instances, - UiInstance::SIZE * self.vertex_buffers.ui_counter, - bytemuck::cast_slice(&[UiInstance { - anchor: PositionAnchor::NeNe.to_int(), - position: [-10.0, -10.0], - angle: 0.0, - size: 200.0, - color: [1.0, 1.0, 1.0, 1.0], - sprite_index: state.content.get_sprite_handle("ui::status").get_index(), - }]), - ); - self.vertex_buffers.ui_counter += 1; - - // We add two items here, so +2 - if self.vertex_buffers.radialbar_counter as u64 + 2 - > galactica_constants::RADIALBAR_SPRITE_INSTANCE_LIMIT - { - // TODO: no panic, handle this better. - panic!("Radialbar limit exceeded!") - } - - self.queue.write_buffer( - &self.vertex_buffers.radialbar.instances, - RadialBarInstance::SIZE * self.vertex_buffers.radialbar_counter, - bytemuck::cast_slice(&[RadialBarInstance { - position: [-19.0, -19.0], - anchor: PositionAnchor::NeNe.to_int(), - diameter: 182.0, - stroke: 5.0, - color: [0.3, 0.6, 0.8, 1.0], - angle: (current_shields / max_shields) * TAU, - }]), - ); - self.vertex_buffers.radialbar_counter += 1; - - self.queue.write_buffer( - &self.vertex_buffers.radialbar.instances, - RadialBarInstance::SIZE * self.vertex_buffers.radialbar_counter, - bytemuck::cast_slice(&[RadialBarInstance { - position: [-27.0, -27.0], - anchor: PositionAnchor::NeNe.to_int(), - diameter: 166.0, - stroke: 5.0, - color: [0.8, 0.7, 0.5, 1.0], - angle: (current_hull / max_hull) * TAU, - }]), - ); - self.vertex_buffers.radialbar_counter += 1; - } } diff --git a/crates/render/src/ui/status.rs b/crates/render/src/ui/status.rs new file mode 100644 index 0000000..2fd59c2 --- /dev/null +++ b/crates/render/src/ui/status.rs @@ -0,0 +1,89 @@ +use std::f32::consts::TAU; + +use crate::{ + datastructs::RenderState, + vertexbuffer::{ + types::{RadialBarInstance, UiInstance}, + BufferObject, + }, + PositionAnchor, RenderInput, +}; + +use super::UiElement; + +pub(super) struct Status {} +impl Status { + pub fn new() -> Self { + Self {} + } +} + +impl UiElement for Status { + fn draw(&mut self, input: &RenderInput, state: &mut RenderState) { + if state.vertex_buffers.ui_counter as u64 > galactica_constants::UI_SPRITE_INSTANCE_LIMIT { + // TODO: no panic, handle this better. + panic!("UI limit exceeded!") + } + + let player_world_object = input.systemsim.get_ship(input.player_data).unwrap(); + + let data = input + .data + .get_ship(player_world_object.data_handle) + .unwrap(); + let max_shields = data.get_outfits().get_shield_strength(); + let current_shields = data.get_shields(); + let current_hull = data.get_hull(); + let max_hull = input.content.get_ship(data.get_content()).hull; + + state.queue.write_buffer( + &state.vertex_buffers.ui.instances, + UiInstance::SIZE * state.vertex_buffers.ui_counter, + bytemuck::cast_slice(&[UiInstance { + anchor: PositionAnchor::NeNe.to_int(), + position: [-10.0, -10.0], + angle: 0.0, + size: 200.0, + color: [1.0, 1.0, 1.0, 1.0], + sprite_index: input.content.get_sprite_handle("ui::status").get_index(), + }]), + ); + state.vertex_buffers.ui_counter += 1; + + // We add two items here, so +2 + if state.vertex_buffers.radialbar_counter as u64 + 2 + > galactica_constants::RADIALBAR_SPRITE_INSTANCE_LIMIT + { + // TODO: no panic, handle this better. + panic!("Radialbar limit exceeded!") + } + + state.queue.write_buffer( + &state.vertex_buffers.radialbar.instances, + RadialBarInstance::SIZE * state.vertex_buffers.radialbar_counter, + bytemuck::cast_slice(&[RadialBarInstance { + position: [-19.0, -19.0], + anchor: PositionAnchor::NeNe.to_int(), + diameter: 182.0, + stroke: 5.0, + color: [0.3, 0.6, 0.8, 1.0], + angle: (current_shields / max_shields) * TAU, + }]), + ); + state.vertex_buffers.radialbar_counter += 1; + + state.queue.write_buffer( + &state.vertex_buffers.radialbar.instances, + RadialBarInstance::SIZE * state.vertex_buffers.radialbar_counter, + bytemuck::cast_slice(&[RadialBarInstance { + position: [-27.0, -27.0], + anchor: PositionAnchor::NeNe.to_int(), + diameter: 166.0, + stroke: 5.0, + color: [0.8, 0.7, 0.5, 1.0], + angle: (current_hull / max_hull) * TAU, + }]), + ); + state.vertex_buffers.radialbar_counter += 1; + } +}