Organized renderer

master
Mark 2024-01-10 17:53:27 -08:00
parent eaa8a7383c
commit 08e9958f2d
Signed by: Mark
GPG Key ID: C6D63995FE72FD80
17 changed files with 1139 additions and 711 deletions

243
Cargo.lock generated
View File

@ -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"

View File

@ -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" }

View File

@ -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(),

View File

@ -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,
);
}
_ => {}

View File

@ -30,3 +30,4 @@ image = { workspace = true }
winit = { workspace = true }
wgpu = { workspace = true }
bytemuck = { workspace = true }
glyphon = { workspace = true }

View File

@ -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<f32>,
/// 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<ParticleBuilder>,
}
/// 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<u32>,
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<VertexBuffer>,
pub starfield: Rc<VertexBuffer>,
pub ui: Rc<VertexBuffer>,
pub particle: Rc<VertexBuffer>,
pub radialbar: Rc<VertexBuffer>,
}

View File

@ -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<u32>,
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<VertexBuffer>,
starfield: Rc<VertexBuffer>,
ui: Rc<VertexBuffer>,
particle: Rc<VertexBuffer>,
radialbar: Rc<VertexBuffer>,
}
/// 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<Self> {
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::<TexturedVertex, ObjectInstance>(
"object",
&device,
Some(SPRITE_VERTICES),
Some(SPRITE_INDICES),
galactica_constants::OBJECT_SPRITE_INSTANCE_LIMIT,
)),
starfield: Rc::new(VertexBuffer::new::<TexturedVertex, StarfieldInstance>(
"starfield",
&device,
Some(SPRITE_VERTICES),
Some(SPRITE_INDICES),
galactica_constants::STARFIELD_SPRITE_INSTANCE_LIMIT,
)),
ui: Rc::new(VertexBuffer::new::<TexturedVertex, UiInstance>(
"ui",
&device,
Some(SPRITE_VERTICES),
Some(SPRITE_INDICES),
galactica_constants::UI_SPRITE_INSTANCE_LIMIT,
)),
particle: Rc::new(VertexBuffer::new::<TexturedVertex, ParticleInstance>(
"particle",
&device,
Some(SPRITE_VERTICES),
Some(SPRITE_INDICES),
galactica_constants::PARTICLE_SPRITE_INSTANCE_LIMIT,
)),
radialbar: Rc::new(VertexBuffer::new::<TexturedVertex, RadialBarInstance>(
"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(())
}
}

View File

@ -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<Self> {
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::<TexturedVertex, ObjectInstance>(
"object",
&device,
Some(SPRITE_VERTICES),
Some(SPRITE_INDICES),
galactica_constants::OBJECT_SPRITE_INSTANCE_LIMIT,
)),
starfield: Rc::new(VertexBuffer::new::<TexturedVertex, StarfieldInstance>(
"starfield",
&device,
Some(SPRITE_VERTICES),
Some(SPRITE_INDICES),
galactica_constants::STARFIELD_SPRITE_INSTANCE_LIMIT,
)),
ui: Rc::new(VertexBuffer::new::<TexturedVertex, UiInstance>(
"ui",
&device,
Some(SPRITE_VERTICES),
Some(SPRITE_INDICES),
galactica_constants::UI_SPRITE_INSTANCE_LIMIT,
)),
particle: Rc::new(VertexBuffer::new::<TexturedVertex, ParticleInstance>(
"particle",
&device,
Some(SPRITE_VERTICES),
Some(SPRITE_INDICES),
galactica_constants::PARTICLE_SPRITE_INSTANCE_LIMIT,
)),
radialbar: Rc::new(VertexBuffer::new::<TexturedVertex, RadialBarInstance>(
"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,
});
}
}

View File

@ -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(())
}
}

View File

@ -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<f32>, Point2<f32>),
) {
@ -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<f32>, Point2<f32>),
) {
@ -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<f32>, Point2<f32>),
) {
@ -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;
}
}
}

View File

@ -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;

View File

@ -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<f32>,
/// 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<ParticleBuilder>,
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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;
}
}