From 020914ab9991d97f15883f011a74048aee4c7506 Mon Sep 17 00:00:00 2001 From: Emil Date: Sun, 21 Jun 2026 00:53:20 +0300 Subject: [PATCH] refactor: --mode window is now Vulkan, remove softbuffer fallback - --mode window = Vulkan renderer (was softbuffer CPU) - --mode vulkan removed (merged into --mode window) - softbuffer dependency removed - log dependency removed (unused) - Old window.rs (softbuffer CPU renderer) deleted - Fallback: if Vulkan init fails, falls back to --mode terminal - 109 tests, 0 failures --- Cargo.toml | 4 +- src/main.rs | 161 +---------------------------------- src/render/mod.rs | 1 - src/render/window.rs | 194 ------------------------------------------- 4 files changed, 5 insertions(+), 355 deletions(-) delete mode 100644 src/render/window.rs diff --git a/Cargo.toml b/Cargo.toml index 9e0370a..90d9256 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,12 +10,10 @@ serde = { version = "1", features = ["derive"] } serde_json = "1" fontdue = "0.9" winit = "0.30" -softbuffer = "0.4" -log = "0.4" ash = "0.38" ash-window = "0.13" raw-window-handle = "0.6" -bytemuck = "1" +bytemuck = { version = "1", features = ["derive"] } [profile.release] opt-level = 3 diff --git a/src/main.rs b/src/main.rs index 453049b..602c354 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,6 @@ use clap::Parser; use verbatim::game::Game; use verbatim::render::terminal::TerminalRenderer; -use verbatim::render::window::WindowRenderer; use verbatim::render::window_input::WindowInput; use verbatim::world::cell::MaterialId; use verbatim::ai; @@ -31,9 +30,6 @@ fn main() { match cli.mode.as_str() { "window" => { - run_window_mode(); - } - "vulkan" => { run_vulkan_mode(); } "terminal" => { @@ -204,156 +200,6 @@ fn player_info(game: &Game) -> String { } } -fn run_window_mode() { - use winit::event::{Event, WindowEvent}; - use winit::event_loop::{EventLoop, ControlFlow}; - use winit::window::Window; - - use std::time::{Duration, Instant}; - - use std::sync::Arc; - use std::num::NonZeroU32; - - let event_loop = EventLoop::new().expect("Failed to create event loop"); - let window = event_loop.create_window( - Window::default_attributes() - .with_title("Verbatim") - .with_inner_size(winit::dpi::LogicalSize::new(160 * 8, 50 * 16)) - ).expect("Failed to create window"); - let window = Arc::new(window); - - let display_handle = event_loop.owned_display_handle(); - let context = softbuffer::Context::new(display_handle) - .expect("Failed to create softbuffer context"); - let mut surface = softbuffer::Surface::new(&context, Arc::clone(&window)) - .expect("Failed to create surface"); - - let pw = 160 * 8; - let ph = 50 * 16; - surface.resize(NonZeroU32::new(pw).unwrap(), NonZeroU32::new(ph).unwrap()) - .expect("Failed to resize surface"); - - let mut renderer = WindowRenderer::new(); - let mut game = Game::new(); - game.init_world(); - - let mut input = WindowInput::new(); - let vw = renderer.width(); - let vh = renderer.height(); - - let fixed_dt = Duration::from_millis(16); - let mut last_time = Instant::now(); - let mut accumulator = Duration::ZERO; - - let mut running = true; - - event_loop.run(|event, ctrl| { - ctrl.set_control_flow(ControlFlow::Poll); - - match event { - Event::WindowEvent { event, .. } => { - match event { - WindowEvent::CloseRequested => { - running = false; - ctrl.exit(); - } - WindowEvent::KeyboardInput { event: key_event, .. } => { - input.on_key_event(key_event.physical_key, key_event.state); - } - _ => {} - } - } - Event::AboutToWait => { - if !running { - ctrl.exit(); - return; - } - - let now = Instant::now(); - let frame_time = now.duration_since(last_time); - last_time = now; - accumulator += frame_time; - - let mut steps = 0; - while accumulator >= fixed_dt && steps < 5 { - game.fixed_update(); - accumulator -= fixed_dt; - steps += 1; - } - - input.update(); - - if input.quit { - running = false; - ctrl.exit(); - return; - } - - if input.jump { - let on_ground = game.check_on_ground(); - game.player.jump(&mut game.entities, on_ground); - } - - if input.left { - game.player.move_left(&mut game.entities); - } else if input.right { - game.player.move_right(&mut game.entities); - } else { - game.player.stop_horizontal(&mut game.entities); - } - - if input.cam_left { game.cam_x -= 3; } - if input.cam_right { game.cam_x += 3; } - if input.cam_up { game.cam_y -= 3; } - if input.cam_down { game.cam_y += 3; } - - if let Some(brush_id) = input.paint { - let mat = match brush_id { - 1 => MaterialId::Sand, - 2 => MaterialId::Water, - 3 => MaterialId::Stone, - 4 => MaterialId::Lava, - 5 => MaterialId::Wood, - 6 => MaterialId::Acid, - 7 => MaterialId::Grass, - 8 => MaterialId::Dirt, - 9 => MaterialId::Fire, - 0 => MaterialId::Flesh, - 99 => MaterialId::Empty, - _ => MaterialId::Empty, - }; - let cx = game.cam_x + (vw as i32 / 2); - let cy = game.cam_y + (vh as i32 / 2); - let r = 2; - for dy in -r..=r { - for dx in -r..=r { - if dx * dx + dy * dy <= r * r + 1 { - if mat == MaterialId::Empty { - game.grid.set(cx + dx, cy + dy, verbatim::world::cell::Cell::empty()); - } else { - game.grid.set_material(cx + dx, cy + dy, mat); - } - } - } - } - } - - let (px, py) = game.player.center(&game.entities); - game.cam_x = px as i32 - (vw as i32 / 2); - game.cam_y = py as i32 - (vh as i32 / 2); - - renderer.render_to_buffer(&game.grid, &game.entities, game.cam_x, game.cam_y); - - let mut buffer = surface.buffer_mut().expect("buffer"); - let pixels = renderer.pixels(); - let len = buffer.len().min(pixels.len()); - buffer[..len].copy_from_slice(&pixels[..len]); - buffer.present().expect("present"); - } - _ => {} - } - }).expect("event loop error"); -} fn run_vulkan_mode() { use verbatim::render::vulkan::VulkanRenderer; @@ -375,9 +221,10 @@ fn run_vulkan_mode() { Ok(r) => r, Err(e) => { eprintln!("Vulkan init failed: {e}"); - eprintln!("Vulkan init failed: {e}"); - eprintln!("Falling back to softbuffer window mode..."); - run_window_mode(); + eprintln!("Falling back to terminal mode..."); + let mut renderer = TerminalRenderer::new(); + let mut game = Game::new(); + game.run(&mut renderer); return; } }; diff --git a/src/render/mod.rs b/src/render/mod.rs index 8daf61e..008ffa9 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -1,5 +1,4 @@ pub mod terminal; -pub mod window; pub mod window_input; pub mod vulkan; diff --git a/src/render/window.rs b/src/render/window.rs deleted file mode 100644 index 7d5644e..0000000 --- a/src/render/window.rs +++ /dev/null @@ -1,194 +0,0 @@ -use fontdue::{Font, FontSettings}; -use crate::entity::{EntityManager, EntityKind}; -use crate::world::cell::MaterialId; -use crate::world::grid::Grid; -use crate::world::material::MaterialRegistry; - -const CHAR_W: usize = 8; -const CHAR_H: usize = 16; -const ATLAS_COLS: usize = 16; -const ATLAS_ROWS: usize = 16; -const ATLAS_W: usize = ATLAS_COLS * CHAR_W; -const ATLAS_H: usize = ATLAS_ROWS * CHAR_H; - -pub struct WindowRenderer { - width: usize, - height: usize, - pixels: Vec, - atlas: Vec, - atlas_map: std::collections::HashMap, - font: Font, -} - -impl WindowRenderer { - pub fn new() -> Self { - let font_bytes: &[u8] = include_bytes!("../../assets/DejaVuSansMono.ttf"); - let font = Font::from_bytes(font_bytes, FontSettings { - collection_index: 0, - scale: CHAR_H as f32, - load_substitutions: false, - }).expect("Failed to load font"); - - let width = 160; - let height = 50; - - let mut atlas = vec![0u8; ATLAS_W * ATLAS_H]; - let mut atlas_map = std::collections::HashMap::new(); - - let chars: Vec = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~?".chars().collect(); - - for (i, &ch) in chars.iter().enumerate() { - let col = i % ATLAS_COLS; - let row = i / ATLAS_COLS; - atlas_map.insert(ch, (col, row)); - - let (metrics, bitmap) = font.rasterize(ch, CHAR_H as f32); - let gw = metrics.width; - let gh = metrics.height; - - for y in 0..gh.min(CHAR_H) { - for x in 0..gw.min(CHAR_W) { - let alpha = bitmap[y * gw + x]; - if alpha > 0 { - let px = (x as i32 + metrics.xmin).max(0) as usize; - let py = (y as i32 + CHAR_H as i32 - gh as i32 - metrics.ymin).max(0) as usize; - if px < CHAR_W && py < CHAR_H { - let ax = col * CHAR_W + px; - let ay = row * CHAR_H + py; - atlas[ay * ATLAS_W + ax] = alpha; - } - } - } - } - } - - Self { - width, - height, - pixels: vec![0x000A0A0F; width * CHAR_W * height * CHAR_H], - atlas, - atlas_map, - font, - } - } - - #[inline(always)] - fn blend_fast(fg: [u8; 3], bg: u32, alpha: u8) -> u32 { - if alpha == 0 { return bg; } - if alpha == 255 { - return ((fg[0] as u32) << 16) | ((fg[1] as u32) << 8) | (fg[2] as u32); - } - let a = alpha as u32; - let inv = 255 - a; - let br = (bg >> 16) & 0xFF; - let bg_ = (bg >> 8) & 0xFF; - let bb = bg & 0xFF; - let r = (fg[0] as u32 * a + br * inv) >> 8; - let g = (fg[1] as u32 * a + bg_ * inv) >> 8; - let b = (fg[2] as u32 * a + bb * inv) >> 8; - (r << 16) | (g << 8) | b - } - - pub fn render_to_buffer(&mut self, grid: &Grid, entities: &EntityManager, cam_x: i32, cam_y: i32) { - let reg = MaterialRegistry::instance(); - let screen_w = self.width * CHAR_W; - let bg_default = 0x000A0A0F; - - // Fast clear: fill with background - self.pixels.fill(bg_default); - - // Build entity overlay - let mut entity_map: std::collections::HashMap<(i32, i32), (char, [u8; 3])> = std::collections::HashMap::new(); - for e in entities.all() { - for b in &e.bodies { - if !b.alive { continue; } - let sx = b.x as i32 - cam_x; - let sy = b.y as i32 - cam_y; - if sx >= 0 && sx < self.width as i32 && sy >= 0 && sy < self.height as i32 { - let ch = match e.kind { - EntityKind::Player if e.alive => '@', - EntityKind::Goblin if e.alive => 'g', - _ => '%', - }; - let fg = if e.on_fire { - [255, 160, 40] - } else if !e.alive { - [100, 60, 60] - } else { - match e.kind { - EntityKind::Player => [255, 255, 100], - EntityKind::Goblin => [100, 220, 100], - _ => [180, 50, 50], - } - }; - entity_map.insert((sx, sy), (ch, fg)); - } - } - } - - // Draw cells - for dy in 0..self.height { - let wy = cam_y + dy as i32; - for dx in 0..self.width { - let wx = cam_x + dx as i32; - - let (ch, fg, bg) = if let Some(&(ec, ef)) = entity_map.get(&(dx as i32, dy as i32)) { - (ec, ef, bg_default) - } else if !grid.in_bounds(wx, wy) { - ('?', [80, 80, 80], bg_default) - } else { - let cell = grid.get(wx, wy); - let mat = reg.get(cell.material); - if cell.is_empty() { - (' ', [10, 10, 15], bg_default) - } else { - let fg = if cell.material == MaterialId::Lava { - let r = 200u8.saturating_add(cell.variant / 2); - [r, 60, 20] - } else { - [mat.color_fg.0, mat.color_fg.1, mat.color_fg.2] - }; - let bg = ((mat.color_bg.0 as u32) << 16) | ((mat.color_bg.1 as u32) << 8) | (mat.color_bg.2 as u32); - (mat.display_char, fg, bg) - } - }; - - // Skip drawing if it's a space on default background - if ch == ' ' && bg == bg_default { - continue; - } - - let (ac, ar) = match self.atlas_map.get(&ch) { - Some(&(c, r)) => (c, r), - None => continue, - }; - - let base_x = dx * CHAR_W; - let base_y = dy * CHAR_H; - - for y in 0..CHAR_H { - let ay = ar * CHAR_H + y; - let py = base_y + y; - let atlas_off = ay * ATLAS_W + ac * CHAR_W; - let pix_off = py * screen_w + base_x; - - for x in 0..CHAR_W { - let alpha = self.atlas[atlas_off + x]; - if alpha > 0 { - self.pixels[pix_off + x] = Self::blend_fast(fg, bg, alpha); - } else { - self.pixels[pix_off + x] = bg; - } - } - } - } - } - } - - pub fn width(&self) -> usize { self.width } - pub fn height(&self) -> usize { self.height } - pub fn pixels(&self) -> &[u32] { &self.pixels } - pub fn pixel_w(&self) -> usize { self.width * CHAR_W } - pub fn pixel_h(&self) -> usize { self.height * CHAR_H } - pub fn font(&self) -> &Font { &self.font } -}