From: Joshua Simmons Date: Sun, 5 May 2024 14:21:22 +0000 (+0200) Subject: shark: Add guns which shoot projectile sharks X-Git-Url: https://git.nega.tv//gitweb.cgi?a=commitdiff_plain;h=508a74ce61940c285177470b615e24c1a7e70a56;p=josh%2Fnarcissus shark: Add guns which shoot projectile sharks --- diff --git a/engine/narcissus-maths/src/affine3.rs b/engine/narcissus-maths/src/affine3.rs index fd32104..a320abb 100644 --- a/engine/narcissus-maths/src/affine3.rs +++ b/engine/narcissus-maths/src/affine3.rs @@ -2,28 +2,35 @@ use crate::{Mat3, Point3, Vec3}; /// Matrix and translation vector which together represent a 3d affine /// transformation. -#[derive(Clone, Copy, PartialEq)] +#[derive(Clone, Copy, PartialEq, Debug)] #[repr(C)] pub struct Affine3 { pub matrix: Mat3, - pub translate: Vec3, + pub translation: Vec3, } impl Affine3 { pub const ZERO: Affine3 = Affine3 { matrix: Mat3::ZERO, - translate: Vec3::ZERO, + translation: Vec3::ZERO, }; pub const IDENTITY: Affine3 = Affine3 { matrix: Mat3::IDENTITY, - translate: Vec3::ZERO, + translation: Vec3::ZERO, }; + pub fn new(matrix: Mat3, translation: Vec3) -> Self { + Self { + matrix, + translation, + } + } + pub fn mul_affine3(&self, rhs: Affine3) -> Affine3 { Self { matrix: self.matrix * rhs.matrix, - translate: self.translate + rhs.translate, + translation: self.translation + rhs.translation, } } @@ -32,7 +39,7 @@ impl Affine3 { } pub fn transform_point3(&self, point: Point3) -> Point3 { - self.matrix * point + self.translate + self.matrix * point + self.translation } } diff --git a/title/shark/src/main.rs b/title/shark/src/main.rs index da6d461..e0f133a 100644 --- a/title/shark/src/main.rs +++ b/title/shark/src/main.rs @@ -1,4 +1,4 @@ -use std::{fmt::Write, time::Instant}; +use std::fmt::Write; use crate::{ fonts::{FontFamily, Fonts}, @@ -6,17 +6,18 @@ use crate::{ }; use helpers::{load_image, load_obj}; use narcissus_app::{create_app, Event, Key, PressedState, WindowDesc}; -use narcissus_core::{default, rand::Pcg64, slice::array_windows}; +use narcissus_core::{default, rand::Pcg64, slice::array_windows, BitIter}; use narcissus_font::{FontCollection, GlyphCache, HorizontalMetrics}; use narcissus_gpu::{ - create_device, Access, BufferImageCopy, BufferUsageFlags, ClearValue, DeviceExt, Extent2d, - Extent3d, ImageAspectFlags, ImageBarrier, ImageDesc, ImageDimension, ImageFormat, ImageLayout, - ImageTiling, ImageUsageFlags, LoadOp, MemoryLocation, Offset2d, Offset3d, RenderingAttachment, - RenderingDesc, Scissor, StoreOp, ThreadToken, Viewport, + create_device, Access, Bind, BufferImageCopy, BufferUsageFlags, ClearValue, DeviceExt, + Extent2d, Extent3d, ImageAspectFlags, ImageBarrier, ImageDesc, ImageDimension, ImageFormat, + ImageLayout, ImageTiling, ImageUsageFlags, IndexType, LoadOp, MemoryLocation, Offset2d, + Offset3d, RenderingAttachment, RenderingDesc, Scissor, StoreOp, ThreadToken, TypedBind, + Viewport, }; use narcissus_maths::{ - clamp, perlin_noise3, sin_cos_pi_f32, sin_pi_f32, vec3, Affine3, Deg, HalfTurn, Mat3, Mat4, - Point3, Vec3, + clamp, exp_f32, perlin_noise3, sin_pi_f32, vec3, Affine3, Deg, HalfTurn, Mat3, Mat4, Point3, + Vec3, }; use pipelines::{BasicUniforms, PrimitiveInstance, PrimitiveVertex, TextUniforms}; @@ -26,12 +27,13 @@ mod pipelines; const SQRT_2: f32 = 0.70710677; -const NUM_SHARKS: usize = 50; const GLYPH_CACHE_SIZE: usize = 1024; +const ARCHTYPE_PROJECTILE_MAX: usize = 65536; + struct GameVariables { game_speed: f32, - player_speed: f32, + camera_distance: f32, camera_angle: Deg, camera_damping: f32, @@ -39,11 +41,17 @@ struct GameVariables { camera_shake_decay: f32, camera_shake_max_offset: f32, camera_shake_frequency: f32, + + player_speed: f32, + + weapon_cooldown: f32, + weapon_projectile_speed: f32, + weapon_projectile_lifetime: f32, } static GAME_VARIABLES: GameVariables = GameVariables { game_speed: 1.0, - player_speed: 15.0, + camera_distance: 55.0, camera_angle: Deg::new(60.0), camera_damping: 35.0, @@ -51,6 +59,12 @@ static GAME_VARIABLES: GameVariables = GameVariables { camera_shake_decay: 2.0, camera_shake_max_offset: 2.0, camera_shake_frequency: 11.0, + + player_speed: 15.0, + + weapon_cooldown: 0.125, + weapon_projectile_speed: 20.0, + weapon_projectile_lifetime: 2.0, }; #[derive(Clone, Copy, Debug)] @@ -106,13 +120,17 @@ impl Actions { struct PlayerState { position: Point3, heading: Vec3, + + weapon_cooldown: f32, } impl PlayerState { fn new() -> Self { Self { position: Point3::ZERO, - heading: Vec3::new(SQRT_2, 0.0, -SQRT_2), + heading: vec3(SQRT_2, 0.0, -SQRT_2), + + weapon_cooldown: GAME_VARIABLES.weapon_cooldown, } } } @@ -136,7 +154,7 @@ impl CameraState { // Rotate camera let one_on_sqrt2 = 1.0 / 2.0_f32.sqrt(); - let eye_offset = Vec3::new(-base * one_on_sqrt2, height, -base * one_on_sqrt2); + let eye_offset = vec3(-base * one_on_sqrt2, height, -base * one_on_sqrt2); Self { eye_offset, @@ -149,7 +167,7 @@ impl CameraState { } } - fn tick(&mut self, target: Point3, time: f32, dt: f32) { + fn tick(&mut self, target: Point3, time: f32, delta_time: f32) { if Point3::distance_sq(self.position, target) > (GAME_VARIABLES.camera_deadzone * GAME_VARIABLES.camera_deadzone) { @@ -158,14 +176,14 @@ impl CameraState { self.velocity.x, target.x, GAME_VARIABLES.camera_damping, - dt, + delta_time, ); let (pos_z, vel_z) = simple_spring_damper_exact( self.position.z, self.velocity.z, target.z, GAME_VARIABLES.camera_damping, - dt, + delta_time, ); self.position.z = pos_z; @@ -174,7 +192,7 @@ impl CameraState { self.velocity.z = vel_z; } - self.shake -= GAME_VARIABLES.camera_shake_decay * dt; + self.shake -= GAME_VARIABLES.camera_shake_decay * delta_time; self.shake = clamp(self.shake, 0.0, 1.0); let t = time * GAME_VARIABLES.camera_shake_frequency; @@ -191,26 +209,49 @@ impl CameraState { } } +#[derive(Clone, Copy, Default)] +struct ArchetypeProjectile8 { + position_x: [f32; 8], + position_z: [f32; 8], + velocity_x: [f32; 8], + velocity_z: [f32; 8], + lifetime: [f32; 8], +} + struct GameState { + rng: Pcg64, + time: f32, actions: Actions, camera: CameraState, player: PlayerState, + + archetype_projectile_bitmap_0: [u64; ARCHTYPE_PROJECTILE_MAX / 64 / 64], + archetype_projectile_bitmap_1: [u64; ARCHTYPE_PROJECTILE_MAX / 64], + archetype_projectile: Box<[ArchetypeProjectile8]>, } impl GameState { fn new() -> Self { Self { + rng: Pcg64::new(), time: 0.0, actions: Actions::new(), camera: CameraState::new(), player: PlayerState::new(), + archetype_projectile_bitmap_0: [0; ARCHTYPE_PROJECTILE_MAX / 64 / 64], + archetype_projectile_bitmap_1: [0; ARCHTYPE_PROJECTILE_MAX / 64], + archetype_projectile: vec![ + ArchetypeProjectile8::default(); + ARCHTYPE_PROJECTILE_MAX / 8 + ] + .into_boxed_slice(), } } - fn tick(&mut self, dt: f32, action_queue: &[ActionEvent]) { - let dt = dt * GAME_VARIABLES.game_speed; - self.time += dt; + fn tick(&mut self, delta_time: f32, action_queue: &[ActionEvent]) { + let delta_time = delta_time * GAME_VARIABLES.game_speed; + self.time += delta_time; self.actions.tick(action_queue); @@ -224,14 +265,14 @@ impl GameState { | (self.actions.is_active(Action::Right) as usize) << 3; // Pre-rotated values - const UP: Vec3 = Vec3::new(SQRT_2, 0.0, SQRT_2); - const DOWN: Vec3 = Vec3::new(-SQRT_2, 0.0, -SQRT_2); - const LEFT: Vec3 = Vec3::new(SQRT_2, 0.0, -SQRT_2); - const RIGHT: Vec3 = Vec3::new(-SQRT_2, 0.0, SQRT_2); - const UP_LEFT: Vec3 = Vec3::new(1.0, 0.0, 0.0); - const UP_RIGHT: Vec3 = Vec3::new(0.0, 0.0, 1.0); - const DOWN_LEFT: Vec3 = Vec3::new(0.0, 0.0, -1.0); - const DOWN_RIGHT: Vec3 = Vec3::new(-1.0, 0.0, 0.0); + const UP: Vec3 = vec3(SQRT_2, 0.0, SQRT_2); + const DOWN: Vec3 = vec3(-SQRT_2, 0.0, -SQRT_2); + const LEFT: Vec3 = vec3(SQRT_2, 0.0, -SQRT_2); + const RIGHT: Vec3 = vec3(-SQRT_2, 0.0, SQRT_2); + const UP_LEFT: Vec3 = vec3(1.0, 0.0, 0.0); + const UP_RIGHT: Vec3 = vec3(0.0, 0.0, 1.0); + const DOWN_LEFT: Vec3 = vec3(0.0, 0.0, -1.0); + const DOWN_RIGHT: Vec3 = vec3(-1.0, 0.0, 0.0); let movement = [ // 0 0 0 0 @@ -272,19 +313,91 @@ impl GameState { self.player.heading = movement; } - self.player.position += movement * GAME_VARIABLES.player_speed * dt; + let player_velocity = movement * GAME_VARIABLES.player_speed; + self.player.position += player_velocity * delta_time; + + self.camera + .tick(self.player.position, self.time, delta_time); + + self.player.weapon_cooldown -= delta_time; - self.camera.tick(self.player.position, self.time, dt); + if self.player.weapon_cooldown <= 0.0 { + // fire! + let [x, y] = self.rng.next_uniform_unit_circle_f32(); + let direction = vec3(x, 0.0, y); + let velocity = player_velocity + direction * GAME_VARIABLES.weapon_projectile_speed; + self.spawn_projectile( + self.player.position, + velocity, + GAME_VARIABLES.weapon_projectile_lifetime, + ); + + self.player.weapon_cooldown = GAME_VARIABLES.weapon_cooldown; + } + + // Expire projectiles + for (base, base_word) in self.archetype_projectile_bitmap_0.iter_mut().enumerate() { + for i in BitIter::new(std::iter::once(*base_word)) { + let index = base * 64 + i; + let word = &mut self.archetype_projectile_bitmap_1[index]; + for j in BitIter::new(std::iter::once(*word)) { + let index = index * 64 + j; + self.archetype_projectile[index / 8].lifetime[index % 8] -= delta_time; + let dead = self.archetype_projectile[index / 8].lifetime[index % 8] <= 0.0; + *word &= !((dead as u64) << j); + } + *base_word &= !(((*word == 0) as u64) << i); + } + } + + // Move projectiles + for base in BitIter::new(self.archetype_projectile_bitmap_0.iter().copied()) { + for i in BitIter::new(std::iter::once(self.archetype_projectile_bitmap_1[base])) { + let i = base * 64 + i; + self.archetype_projectile[i / 8].position_x[i % 8] += + self.archetype_projectile[i / 8].velocity_x[i % 8] * delta_time; + self.archetype_projectile[i / 8].position_z[i % 8] += + self.archetype_projectile[i / 8].velocity_z[i % 8] * delta_time; + } + } + } + + fn spawn_projectile(&mut self, position: Point3, velocity: Vec3, lifetime: f32) { + let i = BitIter::new( + self.archetype_projectile_bitmap_1 + .iter() + .copied() + .map(|x| !x), + ) + .next() + .unwrap(); + + self.archetype_projectile[i / 8].position_x[i % 8] = position.x; + self.archetype_projectile[i / 8].position_z[i % 8] = position.z; + self.archetype_projectile[i / 8].velocity_x[i % 8] = velocity.x; + self.archetype_projectile[i / 8].velocity_z[i % 8] = velocity.z; + self.archetype_projectile[i / 8].lifetime[i % 8] = lifetime; + self.archetype_projectile_bitmap_1[i / 64] |= 1 << i % 64; + self.archetype_projectile_bitmap_0[i / 64 / 64] |= 1 << ((i / 64) % 64); } } // https://theorangeduck.com/page/spring-roll-call -fn simple_spring_damper_exact(x: f32, v: f32, x_goal: f32, damping: f32, dt: f32) -> (f32, f32) { +fn simple_spring_damper_exact( + x: f32, + velocity: f32, + goal: f32, + damping: f32, + delta_time: f32, +) -> (f32, f32) { let y = damping / 2.0; - let j0 = x - x_goal; - let j1 = v + j0 * y; - let eydt = (-y * dt).exp(); - (eydt * (j0 + j1 * dt) + x_goal, eydt * (v - j1 * y * dt)) + let j0 = x - goal; + let j1 = velocity + j0 * y; + let eydt = exp_f32(-y * delta_time); + ( + eydt * (j0 + j1 * delta_time) + goal, + eydt * (velocity - j1 * y * delta_time), + ) } pub fn main() { @@ -299,9 +412,11 @@ pub fn main() { width: 800, height: 600, }); + let device = create_device(narcissus_gpu::DeviceBackend::Vulkan); let thread_token = ThreadToken::new(); + let thread_token = &thread_token; let basic_pipeline = BasicPipeline::new(device.as_ref()); let text_pipeline = TextPipeline::new(device.as_ref()); @@ -363,55 +478,58 @@ pub fn main() { ); let mut cmd_encoder = device.request_cmd_encoder(&frame, &thread_token); + { + let cmd_encoder = &mut cmd_encoder; - device.cmd_barrier( - &mut cmd_encoder, - None, - &[ - ImageBarrier::layout_optimal( - &[Access::None], - &[Access::ShaderSampledImageRead], - glyph_atlas, - ImageAspectFlags::COLOR, - ), - ImageBarrier::layout_optimal( - &[Access::None], + device.cmd_barrier( + cmd_encoder, + None, + &[ + ImageBarrier::layout_optimal( + &[Access::None], + &[Access::ShaderSampledImageRead], + glyph_atlas, + ImageAspectFlags::COLOR, + ), + ImageBarrier::layout_optimal( + &[Access::None], + &[Access::TransferWrite], + blåhaj_image, + ImageAspectFlags::COLOR, + ), + ], + ); + + device.cmd_copy_buffer_to_image( + cmd_encoder, + blåhaj_buffer.to_arg(), + blåhaj_image, + ImageLayout::Optimal, + &[BufferImageCopy { + buffer_offset: 0, + buffer_row_length: 0, + buffer_image_height: 0, + image_subresource: default(), + image_offset: Offset3d { x: 0, y: 0, z: 0 }, + image_extent: Extent3d { + width: blåhaj_image_data.width() as u32, + height: blåhaj_image_data.width() as u32, + depth: 1, + }, + }], + ); + + device.cmd_barrier( + cmd_encoder, + None, + &[ImageBarrier::layout_optimal( &[Access::TransferWrite], + &[Access::FragmentShaderSampledImageRead], blåhaj_image, ImageAspectFlags::COLOR, - ), - ], - ); - - device.cmd_copy_buffer_to_image( - &mut cmd_encoder, - blåhaj_buffer.to_arg(), - blåhaj_image, - ImageLayout::Optimal, - &[BufferImageCopy { - buffer_offset: 0, - buffer_row_length: 0, - buffer_image_height: 0, - image_subresource: default(), - image_offset: Offset3d { x: 0, y: 0, z: 0 }, - image_extent: Extent3d { - width: blåhaj_image_data.width() as u32, - height: blåhaj_image_data.width() as u32, - depth: 1, - }, - }], - ); - - device.cmd_barrier( - &mut cmd_encoder, - None, - &[ImageBarrier::layout_optimal( - &[Access::TransferWrite], - &[Access::FragmentShaderSampledImageRead], - blåhaj_image, - ImageAspectFlags::COLOR, - )], - ); + )], + ); + } device.submit(&frame, cmd_encoder); device.end_frame(frame); @@ -421,29 +539,6 @@ pub fn main() { let mut depth_height = 0; let mut depth_image = default(); - let mut rng = Pcg64::new(); - let shark_distance = 4.0; - - let mut shark_transforms = Vec::new(); - - // Shark 0 is the ultimate shark of player control! - shark_transforms.push(Affine3 { - matrix: Mat3::IDENTITY, - translate: Vec3::ZERO, - }); - - for z in 0..NUM_SHARKS { - for x in 0..NUM_SHARKS { - let x = x as f32 * shark_distance - NUM_SHARKS as f32 / 2.0 * shark_distance; - let z = z as f32 * shark_distance - NUM_SHARKS as f32 / 2.0 * shark_distance; - shark_transforms.push(Affine3 { - matrix: Mat3::from_axis_rotation(Vec3::Y, HalfTurn::new(rng.next_f32() * 2.0)) - * Mat3::from_scale(Vec3::new(0.5, 0.5, 0.5)), - translate: vec3(x, 0.0, z), - }) - } - } - let mut font_size_str = String::new(); let mut primitive_instances = Vec::new(); let mut primitive_vertices = Vec::new(); @@ -453,391 +548,469 @@ pub fn main() { let mut action_queue = Vec::new(); let mut game_state = GameState::new(); - let start_time = Instant::now(); + let mut basic_transforms = vec![]; 'main: loop { let frame = device.begin_frame(); + { + let frame = &frame; + + let (width, height, swapchain_image) = loop { + let (width, height) = main_window.extent(); + if let Ok(result) = device.acquire_swapchain( + &frame, + main_window.upcast(), + width, + height, + ImageFormat::BGRA8_SRGB, + ) { + break result; + } + }; - let (width, height, swapchain_image) = loop { - let (width, height) = main_window.extent(); - if let Ok(result) = device.acquire_swapchain( - &frame, - main_window.upcast(), - width, - height, - ImageFormat::BGRA8_SRGB, - ) { - break result; - } - }; - - 'poll_events: while let Some(event) = app.poll_event() { - use Event::*; - match event { - KeyPress { - window_id: _, - key, - repeat, - pressed, - modifiers: _, - } => { - if repeat { - continue 'poll_events; - } - - if key == Key::Escape { - break 'main; - } - - { - let value = match pressed { - PressedState::Released => 0.0, - PressedState::Pressed => 1.0, - }; - - if key == Key::Left || key == Key::A { - action_queue.push(ActionEvent { - action: Action::Left, - value, - }) + 'poll_events: while let Some(event) = app.poll_event() { + use Event::*; + match event { + KeyPress { + window_id: _, + key, + repeat, + pressed, + modifiers: _, + } => { + if repeat { + continue 'poll_events; } - if key == Key::Right || key == Key::D { - action_queue.push(ActionEvent { - action: Action::Right, - value, - }) - } - if key == Key::Up || key == Key::W { - action_queue.push(ActionEvent { - action: Action::Up, - value, - }) - } - if key == Key::Down || key == Key::S { - action_queue.push(ActionEvent { - action: Action::Down, - value, - }) + + if key == Key::Escape { + break 'main; } - if key == Key::Space { - action_queue.push(ActionEvent { - action: Action::Damage, - value, - }) + + { + let value = match pressed { + PressedState::Released => 0.0, + PressedState::Pressed => 1.0, + }; + + if key == Key::Left || key == Key::A { + action_queue.push(ActionEvent { + action: Action::Left, + value, + }) + } + if key == Key::Right || key == Key::D { + action_queue.push(ActionEvent { + action: Action::Right, + value, + }) + } + if key == Key::Up || key == Key::W { + action_queue.push(ActionEvent { + action: Action::Up, + value, + }) + } + if key == Key::Down || key == Key::S { + action_queue.push(ActionEvent { + action: Action::Down, + value, + }) + } + if key == Key::Space { + action_queue.push(ActionEvent { + action: Action::Damage, + value, + }) + } } } + Quit => { + break 'main; + } + Close { window_id } => { + let window = app.window(window_id); + device.destroy_swapchain(window.upcast()); + } + _ => {} } - Quit => { - break 'main; - } - Close { window_id } => { - let window = app.window(window_id); - device.destroy_swapchain(window.upcast()); - } - _ => {} } - } - - game_state.tick(1.0 / 120.0, &action_queue); - action_queue.clear(); - let mut cmd_encoder = device.request_cmd_encoder(&frame, &thread_token); + game_state.tick(1.0 / 120.0, &action_queue); + action_queue.clear(); - if width != depth_width || height != depth_height { - device.destroy_image(&frame, depth_image); - depth_image = device.create_image(&ImageDesc { - memory_location: MemoryLocation::Device, - host_mapped: false, - usage: ImageUsageFlags::DEPTH_STENCIL_ATTACHMENT, - dimension: ImageDimension::Type2d, - format: ImageFormat::DEPTH_F32, - tiling: ImageTiling::Optimal, - width, - height, - depth: 1, - layer_count: 1, - mip_levels: 1, - }); + let half_turn_y = Mat3::from_axis_rotation(Vec3::Y, HalfTurn::new(0.5)); + let scale = Mat3::from_scale(Vec3::splat(0.25)); - device.cmd_barrier( - &mut cmd_encoder, - None, - &[ImageBarrier::layout_optimal( - &[Access::None], - &[Access::DepthStencilAttachmentWrite], - depth_image, - ImageAspectFlags::DEPTH, - )], - ); - - depth_width = width; - depth_height = height; - } - - let frame_start = Instant::now() - start_time; - let frame_start = frame_start.as_secs_f32() * 0.125; - - let orientation = { - let f = game_state.player.heading.normalized(); - let r = Vec3::cross(f, Vec3::Y).normalized(); - let u = Vec3::cross(r, f); - Mat3::from_rows([[r.x, u.x, -f.x], [r.y, u.y, -f.y], [r.z, u.z, -f.z]]) - } * Mat3::from_axis_rotation(Vec3::Y, HalfTurn::new(0.5)); - - shark_transforms[0].matrix = orientation; - shark_transforms[0].translate = game_state.player.position.as_vec3(); - - for (i, transform) in shark_transforms.iter_mut().skip(1).enumerate() { - let direction = if i & 1 == 0 { 1.0 } else { -1.0 }; - let (s, _) = sin_cos_pi_f32(frame_start + (i as f32) * 0.0125); - transform.translate.y = s; - transform.matrix *= Mat3::from_axis_rotation(Vec3::Y, HalfTurn::new(0.002 * direction)) - } - - let camera_from_model = game_state.camera.camera_from_model(); - let clip_from_camera = Mat4::perspective_rev_inf_zo( - HalfTurn::new(1.0 / 3.0), - width as f32 / height as f32, - 0.01, - ); - let clip_from_model = clip_from_camera * camera_from_model; - - // Do some Font Shit.' - let line0 = "Snarfe, Blåhaj! And the Quick Brown Fox jumped Over the Lazy doge."; - let line1 = "加盟国は、国際連合と協力して"; - - let mut x; - let mut y = 0.0; - - let mut rng = Pcg64::new(); - - primitive_instances.clear(); - primitive_vertices.clear(); - - for line in 0..2 { - let (font_family, font_size_px, text) = if line & 1 == 0 { - (FontFamily::RobotoRegular, 22.0, line0) - } else { - (FontFamily::NotoSansJapanese, 22.0, line1) - }; - - let font = fonts.font(font_family); - let scale = font.scale_for_size_px(font_size_px); - - x = 0.0; - y += (font.ascent() - font.descent() + font.line_gap()) * scale; - - font_size_str.clear(); - write!(&mut font_size_str, "{font_size_px}: ").unwrap(); - - line_glyph_indices.clear(); - line_glyph_indices.extend(font_size_str.chars().chain(text.chars()).map(|c| { - font.glyph_index(c) - .unwrap_or_else(|| font.glyph_index('□').unwrap()) - })); - - line_kern_advances.clear(); - line_kern_advances.push(0.0); - line_kern_advances.extend( - array_windows(line_glyph_indices.as_slice()) - .map(|&[prev_index, next_index]| font.kerning_advance(prev_index, next_index)), - ); - - 'repeat_str: for _ in 0.. { - for (glyph_index, advance) in line_glyph_indices - .iter() - .copied() - .zip(line_kern_advances.iter().copied()) - { - if x >= width as f32 { - break 'repeat_str; - } - - let touched_glyph_index = - glyph_cache.touch_glyph(font_family, glyph_index, font_size_px); - - let HorizontalMetrics { - advance_width, - left_side_bearing: _, - } = font.horizontal_metrics(glyph_index); - - x += advance * scale; - - let color = - *rng.array_select(&[0xfffac228, 0xfff57d15, 0xffd44842, 0xff9f2a63]); + fn rotate_dir(dir: Vec3, up: Vec3) -> Mat3 { + let f = dir.normalized(); + let r = Vec3::cross(f, up).normalized(); + let u = Vec3::cross(r, f); + Mat3::from_rows([[r.x, u.x, -f.x], [r.y, u.y, -f.y], [r.z, u.z, -f.z]]) + } - let instance_index = primitive_instances.len() as u32; - primitive_instances.push(PrimitiveInstance { - x, - y, - touched_glyph_index, - color, - }); - let glyph_vertices = &[ - PrimitiveVertex::glyph(0, instance_index), - PrimitiveVertex::glyph(1, instance_index), - PrimitiveVertex::glyph(2, instance_index), - PrimitiveVertex::glyph(2, instance_index), - PrimitiveVertex::glyph(1, instance_index), - PrimitiveVertex::glyph(3, instance_index), - ]; - primitive_vertices.extend_from_slice(glyph_vertices); - - x += advance_width * scale; + let matrix = rotate_dir(game_state.player.heading, Vec3::Y) * half_turn_y; + let translation = game_state.player.position.as_vec3(); + basic_transforms.push(Affine3::new(matrix, translation)); + + // Render projectiles + for base in BitIter::new(game_state.archetype_projectile_bitmap_0.iter().copied()) { + for i in BitIter::new(std::iter::once( + game_state.archetype_projectile_bitmap_1[base], + )) { + let i = base * 64 + i; + let translation = vec3( + game_state.archetype_projectile[i / 8].position_x[i % 8], + 0.0, + game_state.archetype_projectile[i / 8].position_z[i % 8], + ); + let velocity = vec3( + game_state.archetype_projectile[i / 8].velocity_x[i % 8], + 0.0, + game_state.archetype_projectile[i / 8].velocity_z[i % 8], + ); + + let matrix = rotate_dir(velocity, Vec3::Y) * half_turn_y * scale; + basic_transforms.push(Affine3::new(matrix, translation)) } } - } - - let atlas_width = glyph_cache.width() as u32; - let atlas_height = glyph_cache.height() as u32; - - let (touched_glyphs, texture) = glyph_cache.update_atlas(); - - // If the atlas has been updated, we need to upload it to the GPU. - if let Some(texture) = texture { - let width = atlas_width; - let height = atlas_height; - let image = glyph_atlas; - - let buffer = device.request_transient_buffer_with_data( - &frame, - &thread_token, - BufferUsageFlags::TRANSFER, - texture, - ); - device.cmd_barrier( - &mut cmd_encoder, - None, - &[ImageBarrier::layout_optimal( - &[Access::ShaderSampledImageRead], - &[Access::TransferWrite], - image, - ImageAspectFlags::COLOR, - )], + let camera_from_model = game_state.camera.camera_from_model(); + let clip_from_camera = Mat4::perspective_rev_inf_zo( + HalfTurn::new(1.0 / 3.0), + width as f32 / height as f32, + 0.01, ); - - device.cmd_copy_buffer_to_image( - &mut cmd_encoder, - buffer.to_arg(), - image, - ImageLayout::Optimal, - &[BufferImageCopy { - buffer_offset: 0, - buffer_row_length: 0, - buffer_image_height: 0, - image_subresource: default(), - image_offset: Offset3d { x: 0, y: 0, z: 0 }, - image_extent: Extent3d { + let clip_from_model = clip_from_camera * camera_from_model; + + let mut cmd_encoder = device.request_cmd_encoder(&frame, &thread_token); + { + let cmd_encoder = &mut cmd_encoder; + + if width != depth_width || height != depth_height { + device.destroy_image(&frame, depth_image); + depth_image = device.create_image(&ImageDesc { + memory_location: MemoryLocation::Device, + host_mapped: false, + usage: ImageUsageFlags::DEPTH_STENCIL_ATTACHMENT, + dimension: ImageDimension::Type2d, + format: ImageFormat::DEPTH_F32, + tiling: ImageTiling::Optimal, width, height, depth: 1, - }, - }], - ); - - device.cmd_barrier( - &mut cmd_encoder, - None, - &[ImageBarrier::layout_optimal( - &[Access::TransferWrite], - &[Access::FragmentShaderSampledImageRead], - image, - ImageAspectFlags::COLOR, - )], - ); - } - - device.cmd_begin_rendering( - &mut cmd_encoder, - &RenderingDesc { - x: 0, - y: 0, - width, - height, - color_attachments: &[RenderingAttachment { - image: swapchain_image, - load_op: LoadOp::Clear(ClearValue::ColorF32([1.0, 1.0, 1.0, 1.0])), - store_op: StoreOp::Store, - }], - depth_attachment: Some(RenderingAttachment { - image: depth_image, - load_op: LoadOp::Clear(ClearValue::DepthStencil { - depth: 0.0, - stencil: 0, - }), - store_op: StoreOp::DontCare, - }), - stencil_attachment: None, - }, - ); - - device.cmd_set_scissors( - &mut cmd_encoder, - &[Scissor { - offset: Offset2d { x: 0, y: 0 }, - extent: Extent2d { width, height }, - }], - ); - - device.cmd_set_viewports( - &mut cmd_encoder, - &[Viewport { - x: 0.0, - y: 0.0, - width: width as f32, - height: height as f32, - min_depth: 0.0, - max_depth: 1.0, - }], - ); - - // Render basic stuff. - basic_pipeline.bind( - device.as_ref(), - &frame, - &thread_token, - &mut cmd_encoder, - &BasicUniforms { clip_from_model }, - &blåhaj_vertex_buffer, - &blåhaj_index_buffer, - shark_transforms.as_slice(), - blåhaj_image, - ); + layer_count: 1, + mip_levels: 1, + }); - device.cmd_draw_indexed( - &mut cmd_encoder, - blåhaj_indices.len() as u32, - shark_transforms.len() as u32, - 0, - 0, - 0, - ); + device.cmd_barrier( + cmd_encoder, + None, + &[ImageBarrier::layout_optimal( + &[Access::None], + &[Access::DepthStencilAttachmentWrite], + depth_image, + ImageAspectFlags::DEPTH, + )], + ); + + depth_width = width; + depth_height = height; + } - // Render text stuff. - text_pipeline.bind( - device.as_ref(), - &frame, - &thread_token, - &mut cmd_encoder, - &TextUniforms { - screen_width: width, - screen_height: height, - atlas_width, - atlas_height, - }, - primitive_vertices.as_slice(), - touched_glyphs, - primitive_instances.as_slice(), - glyph_atlas, - ); + // Do some Font Shit.' + let line0 = "Snarfe, Blåhaj! And the Quick Brown Fox jumped Over the Lazy doge."; + let line1 = "加盟国は、国際連合と協力して"; + + let mut x; + let mut y = 0.0; + + let mut rng = Pcg64::new(); + + primitive_instances.clear(); + primitive_vertices.clear(); + + for line in 0..2 { + let (font_family, font_size_px, text) = if line & 1 == 0 { + (FontFamily::RobotoRegular, 22.0, line0) + } else { + (FontFamily::NotoSansJapanese, 22.0, line1) + }; + + let font = fonts.font(font_family); + let scale = font.scale_for_size_px(font_size_px); + + x = 0.0; + y += (font.ascent() - font.descent() + font.line_gap()) * scale; + + font_size_str.clear(); + write!(&mut font_size_str, "{font_size_px}: ").unwrap(); + + line_glyph_indices.clear(); + line_glyph_indices.extend(font_size_str.chars().chain(text.chars()).map(|c| { + font.glyph_index(c) + .unwrap_or_else(|| font.glyph_index('□').unwrap()) + })); + + line_kern_advances.clear(); + line_kern_advances.push(0.0); + line_kern_advances.extend(array_windows(line_glyph_indices.as_slice()).map( + |&[prev_index, next_index]| font.kerning_advance(prev_index, next_index), + )); + + 'repeat_str: for _ in 0.. { + for (glyph_index, advance) in line_glyph_indices + .iter() + .copied() + .zip(line_kern_advances.iter().copied()) + { + if x >= width as f32 { + break 'repeat_str; + } + + let touched_glyph_index = + glyph_cache.touch_glyph(font_family, glyph_index, font_size_px); + + let HorizontalMetrics { + advance_width, + left_side_bearing: _, + } = font.horizontal_metrics(glyph_index); + + x += advance * scale; + + let color = *rng + .array_select(&[0xfffac228, 0xfff57d15, 0xffd44842, 0xff9f2a63]); + + let instance_index = primitive_instances.len() as u32; + primitive_instances.push(PrimitiveInstance { + x, + y, + touched_glyph_index, + color, + }); + let glyph_vertices = &[ + PrimitiveVertex::glyph(0, instance_index), + PrimitiveVertex::glyph(1, instance_index), + PrimitiveVertex::glyph(2, instance_index), + PrimitiveVertex::glyph(2, instance_index), + PrimitiveVertex::glyph(1, instance_index), + PrimitiveVertex::glyph(3, instance_index), + ]; + primitive_vertices.extend_from_slice(glyph_vertices); + + x += advance_width * scale; + } + } + } - device.cmd_draw(&mut cmd_encoder, primitive_vertices.len() as u32, 1, 0, 0); + let atlas_width = glyph_cache.width() as u32; + let atlas_height = glyph_cache.height() as u32; + + let (touched_glyphs, texture) = glyph_cache.update_atlas(); + + // If the atlas has been updated, we need to upload it to the GPU. + if let Some(texture) = texture { + let width = atlas_width; + let height = atlas_height; + let image = glyph_atlas; + + let buffer = device.request_transient_buffer_with_data( + frame, + thread_token, + BufferUsageFlags::TRANSFER, + texture, + ); + + device.cmd_barrier( + cmd_encoder, + None, + &[ImageBarrier::layout_optimal( + &[Access::ShaderSampledImageRead], + &[Access::TransferWrite], + image, + ImageAspectFlags::COLOR, + )], + ); + + device.cmd_copy_buffer_to_image( + cmd_encoder, + buffer.to_arg(), + image, + ImageLayout::Optimal, + &[BufferImageCopy { + buffer_offset: 0, + buffer_row_length: 0, + buffer_image_height: 0, + image_subresource: default(), + image_offset: Offset3d { x: 0, y: 0, z: 0 }, + image_extent: Extent3d { + width, + height, + depth: 1, + }, + }], + ); + + device.cmd_barrier( + cmd_encoder, + None, + &[ImageBarrier::layout_optimal( + &[Access::TransferWrite], + &[Access::FragmentShaderSampledImageRead], + image, + ImageAspectFlags::COLOR, + )], + ); + } - device.cmd_end_rendering(&mut cmd_encoder); + device.cmd_begin_rendering( + cmd_encoder, + &RenderingDesc { + x: 0, + y: 0, + width, + height, + color_attachments: &[RenderingAttachment { + image: swapchain_image, + load_op: LoadOp::Clear(ClearValue::ColorF32([1.0, 1.0, 1.0, 1.0])), + store_op: StoreOp::Store, + }], + depth_attachment: Some(RenderingAttachment { + image: depth_image, + load_op: LoadOp::Clear(ClearValue::DepthStencil { + depth: 0.0, + stencil: 0, + }), + store_op: StoreOp::DontCare, + }), + stencil_attachment: None, + }, + ); + + device.cmd_set_scissors( + cmd_encoder, + &[Scissor { + offset: Offset2d { x: 0, y: 0 }, + extent: Extent2d { width, height }, + }], + ); + + device.cmd_set_viewports( + cmd_encoder, + &[Viewport { + x: 0.0, + y: 0.0, + width: width as f32, + height: height as f32, + min_depth: 0.0, + max_depth: 1.0, + }], + ); + + // Render basic stuff. + { + device.cmd_set_pipeline(cmd_encoder, basic_pipeline.pipeline); + + let basic_uniforms = BasicUniforms { clip_from_model }; + + let uniform_buffer = device.request_transient_buffer_with_data( + frame, + thread_token, + BufferUsageFlags::UNIFORM, + &basic_uniforms, + ); + + let transform_buffer = device.request_transient_buffer_with_data( + frame, + thread_token, + BufferUsageFlags::STORAGE, + basic_transforms.as_slice(), + ); + + device.cmd_set_bind_group( + frame, + cmd_encoder, + basic_pipeline.uniforms_bind_group_layout, + 0, + &[Bind { + binding: 0, + array_element: 0, + typed: TypedBind::UniformBuffer(&[uniform_buffer.to_arg()]), + }], + ); + + device.cmd_set_bind_group( + frame, + cmd_encoder, + basic_pipeline.storage_bind_group_layout, + 1, + &[ + Bind { + binding: 0, + array_element: 0, + typed: TypedBind::StorageBuffer(&[blåhaj_vertex_buffer.to_arg()]), + }, + Bind { + binding: 1, + array_element: 0, + typed: TypedBind::StorageBuffer(&[transform_buffer.to_arg()]), + }, + Bind { + binding: 2, + array_element: 0, + typed: TypedBind::Sampler(&[basic_pipeline.sampler]), + }, + Bind { + binding: 3, + array_element: 0, + typed: TypedBind::Image(&[(ImageLayout::Optimal, blåhaj_image)]), + }, + ], + ); + + device.cmd_set_index_buffer( + cmd_encoder, + blåhaj_index_buffer.to_arg(), + 0, + IndexType::U16, + ); + + device.cmd_draw_indexed( + cmd_encoder, + blåhaj_indices.len() as u32, + basic_transforms.len() as u32, + 0, + 0, + 0, + ); + + // We're done with you now! + basic_transforms.clear(); + }; + + // Render text stuff. + text_pipeline.bind( + device.as_ref(), + frame, + thread_token, + cmd_encoder, + &TextUniforms { + screen_width: width, + screen_height: height, + atlas_width, + atlas_height, + }, + primitive_vertices.as_slice(), + touched_glyphs, + primitive_instances.as_slice(), + glyph_atlas, + ); - device.submit(&frame, cmd_encoder); + device.cmd_draw(cmd_encoder, primitive_vertices.len() as u32, 1, 0, 0); + device.cmd_end_rendering(cmd_encoder); + } + device.submit(&frame, cmd_encoder); + } device.end_frame(frame); } } diff --git a/title/shark/src/pipelines/basic.rs b/title/shark/src/pipelines/basic.rs index 636fac9..2460777 100644 --- a/title/shark/src/pipelines/basic.rs +++ b/title/shark/src/pipelines/basic.rs @@ -1,12 +1,11 @@ use narcissus_core::default; use narcissus_gpu::{ - Bind, BindGroupLayout, BindGroupLayoutDesc, BindGroupLayoutEntryDesc, BindingType, BlendMode, - BufferUsageFlags, CmdEncoder, CompareOp, CullingMode, Device, DeviceExt, Frame, FrontFace, - GraphicsPipelineDesc, GraphicsPipelineLayout, Image, ImageFormat, ImageLayout, IndexType, - PersistentBuffer, Pipeline, PolygonMode, Sampler, SamplerAddressMode, SamplerDesc, - SamplerFilter, ShaderDesc, ShaderStageFlags, ThreadToken, Topology, TypedBind, + BindGroupLayout, BindGroupLayoutDesc, BindGroupLayoutEntryDesc, BindingType, BlendMode, + CompareOp, CullingMode, Device, FrontFace, GraphicsPipelineDesc, GraphicsPipelineLayout, + ImageFormat, Pipeline, PolygonMode, Sampler, SamplerAddressMode, SamplerDesc, SamplerFilter, + ShaderDesc, ShaderStageFlags, Topology, }; -use narcissus_maths::{Affine3, Mat4}; +use narcissus_maths::Mat4; #[allow(unused)] #[repr(C)] @@ -115,76 +114,4 @@ impl BasicPipeline { pipeline, } } - - pub fn bind( - &self, - device: &(dyn Device + 'static), - frame: &Frame, - thread_token: &ThreadToken, - cmd_encoder: &mut CmdEncoder, - basic_uniforms: &BasicUniforms, - vertex_buffer: &PersistentBuffer, - index_buffer: &PersistentBuffer, - transforms: &[Affine3], - texture: Image, - ) { - let uniform_buffer = device.request_transient_buffer_with_data( - frame, - thread_token, - BufferUsageFlags::UNIFORM, - basic_uniforms, - ); - - let transform_buffer = device.request_transient_buffer_with_data( - frame, - thread_token, - BufferUsageFlags::STORAGE, - transforms, - ); - - device.cmd_set_pipeline(cmd_encoder, self.pipeline); - - device.cmd_set_bind_group( - frame, - cmd_encoder, - self.uniforms_bind_group_layout, - 0, - &[Bind { - binding: 0, - array_element: 0, - typed: TypedBind::UniformBuffer(&[uniform_buffer.to_arg()]), - }], - ); - - device.cmd_set_bind_group( - frame, - cmd_encoder, - self.storage_bind_group_layout, - 1, - &[ - Bind { - binding: 0, - array_element: 0, - typed: TypedBind::StorageBuffer(&[vertex_buffer.to_arg()]), - }, - Bind { - binding: 1, - array_element: 0, - typed: TypedBind::StorageBuffer(&[transform_buffer.to_arg()]), - }, - Bind { - binding: 2, - array_element: 0, - typed: TypedBind::Sampler(&[self.sampler]), - }, - Bind { - binding: 3, - array_element: 0, - typed: TypedBind::Image(&[(ImageLayout::Optimal, texture)]), - }, - ], - ); - - device.cmd_set_index_buffer(cmd_encoder, index_buffer.to_arg(), 0, IndexType::U16); - } }