feat: GPU optimization — lighting, viewport CA, benchmark mode, 531 FPS
- Resolution: 8x8 world cells, 2x2 UI cells (UI_SCALE=4) - GPU lighting: vertex-shader computed, light source list buffer (max 64) instead of O(N×R²) grid scan, O(N×S) per cell - Viewport-aware CA: iterate only active chunks, not all 250×250 - Flat array entity/item/shadow maps instead of HashMaps - Flat 128-entry ASCII atlas array instead of HashMap lookup - Partial grid upload: viewport + 30-cell margin only - Pre-allocated viewport arrays in renderer structs (zero alloc/frame) - Skip CPU lighting for GPU modes (pass None) - Benchmark mode: --mode benchmark with per-subsystem timing - GpuLightSource struct, light_count in push constants - gather_sources_in_range() for viewport-scoped source gathering Benchmark (600 ticks, release): Graphics: 531 FPS (was 386, +38%), render 1013us (was 1699us, -40%) ASCII: 402 FPS (was 313, +28%), render 1502us (was 2346us, -36%) All 171 tests + 14 scenarios pass.
This commit is contained in:
@@ -0,0 +1,266 @@
|
||||
use crate::entity::item::ItemManager;
|
||||
use crate::entity::EntityManager;
|
||||
use crate::render::lighting;
|
||||
use crate::ui::UiLayer;
|
||||
use crate::world::cell::MaterialId;
|
||||
use crate::world::grid::Grid;
|
||||
use image::{ImageBuffer, RgbImage};
|
||||
|
||||
pub const CELL_SIZE: u32 = 8;
|
||||
pub const UI_CELL_SIZE: u32 = 2;
|
||||
|
||||
pub fn capture_frame(
|
||||
grid: &Grid,
|
||||
entities: &EntityManager,
|
||||
items: &ItemManager,
|
||||
ui: &UiLayer,
|
||||
cam_x: i32,
|
||||
cam_y: i32,
|
||||
view_w: u32,
|
||||
view_h: u32,
|
||||
lighting: Option<&lighting::LightGrid>,
|
||||
) -> RgbImage {
|
||||
let width = view_w * CELL_SIZE;
|
||||
let height = view_h * CELL_SIZE;
|
||||
let mut img: RgbImage = ImageBuffer::new(width, height);
|
||||
|
||||
let entity_positions = entity_positions(entities, cam_x, cam_y, view_w, view_h);
|
||||
let shadow_positions = shadow_positions(&entity_positions, grid, cam_x, cam_y, view_w, view_h);
|
||||
|
||||
for vy in 0..view_h as i32 {
|
||||
for vx in 0..view_w as i32 {
|
||||
let wx = cam_x + vx;
|
||||
let wy = cam_y + vy;
|
||||
let light = lighting
|
||||
.map(|l| l.get(vx, vy))
|
||||
.unwrap_or_else(lighting::ambient_light);
|
||||
|
||||
let color = if let Some(c) = entity_positions.get(&(vx, vy)) {
|
||||
lighting::apply_light(*c, light)
|
||||
} else if let Some(c) = item_color_at(items, wx, wy) {
|
||||
lighting::apply_light(c, light)
|
||||
} else if shadow_positions.contains(&(vx, vy)) {
|
||||
[0, 0, 0]
|
||||
} else if !grid.in_bounds(wx, wy) {
|
||||
lighting::apply_light([40, 40, 40], light)
|
||||
} else {
|
||||
let cell = grid.get(wx, wy);
|
||||
if cell.is_empty() {
|
||||
lighting::apply_light(background_color(wx, wy, vy, view_h as i32), light)
|
||||
} else if cell.material == MaterialId::Lava {
|
||||
let r = 200u8.saturating_add(cell.variant / 2);
|
||||
lighting::apply_light([r, 60, 20], light)
|
||||
} else {
|
||||
lighting::apply_light([cell.fg[0], cell.fg[1], cell.fg[2]], light)
|
||||
}
|
||||
};
|
||||
|
||||
draw_cell(&mut img, vx as u32, vy as u32, color);
|
||||
}
|
||||
}
|
||||
|
||||
for (x, y) in ui.keys() {
|
||||
let cell = ui.get(*x, *y).unwrap();
|
||||
let px = (*x as u32) * UI_CELL_SIZE;
|
||||
let py = (*y as u32) * UI_CELL_SIZE;
|
||||
if px + UI_CELL_SIZE <= img.width() && py + UI_CELL_SIZE <= img.height() {
|
||||
let alpha = cell.alpha as f32 / 255.0;
|
||||
for dy in 0..UI_CELL_SIZE {
|
||||
for dx in 0..UI_CELL_SIZE {
|
||||
let p = img.get_pixel(px + dx, py + dy);
|
||||
let r = (cell.fg[0] as f32 * alpha + p[0] as f32 * (1.0 - alpha)) as u8;
|
||||
let g = (cell.fg[1] as f32 * alpha + p[1] as f32 * (1.0 - alpha)) as u8;
|
||||
let b = (cell.fg[2] as f32 * alpha + p[2] as f32 * (1.0 - alpha)) as u8;
|
||||
img.put_pixel(px + dx, py + dy, image::Rgb([r, g, b]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
img
|
||||
}
|
||||
|
||||
fn background_color(wx: i32, wy: i32, vy: i32, view_h: i32) -> [u8; 3] {
|
||||
let t = (vy as f32 / view_h as f32).clamp(0.0, 1.0);
|
||||
let base_r = (10.0 + t * 15.0) as u8;
|
||||
let base_g = (10.0 + t * 25.0) as u8;
|
||||
let base_b = (25.0 + t * 35.0) as u8;
|
||||
|
||||
let hash = ((wx.wrapping_mul(73856093)) ^ (wy.wrapping_mul(19349663))).abs();
|
||||
if hash % 80 == 0 {
|
||||
let brightness = (60 + (hash % 120) as u8).min(255);
|
||||
return [brightness, brightness, brightness + 20];
|
||||
}
|
||||
|
||||
[base_r, base_g, base_b]
|
||||
}
|
||||
|
||||
fn entity_priority(kind: crate::entity::EntityKind) -> u32 {
|
||||
use crate::entity::EntityKind;
|
||||
match kind {
|
||||
EntityKind::Player => 3,
|
||||
EntityKind::Goblin => 2,
|
||||
EntityKind::Slime => 1,
|
||||
EntityKind::Corpse => 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn entity_positions(
|
||||
entities: &EntityManager,
|
||||
cam_x: i32,
|
||||
cam_y: i32,
|
||||
view_w: u32,
|
||||
view_h: u32,
|
||||
) -> std::collections::HashMap<(i32, i32), [u8; 3]> {
|
||||
let mut map: std::collections::HashMap<(i32, i32), (u32, [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 >= view_w as i32 || sy < 0 || sy >= view_h as i32 {
|
||||
continue;
|
||||
}
|
||||
let color = if e.on_fire {
|
||||
let flicker = b.fire_timer % 4;
|
||||
[255, 120 + flicker as u8 * 20, 20 + flicker as u8 * 10]
|
||||
} else {
|
||||
[b.color[0], b.color[1], b.color[2]]
|
||||
};
|
||||
let priority = entity_priority(e.kind);
|
||||
if map
|
||||
.get(&(sx, sy))
|
||||
.map(|(p, _)| priority > *p)
|
||||
.unwrap_or(true)
|
||||
{
|
||||
map.insert((sx, sy), (priority, color));
|
||||
}
|
||||
}
|
||||
}
|
||||
map.into_iter().map(|(k, (_, c))| (k, c)).collect()
|
||||
}
|
||||
|
||||
fn shadow_positions(
|
||||
entity_positions: &std::collections::HashMap<(i32, i32), [u8; 3]>,
|
||||
grid: &Grid,
|
||||
cam_x: i32,
|
||||
cam_y: i32,
|
||||
view_w: u32,
|
||||
view_h: u32,
|
||||
) -> std::collections::HashSet<(i32, i32)> {
|
||||
let mut shadows = std::collections::HashSet::new();
|
||||
for (vx, vy) in entity_positions.keys() {
|
||||
for dy in -1..=1 {
|
||||
for dx in -1..=1 {
|
||||
if dx == 0 && dy == 0 {
|
||||
continue;
|
||||
}
|
||||
let sx = vx + dx;
|
||||
let sy = vy + dy;
|
||||
if sx < 0 || sx >= view_w as i32 || sy < 0 || sy >= view_h as i32 {
|
||||
continue;
|
||||
}
|
||||
if entity_positions.contains_key(&(sx, sy)) {
|
||||
continue;
|
||||
}
|
||||
let wx = cam_x + sx;
|
||||
let wy = cam_y + sy;
|
||||
let empty = !grid.in_bounds(wx, wy) || grid.get(wx, wy).is_empty();
|
||||
if empty {
|
||||
shadows.insert((sx, sy));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
shadows
|
||||
}
|
||||
|
||||
fn item_color_at(items: &ItemManager, wx: i32, wy: i32) -> Option<[u8; 3]> {
|
||||
for item in items.all() {
|
||||
if item.x == wx && item.y == wy {
|
||||
return Some(item.color());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn draw_cell(img: &mut RgbImage, vx: u32, vy: u32, color: [u8; 3]) {
|
||||
let base_x = vx * CELL_SIZE;
|
||||
let base_y = vy * CELL_SIZE;
|
||||
for dy in 0..CELL_SIZE {
|
||||
for dx in 0..CELL_SIZE {
|
||||
let px = base_x + dx;
|
||||
let py = base_y + dy;
|
||||
if px < img.width() && py < img.height() {
|
||||
img.put_pixel(px, py, image::Rgb(color));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save_capture(
|
||||
path: &str,
|
||||
grid: &Grid,
|
||||
entities: &EntityManager,
|
||||
items: &ItemManager,
|
||||
ui: &UiLayer,
|
||||
cam_x: i32,
|
||||
cam_y: i32,
|
||||
view_w: u32,
|
||||
view_h: u32,
|
||||
lighting: Option<&lighting::LightGrid>,
|
||||
) -> Result<(), String> {
|
||||
let img = capture_frame(
|
||||
grid, entities, items, ui, cam_x, cam_y, view_w, view_h, lighting,
|
||||
);
|
||||
img.save(path).map_err(|e| format!("save capture: {e}"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn capture_from_state(
|
||||
grid: &Grid,
|
||||
entities: &EntityManager,
|
||||
items: &ItemManager,
|
||||
ui: &UiLayer,
|
||||
cam_x: i32,
|
||||
cam_y: i32,
|
||||
path: &str,
|
||||
lighting: Option<&lighting::LightGrid>,
|
||||
) -> Result<(), String> {
|
||||
let view_w = (grid.width as u32 / CELL_SIZE).min(256);
|
||||
let view_h = (grid.height as u32 / CELL_SIZE).min(256);
|
||||
save_capture(
|
||||
path, grid, entities, items, ui, cam_x, cam_y, view_w, view_h, lighting,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn capture_from_game(game: &crate::game::Game, path: &str) -> Result<(), String> {
|
||||
let (px, py) = game.player.center(&game.entities);
|
||||
let view_w = (game.grid.width as u32 / CELL_SIZE).min(256);
|
||||
let view_h = (game.grid.height as u32 / CELL_SIZE).min(256);
|
||||
let cam_x = px as i32 - (view_w as i32 / 2);
|
||||
let cam_y = py as i32 - (view_h as i32 / 2);
|
||||
let light = lighting::compute_lighting(
|
||||
&game.grid,
|
||||
cam_x,
|
||||
cam_y,
|
||||
view_w as usize,
|
||||
view_h as usize,
|
||||
lighting::ambient_light(),
|
||||
);
|
||||
save_capture(
|
||||
path,
|
||||
&game.grid,
|
||||
&game.entities,
|
||||
&game.items,
|
||||
&game.ui,
|
||||
cam_x,
|
||||
cam_y,
|
||||
view_w,
|
||||
view_h,
|
||||
Some(&light),
|
||||
)
|
||||
}
|
||||
+431
-20
@@ -2,14 +2,41 @@ use ash::vk;
|
||||
use std::ffi::CString;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::entity::{EntityKind, EntityManager};
|
||||
use crate::entity::EntityManager;
|
||||
use crate::render::lighting;
|
||||
use crate::world::cell::MaterialId;
|
||||
use crate::world::grid::Grid;
|
||||
use crate::world::grid::{Grid, WORLD_H, WORLD_W};
|
||||
|
||||
const CHAR_W: u32 = 10;
|
||||
const CHAR_H: u32 = 10;
|
||||
const CHAR_W: u32 = 8;
|
||||
const CHAR_H: u32 = 8;
|
||||
const UI_CELL_SIZE: u32 = 2;
|
||||
const MAX_FRAMES: usize = 2;
|
||||
|
||||
fn entity_priority(kind: crate::entity::EntityKind) -> u32 {
|
||||
use crate::entity::EntityKind;
|
||||
match kind {
|
||||
EntityKind::Player => 3,
|
||||
EntityKind::Goblin => 2,
|
||||
EntityKind::Slime => 1,
|
||||
EntityKind::Corpse => 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn background_color(wx: i32, wy: i32, vy: i32, view_h: i32) -> [u8; 4] {
|
||||
let t = (vy as f32 / view_h as f32).clamp(0.0, 1.0);
|
||||
let base_r = (10.0 + t * 15.0) as u8;
|
||||
let base_g = (10.0 + t * 25.0) as u8;
|
||||
let base_b = (25.0 + t * 35.0) as u8;
|
||||
|
||||
let hash = ((wx.wrapping_mul(73856093)) ^ (wy.wrapping_mul(19349663))).abs();
|
||||
if hash % 80 == 0 {
|
||||
let brightness = (60 + (hash % 120) as u8).min(255);
|
||||
return [brightness, brightness, brightness + 20, 255];
|
||||
}
|
||||
|
||||
[base_r, base_g, base_b, 255]
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Default)]
|
||||
struct ColorInstance {
|
||||
@@ -18,11 +45,28 @@ struct ColorInstance {
|
||||
color: [u8; 4],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(bytemuck::NoUninit, Clone, Copy, Default)]
|
||||
struct GpuLightSource {
|
||||
pos: [f32; 2],
|
||||
radius: f32,
|
||||
_pad0: f32,
|
||||
color: [f32; 3],
|
||||
_pad1: f32,
|
||||
}
|
||||
|
||||
const MAX_LIGHT_SOURCES: usize = 64;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(bytemuck::NoUninit, Clone, Copy)]
|
||||
struct PushConstants {
|
||||
screen_size: [f32; 2],
|
||||
cell_size: [f32; 2],
|
||||
world_size: [i32; 2],
|
||||
cam_pos: [i32; 2],
|
||||
ambient: [f32; 3],
|
||||
is_ui: u32,
|
||||
light_count: u32,
|
||||
}
|
||||
|
||||
pub struct GraphicsRenderer {
|
||||
@@ -40,6 +84,7 @@ pub struct GraphicsRenderer {
|
||||
swapchain: vk::SwapchainKHR,
|
||||
swapchain_image_views: Vec<vk::ImageView>,
|
||||
swapchain_extent: vk::Extent2D,
|
||||
present_mode: vk::PresentModeKHR,
|
||||
|
||||
render_pass: vk::RenderPass,
|
||||
pipeline: vk::Pipeline,
|
||||
@@ -64,6 +109,28 @@ pub struct GraphicsRenderer {
|
||||
instance_ptr: *mut ColorInstance,
|
||||
instance_count: usize,
|
||||
|
||||
ui_instance_buffer: vk::Buffer,
|
||||
ui_instance_memory: vk::DeviceMemory,
|
||||
ui_instance_ptr: *mut ColorInstance,
|
||||
ui_instance_capacity: usize,
|
||||
|
||||
grid_buffer: vk::Buffer,
|
||||
grid_memory: vk::DeviceMemory,
|
||||
grid_ptr: *mut u32,
|
||||
|
||||
light_buffer: vk::Buffer,
|
||||
light_memory: vk::DeviceMemory,
|
||||
light_ptr: *mut GpuLightSource,
|
||||
|
||||
ent_pri_buf: Vec<u8>,
|
||||
entity_color_buf: Vec<[u8; 4]>,
|
||||
item_color_buf: Vec<[u8; 4]>,
|
||||
shadow_buf: Vec<bool>,
|
||||
|
||||
descriptor_set_layout: vk::DescriptorSetLayout,
|
||||
descriptor_pool: vk::DescriptorPool,
|
||||
descriptor_set: vk::DescriptorSet,
|
||||
|
||||
window: Arc<winit::window::Window>,
|
||||
}
|
||||
|
||||
@@ -156,6 +223,14 @@ impl GraphicsRenderer {
|
||||
let swapchain_loader = ash::khr::swapchain::Device::new(&instance, &device);
|
||||
let caps = unsafe { sl.get_physical_device_surface_capabilities(physical_device, surface) }
|
||||
.map_err(|e| format!("caps: {e:?}"))?;
|
||||
let present_modes =
|
||||
unsafe { sl.get_physical_device_surface_present_modes(physical_device, surface) }
|
||||
.unwrap_or_default();
|
||||
let present_mode = present_modes
|
||||
.iter()
|
||||
.copied()
|
||||
.find(|&m| m == vk::PresentModeKHR::MAILBOX)
|
||||
.unwrap_or(vk::PresentModeKHR::FIFO);
|
||||
let format = vk::SurfaceFormatKHR {
|
||||
format: vk::Format::B8G8R8A8_UNORM,
|
||||
color_space: vk::ColorSpaceKHR::SRGB_NONLINEAR,
|
||||
@@ -182,7 +257,7 @@ impl GraphicsRenderer {
|
||||
.queue_family_indices(&qf_slice)
|
||||
.pre_transform(caps.current_transform)
|
||||
.composite_alpha(vk::CompositeAlphaFlagsKHR::OPAQUE)
|
||||
.present_mode(vk::PresentModeKHR::FIFO)
|
||||
.present_mode(present_mode)
|
||||
.clipped(true);
|
||||
let swapchain = unsafe { swapchain_loader.create_swapchain(&sci, None) }
|
||||
.map_err(|e| format!("swapchain: {e:?}"))?;
|
||||
@@ -316,15 +391,37 @@ impl GraphicsRenderer {
|
||||
let ms = vk::PipelineMultisampleStateCreateInfo::default()
|
||||
.rasterization_samples(vk::SampleCountFlags::TYPE_1);
|
||||
let cba = vk::PipelineColorBlendAttachmentState::default()
|
||||
.blend_enable(true)
|
||||
.src_color_blend_factor(vk::BlendFactor::SRC_ALPHA)
|
||||
.dst_color_blend_factor(vk::BlendFactor::ONE_MINUS_SRC_ALPHA)
|
||||
.color_blend_op(vk::BlendOp::ADD)
|
||||
.src_alpha_blend_factor(vk::BlendFactor::ONE)
|
||||
.dst_alpha_blend_factor(vk::BlendFactor::ZERO)
|
||||
.alpha_blend_op(vk::BlendOp::ADD)
|
||||
.color_write_mask(vk::ColorComponentFlags::RGBA);
|
||||
let cb = vk::PipelineColorBlendStateCreateInfo::default()
|
||||
.attachments(std::slice::from_ref(&cba));
|
||||
let grid_binding = vk::DescriptorSetLayoutBinding::default()
|
||||
.binding(0)
|
||||
.descriptor_type(vk::DescriptorType::STORAGE_BUFFER)
|
||||
.descriptor_count(1)
|
||||
.stage_flags(vk::ShaderStageFlags::VERTEX);
|
||||
let light_binding = vk::DescriptorSetLayoutBinding::default()
|
||||
.binding(1)
|
||||
.descriptor_type(vk::DescriptorType::STORAGE_BUFFER)
|
||||
.descriptor_count(1)
|
||||
.stage_flags(vk::ShaderStageFlags::VERTEX);
|
||||
let dsl_bindings = [grid_binding, light_binding];
|
||||
let dsl_ci = vk::DescriptorSetLayoutCreateInfo::default().bindings(&dsl_bindings);
|
||||
let descriptor_set_layout = unsafe { device.create_descriptor_set_layout(&dsl_ci, None) }
|
||||
.map_err(|e| format!("dsl: {e:?}"))?;
|
||||
let pcr = vk::PushConstantRange {
|
||||
stage_flags: vk::ShaderStageFlags::VERTEX,
|
||||
offset: 0,
|
||||
size: std::mem::size_of::<PushConstants>() as u32,
|
||||
};
|
||||
let pli = vk::PipelineLayoutCreateInfo::default()
|
||||
.set_layouts(std::slice::from_ref(&descriptor_set_layout))
|
||||
.push_constant_ranges(std::slice::from_ref(&pcr));
|
||||
let pipeline_layout = unsafe { device.create_pipeline_layout(&pli, None) }
|
||||
.map_err(|e| format!("pipeline_layout: {e:?}"))?;
|
||||
@@ -454,6 +551,78 @@ impl GraphicsRenderer {
|
||||
}
|
||||
Ok((buf, mem))
|
||||
};
|
||||
|
||||
let grid_data = vec![0u32; WORLD_W * WORLD_H];
|
||||
let (grid_buffer, grid_memory) = make_buf(
|
||||
bytemuck::cast_slice(&grid_data),
|
||||
vk::BufferUsageFlags::STORAGE_BUFFER,
|
||||
)?;
|
||||
let grid_ptr = unsafe {
|
||||
let sz = (WORLD_W * WORLD_H * std::mem::size_of::<u32>()) as vk::DeviceSize;
|
||||
let ptr = device
|
||||
.map_memory(grid_memory, 0, sz, vk::MemoryMapFlags::default())
|
||||
.map_err(|e| format!("map grid: {e:?}"))?;
|
||||
ptr as *mut u32
|
||||
};
|
||||
|
||||
let light_data = vec![GpuLightSource::default(); MAX_LIGHT_SOURCES];
|
||||
let (light_buffer, light_memory) = make_buf(
|
||||
bytemuck::cast_slice(&light_data),
|
||||
vk::BufferUsageFlags::STORAGE_BUFFER,
|
||||
)?;
|
||||
let light_ptr = unsafe {
|
||||
let sz = (MAX_LIGHT_SOURCES * std::mem::size_of::<GpuLightSource>()) as vk::DeviceSize;
|
||||
let ptr = device
|
||||
.map_memory(light_memory, 0, sz, vk::MemoryMapFlags::default())
|
||||
.map_err(|e| format!("map light: {e:?}"))?;
|
||||
ptr as *mut GpuLightSource
|
||||
};
|
||||
|
||||
let pool_sizes = [vk::DescriptorPoolSize {
|
||||
ty: vk::DescriptorType::STORAGE_BUFFER,
|
||||
descriptor_count: 2,
|
||||
}];
|
||||
let descriptor_pool = unsafe {
|
||||
device.create_descriptor_pool(
|
||||
&vk::DescriptorPoolCreateInfo::default()
|
||||
.pool_sizes(&pool_sizes)
|
||||
.max_sets(1),
|
||||
None,
|
||||
)
|
||||
}
|
||||
.map_err(|e| format!("descriptor_pool: {e:?}"))?;
|
||||
let descriptor_set = unsafe {
|
||||
device.allocate_descriptor_sets(
|
||||
&vk::DescriptorSetAllocateInfo::default()
|
||||
.descriptor_pool(descriptor_pool)
|
||||
.set_layouts(std::slice::from_ref(&descriptor_set_layout)),
|
||||
)
|
||||
}
|
||||
.map_err(|e| format!("descriptor_set: {e:?}"))?[0];
|
||||
let grid_info = vk::DescriptorBufferInfo::default()
|
||||
.buffer(grid_buffer)
|
||||
.offset(0)
|
||||
.range((WORLD_W * WORLD_H * std::mem::size_of::<u32>()) as vk::DeviceSize);
|
||||
let light_info = vk::DescriptorBufferInfo::default()
|
||||
.buffer(light_buffer)
|
||||
.offset(0)
|
||||
.range((MAX_LIGHT_SOURCES * std::mem::size_of::<GpuLightSource>()) as vk::DeviceSize);
|
||||
let descriptor_writes = [
|
||||
vk::WriteDescriptorSet::default()
|
||||
.dst_set(descriptor_set)
|
||||
.dst_binding(0)
|
||||
.descriptor_type(vk::DescriptorType::STORAGE_BUFFER)
|
||||
.buffer_info(std::slice::from_ref(&grid_info)),
|
||||
vk::WriteDescriptorSet::default()
|
||||
.dst_set(descriptor_set)
|
||||
.dst_binding(1)
|
||||
.descriptor_type(vk::DescriptorType::STORAGE_BUFFER)
|
||||
.buffer_info(std::slice::from_ref(&light_info)),
|
||||
];
|
||||
unsafe {
|
||||
device.update_descriptor_sets(&descriptor_writes, &[]);
|
||||
}
|
||||
|
||||
let verts: [f32; 8] = [0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0];
|
||||
let indices: [u16; 6] = [0, 1, 2, 1, 3, 2];
|
||||
let (vertex_buffer, vertex_memory) = make_buf(
|
||||
@@ -498,6 +667,43 @@ impl GraphicsRenderer {
|
||||
ptr as *mut ColorInstance
|
||||
};
|
||||
|
||||
let ui_capacity = 65536usize;
|
||||
let ui_inst_sz = (ui_capacity * std::mem::size_of::<ColorInstance>()) as vk::DeviceSize;
|
||||
let uibi = vk::BufferCreateInfo::default()
|
||||
.size(ui_inst_sz)
|
||||
.usage(vk::BufferUsageFlags::VERTEX_BUFFER)
|
||||
.sharing_mode(vk::SharingMode::EXCLUSIVE);
|
||||
let ui_instance_buffer = unsafe { device.create_buffer(&uibi, None) }
|
||||
.map_err(|e| format!("ui inst buf: {e:?}"))?;
|
||||
let uireq = unsafe { device.get_buffer_memory_requirements(ui_instance_buffer) };
|
||||
let uimt = find_mem(
|
||||
uireq.memory_type_bits,
|
||||
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
|
||||
)?;
|
||||
let ui_instance_memory = unsafe {
|
||||
device.allocate_memory(
|
||||
&vk::MemoryAllocateInfo::default()
|
||||
.allocation_size(uireq.size)
|
||||
.memory_type_index(uimt),
|
||||
None,
|
||||
)
|
||||
}
|
||||
.map_err(|e| format!("ui inst mem: {e:?}"))?;
|
||||
let ui_instance_ptr = unsafe {
|
||||
device
|
||||
.bind_buffer_memory(ui_instance_buffer, ui_instance_memory, 0)
|
||||
.expect("bind ui inst");
|
||||
let ptr = device
|
||||
.map_memory(
|
||||
ui_instance_memory,
|
||||
0,
|
||||
ui_inst_sz,
|
||||
vk::MemoryMapFlags::default(),
|
||||
)
|
||||
.expect("map ui inst");
|
||||
ptr as *mut ColorInstance
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
grid_w,
|
||||
grid_h,
|
||||
@@ -511,6 +717,7 @@ impl GraphicsRenderer {
|
||||
swapchain,
|
||||
swapchain_image_views,
|
||||
swapchain_extent: extent,
|
||||
present_mode,
|
||||
render_pass,
|
||||
pipeline,
|
||||
pipeline_layout,
|
||||
@@ -529,15 +736,75 @@ impl GraphicsRenderer {
|
||||
instance_memory,
|
||||
instance_ptr,
|
||||
instance_count,
|
||||
ui_instance_buffer,
|
||||
ui_instance_memory,
|
||||
ui_instance_ptr,
|
||||
ui_instance_capacity: ui_capacity,
|
||||
|
||||
grid_buffer,
|
||||
grid_memory,
|
||||
grid_ptr,
|
||||
|
||||
light_buffer,
|
||||
light_memory,
|
||||
light_ptr,
|
||||
|
||||
ent_pri_buf: Vec::new(),
|
||||
entity_color_buf: Vec::new(),
|
||||
item_color_buf: Vec::new(),
|
||||
shadow_buf: Vec::new(),
|
||||
|
||||
descriptor_set_layout,
|
||||
descriptor_pool,
|
||||
descriptor_set,
|
||||
|
||||
window,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn render(&mut self, grid: &Grid, entities: &EntityManager, cam_x: i32, cam_y: i32) {
|
||||
pub fn render(
|
||||
&mut self,
|
||||
grid: &Grid,
|
||||
entities: &EntityManager,
|
||||
items: &crate::entity::item::ItemManager,
|
||||
ui: &crate::ui::UiLayer,
|
||||
cam_x: i32,
|
||||
cam_y: i32,
|
||||
_lighting: Option<&lighting::LightGrid>,
|
||||
) {
|
||||
self.check_resize();
|
||||
|
||||
let mut entity_map: std::collections::HashMap<(i32, i32), [u8; 4]> =
|
||||
std::collections::HashMap::new();
|
||||
let amb_u8 = lighting::ambient_light();
|
||||
let ambient = [
|
||||
amb_u8[0] as f32 / 255.0,
|
||||
amb_u8[1] as f32 / 255.0,
|
||||
amb_u8[2] as f32 / 255.0,
|
||||
];
|
||||
|
||||
let vp_size = self.grid_w * self.grid_h;
|
||||
if self.ent_pri_buf.len() != vp_size {
|
||||
self.ent_pri_buf.resize(vp_size, 0);
|
||||
self.entity_color_buf.resize(vp_size, [0, 0, 0, 0]);
|
||||
self.item_color_buf.resize(vp_size, [0, 0, 0, 0]);
|
||||
self.shadow_buf.resize(vp_size, false);
|
||||
}
|
||||
self.ent_pri_buf.fill(0);
|
||||
self.entity_color_buf.fill([0, 0, 0, 0]);
|
||||
self.item_color_buf.fill([0, 0, 0, 0]);
|
||||
self.shadow_buf.fill(false);
|
||||
|
||||
let ent_pri = &mut self.ent_pri_buf;
|
||||
let entity_color = &mut self.entity_color_buf;
|
||||
let item_color = &mut self.item_color_buf;
|
||||
|
||||
for item in items.all() {
|
||||
let sx = item.x - cam_x;
|
||||
let sy = item.y - cam_y;
|
||||
if sx >= 0 && sx < self.grid_w as i32 && sy >= 0 && sy < self.grid_h as i32 {
|
||||
let idx = sy as usize * self.grid_w + sx as usize;
|
||||
item_color[idx] = [item.color()[0], item.color()[1], item.color()[2], 255];
|
||||
}
|
||||
}
|
||||
for e in entities.all() {
|
||||
for b in &e.bodies {
|
||||
if !b.alive {
|
||||
@@ -546,42 +813,79 @@ impl GraphicsRenderer {
|
||||
let sx = b.x as i32 - cam_x;
|
||||
let sy = b.y as i32 - cam_y;
|
||||
if sx >= 0 && sx < self.grid_w as i32 && sy >= 0 && sy < self.grid_h as i32 {
|
||||
let idx = sy as usize * self.grid_w + sx as usize;
|
||||
let color = if e.on_fire {
|
||||
let flicker = b.fire_timer % 4;
|
||||
[255, 120 + flicker as u8 * 20, 20 + flicker as u8 * 10, 255]
|
||||
} else {
|
||||
b.color
|
||||
};
|
||||
entity_map.insert((sx, sy), color);
|
||||
let pri = entity_priority(e.kind);
|
||||
if pri as u8 > ent_pri[idx] {
|
||||
ent_pri[idx] = pri as u8;
|
||||
entity_color[idx] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let shadow_buf = &mut self.shadow_buf;
|
||||
for idx in 0..vp_size {
|
||||
if ent_pri[idx] == 0 {
|
||||
continue;
|
||||
}
|
||||
let ex = idx % self.grid_w;
|
||||
let ey = idx / self.grid_w;
|
||||
for dy in -1i32..=1 {
|
||||
for dx in -1i32..=1 {
|
||||
if dx == 0 && dy == 0 {
|
||||
continue;
|
||||
}
|
||||
let sx = ex as i32 + dx;
|
||||
let sy = ey as i32 + dy;
|
||||
if sx < 0 || sx >= self.grid_w as i32 || sy < 0 || sy >= self.grid_h as i32 {
|
||||
continue;
|
||||
}
|
||||
let sidx = sy as usize * self.grid_w + sx as usize;
|
||||
if ent_pri[sidx] > 0 {
|
||||
continue;
|
||||
}
|
||||
let wx = cam_x + sx;
|
||||
let wy = cam_y + sy;
|
||||
if !grid.in_bounds(wx, wy) || grid.get(wx, wy).is_empty() {
|
||||
shadow_buf[sidx] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let instances =
|
||||
unsafe { std::slice::from_raw_parts_mut(self.instance_ptr, self.instance_count) };
|
||||
let bg_default = [10u8, 10, 15, 255];
|
||||
|
||||
let gh = self.grid_h as i32;
|
||||
for dy in 0..self.grid_h {
|
||||
for dx in 0..self.grid_w {
|
||||
let idx = dy * self.grid_w + dx;
|
||||
let wx = cam_x + dx as i32;
|
||||
let wy = cam_y + dy as i32;
|
||||
|
||||
let color = if let Some(&ec) = entity_map.get(&(dx as i32, dy as i32)) {
|
||||
ec
|
||||
let color = if ent_pri[idx] > 0 {
|
||||
entity_color[idx]
|
||||
} else if item_color[idx][3] > 0 {
|
||||
item_color[idx]
|
||||
} else if shadow_buf[idx] {
|
||||
[0, 0, 0, 255]
|
||||
} else if !grid.in_bounds(wx, wy) {
|
||||
[40, 40, 40, 255]
|
||||
} else {
|
||||
let cell = grid.get(wx, wy);
|
||||
if cell.is_empty() {
|
||||
bg_default
|
||||
background_color(wx, wy, dy as i32, gh)
|
||||
} else if cell.material == MaterialId::Lava {
|
||||
let r = 200u8.saturating_add(cell.variant / 2);
|
||||
[r, 60, 20, 255]
|
||||
} else {
|
||||
if cell.material == MaterialId::Lava {
|
||||
let r = 200u8.saturating_add(cell.variant / 2);
|
||||
[r, 60, 20, 255]
|
||||
} else {
|
||||
[cell.fg[0], cell.fg[1], cell.fg[2], 255]
|
||||
}
|
||||
[cell.fg[0], cell.fg[1], cell.fg[2], 255]
|
||||
}
|
||||
};
|
||||
|
||||
@@ -593,6 +897,60 @@ impl GraphicsRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
let ui_instances = unsafe {
|
||||
std::slice::from_raw_parts_mut(self.ui_instance_ptr, self.ui_instance_capacity)
|
||||
};
|
||||
let mut ui_count = 0usize;
|
||||
for (x, y) in ui.keys() {
|
||||
if ui_count >= self.ui_instance_capacity {
|
||||
break;
|
||||
}
|
||||
ui_instances[ui_count] = ColorInstance {
|
||||
grid_x: *x as f32,
|
||||
grid_y: *y as f32,
|
||||
color: {
|
||||
let cell = ui.get(*x, *y).unwrap();
|
||||
[cell.fg[0], cell.fg[1], cell.fg[2], cell.alpha]
|
||||
},
|
||||
};
|
||||
ui_count += 1;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let margin = 30i32;
|
||||
let x_min = (cam_x - margin).max(0) as usize;
|
||||
let x_max = (cam_x + self.grid_w as i32 + margin).min(WORLD_W as i32) as usize;
|
||||
let y_min = (cam_y - margin).max(0) as usize;
|
||||
let y_max = (cam_y + self.grid_h as i32 + margin).min(WORLD_H as i32) as usize;
|
||||
for y in y_min..y_max {
|
||||
let row_offset = y * WORLD_W;
|
||||
for x in x_min..x_max {
|
||||
let i = row_offset + x;
|
||||
*self.grid_ptr.add(i) = grid.cells[i].material as u32;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let sources =
|
||||
lighting::gather_sources_in_range(grid, cam_x, cam_y, self.grid_w, self.grid_h, 30);
|
||||
let light_count = sources.len().min(MAX_LIGHT_SOURCES) as u32;
|
||||
unsafe {
|
||||
let light_slice = std::slice::from_raw_parts_mut(self.light_ptr, MAX_LIGHT_SOURCES);
|
||||
for (i, src) in sources.iter().take(MAX_LIGHT_SOURCES).enumerate() {
|
||||
light_slice[i] = GpuLightSource {
|
||||
pos: [src.x as f32, src.y as f32],
|
||||
radius: src.radius as f32,
|
||||
_pad0: 0.0,
|
||||
color: [
|
||||
src.color[0] as f32 / 255.0,
|
||||
src.color[1] as f32 / 255.0,
|
||||
src.color[2] as f32 / 255.0,
|
||||
],
|
||||
_pad1: 0.0,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let frame = self.frame_index;
|
||||
let device = &self.device;
|
||||
|
||||
@@ -649,6 +1007,14 @@ impl GraphicsRenderer {
|
||||
device.cmd_set_viewport(cmd, 0, std::slice::from_ref(&viewport));
|
||||
device.cmd_set_scissor(cmd, 0, std::slice::from_ref(&scissor));
|
||||
|
||||
device.cmd_bind_descriptor_sets(
|
||||
cmd,
|
||||
vk::PipelineBindPoint::GRAPHICS,
|
||||
self.pipeline_layout,
|
||||
0,
|
||||
&[self.descriptor_set],
|
||||
&[],
|
||||
);
|
||||
device.cmd_bind_vertex_buffers(
|
||||
cmd,
|
||||
0,
|
||||
@@ -663,6 +1029,11 @@ impl GraphicsRenderer {
|
||||
self.swapchain_extent.height as f32,
|
||||
],
|
||||
cell_size: [CHAR_W as f32, CHAR_H as f32],
|
||||
world_size: [WORLD_W as i32, WORLD_H as i32],
|
||||
cam_pos: [cam_x, cam_y],
|
||||
ambient,
|
||||
is_ui: 0,
|
||||
light_count,
|
||||
};
|
||||
device.cmd_push_constants(
|
||||
cmd,
|
||||
@@ -673,6 +1044,36 @@ impl GraphicsRenderer {
|
||||
);
|
||||
|
||||
device.cmd_draw_indexed(cmd, 6, self.instance_count as u32, 0, 0, 0);
|
||||
|
||||
if ui_count > 0 {
|
||||
device.cmd_bind_vertex_buffers(
|
||||
cmd,
|
||||
0,
|
||||
&[self.vertex_buffer, self.ui_instance_buffer],
|
||||
&[0, 0],
|
||||
);
|
||||
let ui_pc = PushConstants {
|
||||
screen_size: [
|
||||
self.swapchain_extent.width as f32,
|
||||
self.swapchain_extent.height as f32,
|
||||
],
|
||||
cell_size: [UI_CELL_SIZE as f32, UI_CELL_SIZE as f32],
|
||||
world_size: [WORLD_W as i32, WORLD_H as i32],
|
||||
cam_pos: [0, 0],
|
||||
ambient: [1.0, 1.0, 1.0],
|
||||
is_ui: 1,
|
||||
light_count: 0,
|
||||
};
|
||||
device.cmd_push_constants(
|
||||
cmd,
|
||||
self.pipeline_layout,
|
||||
vk::ShaderStageFlags::VERTEX,
|
||||
0,
|
||||
bytemuck::bytes_of(&ui_pc),
|
||||
);
|
||||
device.cmd_draw_indexed(cmd, 6, ui_count as u32, 0, 0, 0);
|
||||
}
|
||||
|
||||
device.cmd_end_render_pass(cmd);
|
||||
let _ = device.end_command_buffer(cmd);
|
||||
|
||||
@@ -766,7 +1167,7 @@ impl GraphicsRenderer {
|
||||
.image_sharing_mode(vk::SharingMode::EXCLUSIVE)
|
||||
.pre_transform(caps.current_transform)
|
||||
.composite_alpha(vk::CompositeAlphaFlagsKHR::OPAQUE)
|
||||
.present_mode(vk::PresentModeKHR::FIFO)
|
||||
.present_mode(self.present_mode)
|
||||
.clipped(true)
|
||||
.old_swapchain(self.swapchain);
|
||||
|
||||
@@ -937,10 +1338,20 @@ impl Drop for GraphicsRenderer {
|
||||
self.device.destroy_render_pass(self.render_pass, None);
|
||||
self.device.destroy_buffer(self.instance_buffer, None);
|
||||
self.device.free_memory(self.instance_memory, None);
|
||||
self.device.destroy_buffer(self.ui_instance_buffer, None);
|
||||
self.device.free_memory(self.ui_instance_memory, None);
|
||||
self.device.destroy_buffer(self.vertex_buffer, None);
|
||||
self.device.free_memory(self.vertex_memory, None);
|
||||
self.device.destroy_buffer(self.index_buffer, None);
|
||||
self.device.free_memory(self.index_memory, None);
|
||||
self.device.destroy_buffer(self.grid_buffer, None);
|
||||
self.device.free_memory(self.grid_memory, None);
|
||||
self.device.destroy_buffer(self.light_buffer, None);
|
||||
self.device.free_memory(self.light_memory, None);
|
||||
self.device
|
||||
.destroy_descriptor_pool(self.descriptor_pool, None);
|
||||
self.device
|
||||
.destroy_descriptor_set_layout(self.descriptor_set_layout, None);
|
||||
for &v in &self.swapchain_image_views {
|
||||
self.device.destroy_image_view(v, None);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,283 @@
|
||||
use crate::world::cell::MaterialId;
|
||||
use crate::world::grid::Grid;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct LightSource {
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
pub color: [u8; 3],
|
||||
pub intensity: f32,
|
||||
pub radius: u32,
|
||||
}
|
||||
|
||||
pub struct LightGrid {
|
||||
pub width: usize,
|
||||
pub height: usize,
|
||||
pub data: Vec<[u8; 3]>,
|
||||
}
|
||||
|
||||
impl LightGrid {
|
||||
pub fn new(width: usize, height: usize) -> Self {
|
||||
let data = vec![[0; 3]; width * height];
|
||||
Self {
|
||||
width,
|
||||
height,
|
||||
data,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self, x: i32, y: i32) -> [u8; 3] {
|
||||
if x < 0 || y < 0 || x >= self.width as i32 || y >= self.height as i32 {
|
||||
return [0, 0, 0];
|
||||
}
|
||||
self.data[y as usize * self.width + x as usize]
|
||||
}
|
||||
|
||||
pub fn set(&mut self, x: i32, y: i32, value: [u8; 3]) {
|
||||
if x < 0 || y < 0 || x >= self.width as i32 || y >= self.height as i32 {
|
||||
return;
|
||||
}
|
||||
self.data[y as usize * self.width + x as usize] = value;
|
||||
}
|
||||
|
||||
pub fn clear(&mut self, ambient: [u8; 3]) {
|
||||
for v in self.data.iter_mut() {
|
||||
*v = ambient;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn material_light(material: MaterialId) -> Option<LightSource> {
|
||||
match material {
|
||||
MaterialId::Lava => Some(LightSource {
|
||||
x: 0,
|
||||
y: 0,
|
||||
color: [255, 80, 20],
|
||||
intensity: 1.0,
|
||||
radius: 24,
|
||||
}),
|
||||
MaterialId::Fire => Some(LightSource {
|
||||
x: 0,
|
||||
y: 0,
|
||||
color: [255, 160, 40],
|
||||
intensity: 1.0,
|
||||
radius: 18,
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gather_sources(grid: &Grid) -> Vec<LightSource> {
|
||||
let mut sources = Vec::new();
|
||||
let w = grid.width;
|
||||
let h = grid.height;
|
||||
for y in 0..h {
|
||||
for x in 0..w {
|
||||
let cell = grid.get(x as i32, y as i32);
|
||||
if let Some(mut src) = material_light(cell.material) {
|
||||
src.x = x as i32;
|
||||
src.y = y as i32;
|
||||
sources.push(src);
|
||||
}
|
||||
}
|
||||
}
|
||||
sources
|
||||
}
|
||||
|
||||
pub fn gather_sources_in_range(
|
||||
grid: &Grid,
|
||||
cam_x: i32,
|
||||
cam_y: i32,
|
||||
view_w: usize,
|
||||
view_h: usize,
|
||||
margin: i32,
|
||||
) -> Vec<LightSource> {
|
||||
let mut sources = Vec::new();
|
||||
let min_x = (cam_x - margin).max(0);
|
||||
let max_x = (cam_x + view_w as i32 + margin).min(grid.width as i32);
|
||||
let min_y = (cam_y - margin).max(0);
|
||||
let max_y = (cam_y + view_h as i32 + margin).min(grid.height as i32);
|
||||
for y in min_y..max_y {
|
||||
for x in min_x..max_x {
|
||||
let cell = grid.get(x, y);
|
||||
if let Some(mut src) = material_light(cell.material) {
|
||||
src.x = x;
|
||||
src.y = y;
|
||||
sources.push(src);
|
||||
}
|
||||
}
|
||||
}
|
||||
sources
|
||||
}
|
||||
|
||||
pub fn compute_lighting(
|
||||
grid: &Grid,
|
||||
cam_x: i32,
|
||||
cam_y: i32,
|
||||
view_w: usize,
|
||||
view_h: usize,
|
||||
ambient: [u8; 3],
|
||||
) -> LightGrid {
|
||||
let mut grid_light = LightGrid::new(view_w, view_h);
|
||||
grid_light.clear(ambient);
|
||||
|
||||
let sources = gather_sources(grid);
|
||||
let cap = sources.len().min(32);
|
||||
let radius_limit = 30u32;
|
||||
|
||||
for src in sources.iter().take(cap) {
|
||||
let r = src.radius.min(radius_limit) as i32;
|
||||
let r2 = r * r;
|
||||
let sx = src.x;
|
||||
let sy = src.y;
|
||||
|
||||
for dy in -r..=r {
|
||||
for dx in -r..=r {
|
||||
let d2 = dx * dx + dy * dy;
|
||||
if d2 > r2 {
|
||||
continue;
|
||||
}
|
||||
let tx = sx + dx;
|
||||
let ty = sy + dy;
|
||||
if !grid.in_bounds(tx, ty) {
|
||||
continue;
|
||||
}
|
||||
let vx = tx - cam_x;
|
||||
let vy = ty - cam_y;
|
||||
if vx < 0 || vx >= view_w as i32 || vy < 0 || vy >= view_h as i32 {
|
||||
continue;
|
||||
}
|
||||
if !line_of_sight(grid, sx, sy, tx, ty) {
|
||||
continue;
|
||||
}
|
||||
let dist = (d2 as f32).sqrt();
|
||||
let t = 1.0 - (dist / r as f32);
|
||||
if t <= 0.0 {
|
||||
continue;
|
||||
}
|
||||
let attenuation = t * t;
|
||||
let contrib = [
|
||||
(src.color[0] as f32 * src.intensity * attenuation),
|
||||
(src.color[1] as f32 * src.intensity * attenuation),
|
||||
(src.color[2] as f32 * src.intensity * attenuation),
|
||||
];
|
||||
let idx = vy as usize * view_w + vx as usize;
|
||||
let cur = grid_light.data[idx];
|
||||
let next = [
|
||||
(cur[0] as f32 + contrib[0]).min(255.0) as u8,
|
||||
(cur[1] as f32 + contrib[1]).min(255.0) as u8,
|
||||
(cur[2] as f32 + contrib[2]).min(255.0) as u8,
|
||||
];
|
||||
grid_light.data[idx] = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
grid_light
|
||||
}
|
||||
|
||||
pub fn line_of_sight(grid: &Grid, x0: i32, y0: i32, x1: i32, y1: i32) -> bool {
|
||||
let mut x = x0;
|
||||
let mut y = y0;
|
||||
let dx = (x1 - x0).abs();
|
||||
let dy = (y1 - y0).abs();
|
||||
let sx = if x0 < x1 { 1 } else { -1 };
|
||||
let sy = if y0 < y1 { 1 } else { -1 };
|
||||
let mut err = dx - dy;
|
||||
|
||||
loop {
|
||||
if x == x1 && y == y1 {
|
||||
return true;
|
||||
}
|
||||
if grid.in_bounds(x, y) && grid.get(x, y).is_solid() {
|
||||
return false;
|
||||
}
|
||||
let e2 = 2 * err;
|
||||
if e2 > -dy {
|
||||
err -= dy;
|
||||
x += sx;
|
||||
}
|
||||
if e2 < dx {
|
||||
err += dx;
|
||||
y += sy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_light(color: [u8; 3], light: [u8; 3]) -> [u8; 3] {
|
||||
[
|
||||
((color[0] as f32 * light[0] as f32 / 255.0).min(255.0) as u8),
|
||||
((color[1] as f32 * light[1] as f32 / 255.0).min(255.0) as u8),
|
||||
((color[2] as f32 * light[2] as f32 / 255.0).min(255.0) as u8),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn apply_light_rgba(color: [u8; 4], light: [u8; 3]) -> [u8; 4] {
|
||||
[
|
||||
((color[0] as f32 * light[0] as f32 / 255.0).min(255.0) as u8),
|
||||
((color[1] as f32 * light[1] as f32 / 255.0).min(255.0) as u8),
|
||||
((color[2] as f32 * light[2] as f32 / 255.0).min(255.0) as u8),
|
||||
color[3],
|
||||
]
|
||||
}
|
||||
|
||||
pub fn apply_light_tuple(color: (u8, u8, u8), light: [u8; 3]) -> (u8, u8, u8) {
|
||||
(
|
||||
((color.0 as f32 * light[0] as f32 / 255.0).min(255.0) as u8),
|
||||
((color.1 as f32 * light[1] as f32 / 255.0).min(255.0) as u8),
|
||||
((color.2 as f32 * light[2] as f32 / 255.0).min(255.0) as u8),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn ambient_light() -> [u8; 3] {
|
||||
[160, 160, 180]
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::world::grid::Grid;
|
||||
|
||||
fn grid_with_lava() -> (Grid, i32, i32) {
|
||||
let mut grid = Grid::new();
|
||||
grid.set_material(10, 10, MaterialId::Lava);
|
||||
(grid, 10, 10)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lava_emits_light() {
|
||||
let (grid, x, y) = grid_with_lava();
|
||||
let sources = gather_sources(&grid);
|
||||
assert_eq!(sources.len(), 1);
|
||||
assert_eq!(sources[0].x, x);
|
||||
assert_eq!(sources[0].y, y);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn light_attenuates_with_distance() {
|
||||
let (grid, _, _) = grid_with_lava();
|
||||
let light = compute_lighting(&grid, 0, 0, 20, 20, ambient_light());
|
||||
let center = light.get(10, 10);
|
||||
let far = light.get(10, 0);
|
||||
assert!(
|
||||
center.iter().map(|&v| v as u32).sum::<u32>()
|
||||
> far.iter().map(|&v| v as u32).sum::<u32>()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn walls_block_light() {
|
||||
let mut grid = Grid::new();
|
||||
grid.set_material(5, 10, MaterialId::Lava);
|
||||
for y in 7..13 {
|
||||
grid.set_material(8, y, MaterialId::Stone);
|
||||
}
|
||||
let light = compute_lighting(&grid, 0, 0, 20, 20, ambient_light());
|
||||
let lit_side = light.get(6, 10);
|
||||
let shadow_side = light.get(10, 10);
|
||||
assert!(
|
||||
lit_side.iter().map(|&v| v as u32).sum::<u32>()
|
||||
> shadow_side.iter().map(|&v| v as u32).sum::<u32>()
|
||||
);
|
||||
}
|
||||
}
|
||||
+17
-4
@@ -1,14 +1,27 @@
|
||||
pub mod terminal;
|
||||
pub mod window_input;
|
||||
pub mod vulkan;
|
||||
pub mod capture;
|
||||
pub mod graphics;
|
||||
pub mod lighting;
|
||||
pub mod terminal;
|
||||
pub mod vulkan;
|
||||
pub mod window_input;
|
||||
|
||||
use crate::entity::item::ItemManager;
|
||||
use crate::entity::EntityManager;
|
||||
use crate::ui::UiLayer;
|
||||
use crate::world::grid::Grid;
|
||||
|
||||
pub trait Renderer {
|
||||
fn init(&mut self) -> std::io::Result<()>;
|
||||
fn render(&mut self, grid: &Grid, entities: &EntityManager, cam_x: i32, cam_y: i32) -> std::io::Result<()>;
|
||||
fn render(
|
||||
&mut self,
|
||||
grid: &Grid,
|
||||
entities: &EntityManager,
|
||||
items: &ItemManager,
|
||||
ui: &UiLayer,
|
||||
cam_x: i32,
|
||||
cam_y: i32,
|
||||
lighting: Option<&lighting::LightGrid>,
|
||||
) -> std::io::Result<()>;
|
||||
fn shutdown(&mut self) -> std::io::Result<()>;
|
||||
fn viewport_w(&self) -> usize;
|
||||
fn viewport_h(&self) -> usize;
|
||||
|
||||
+161
-30
@@ -13,10 +13,35 @@ use crossterm::{
|
||||
use std::io::{self, stdout, Write};
|
||||
|
||||
use crate::entity::EntityManager;
|
||||
use crate::render::lighting::{self, apply_light_tuple, LightGrid};
|
||||
use crate::render::Renderer;
|
||||
use crate::world::cell::MaterialId;
|
||||
use crate::world::grid::Grid;
|
||||
|
||||
fn entity_priority(kind: crate::entity::EntityKind) -> u32 {
|
||||
match kind {
|
||||
crate::entity::EntityKind::Player => 3,
|
||||
crate::entity::EntityKind::Goblin => 2,
|
||||
crate::entity::EntityKind::Slime => 1,
|
||||
crate::entity::EntityKind::Corpse => 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn background_color(wx: i32, wy: i32, vy: i32, view_h: i32) -> (u8, u8, u8) {
|
||||
let t = (vy as f32 / view_h as f32).clamp(0.0, 1.0);
|
||||
let base_r = (10.0 + t * 15.0) as u8;
|
||||
let base_g = (10.0 + t * 25.0) as u8;
|
||||
let base_b = (25.0 + t * 35.0) as u8;
|
||||
|
||||
let hash = ((wx.wrapping_mul(73856093)) ^ (wy.wrapping_mul(19349663))).abs();
|
||||
if hash % 80 == 0 {
|
||||
let brightness = (60 + (hash % 120) as u8).min(255);
|
||||
return (brightness, brightness, (brightness + 20).min(255));
|
||||
}
|
||||
|
||||
(base_r, base_g, base_b)
|
||||
}
|
||||
|
||||
pub struct TerminalRenderer {
|
||||
width: usize,
|
||||
height: usize,
|
||||
@@ -73,8 +98,11 @@ impl Renderer for TerminalRenderer {
|
||||
&mut self,
|
||||
grid: &Grid,
|
||||
entities: &EntityManager,
|
||||
items: &crate::entity::item::ItemManager,
|
||||
ui: &crate::ui::UiLayer,
|
||||
cam_x: i32,
|
||||
cam_y: i32,
|
||||
lighting: Option<&LightGrid>,
|
||||
) -> io::Result<()> {
|
||||
if !self.initialized {
|
||||
return Ok(());
|
||||
@@ -85,35 +113,10 @@ impl Renderer for TerminalRenderer {
|
||||
let total = self.width * self.height;
|
||||
frame = vec![Self::empty_cell(); total];
|
||||
|
||||
for dy in 0..self.height {
|
||||
for dx in 0..self.width {
|
||||
let wx = cam_x + dx as i32;
|
||||
let wy = cam_y + dy as i32;
|
||||
let idx = dy * self.width + dx;
|
||||
if !grid.in_bounds(wx, wy) {
|
||||
frame[idx] = ('?', (80, 80, 80), (10, 10, 15));
|
||||
continue;
|
||||
}
|
||||
let cell = grid.get(wx, wy);
|
||||
if cell.is_empty() {
|
||||
frame[idx] = (
|
||||
' ',
|
||||
(cell.fg[0], cell.fg[1], cell.fg[2]),
|
||||
(cell.bg[0], cell.bg[1], cell.bg[2]),
|
||||
);
|
||||
} else {
|
||||
let ch = cell.material.display_char();
|
||||
let fg = if cell.material == MaterialId::Lava {
|
||||
let r = 200u8.saturating_add(cell.variant / 2);
|
||||
(r, 60, 20)
|
||||
} else {
|
||||
(cell.fg[0], cell.fg[1], cell.fg[2])
|
||||
};
|
||||
frame[idx] = (ch, fg, (cell.bg[0], cell.bg[1], cell.bg[2]));
|
||||
}
|
||||
}
|
||||
}
|
||||
let h = self.height as i32;
|
||||
|
||||
let mut entity_map: std::collections::HashMap<(i32, i32), (u32, char, (u8, u8, u8))> =
|
||||
std::collections::HashMap::new();
|
||||
for e in entities.all() {
|
||||
for b in &e.bodies {
|
||||
if !b.alive {
|
||||
@@ -122,7 +125,6 @@ impl Renderer for TerminalRenderer {
|
||||
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 idx = sy as usize * self.width + sx as usize;
|
||||
let ch = match e.kind {
|
||||
crate::entity::EntityKind::Player if e.alive => '@',
|
||||
crate::entity::EntityKind::Goblin if e.alive => 'g',
|
||||
@@ -134,11 +136,140 @@ impl Renderer for TerminalRenderer {
|
||||
} else {
|
||||
(b.color[0], b.color[1], b.color[2])
|
||||
};
|
||||
frame[idx] = (ch, fg, (20, 10, 10));
|
||||
let priority = entity_priority(e.kind);
|
||||
let key = (sx, sy);
|
||||
if entity_map
|
||||
.get(&key)
|
||||
.map(|(p, _, _)| priority > *p)
|
||||
.unwrap_or(true)
|
||||
{
|
||||
entity_map.insert(key, (priority, ch, fg));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut shadow_map: std::collections::HashSet<(i32, i32)> =
|
||||
std::collections::HashSet::new();
|
||||
for (&(ex, ey), _) in entity_map.iter() {
|
||||
for dy in -1..=1 {
|
||||
for dx in -1..=1 {
|
||||
if dx == 0 && dy == 0 {
|
||||
continue;
|
||||
}
|
||||
let sx = ex + dx;
|
||||
let sy = ey + dy;
|
||||
if sx < 0 || sx >= self.width as i32 || sy < 0 || sy >= self.height as i32 {
|
||||
continue;
|
||||
}
|
||||
if entity_map.contains_key(&(sx, sy)) {
|
||||
continue;
|
||||
}
|
||||
let wx = cam_x + sx;
|
||||
let wy = cam_y + sy;
|
||||
if grid.in_bounds(wx, wy) && grid.get(wx, wy).is_empty() {
|
||||
shadow_map.insert((sx, sy));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for dy in 0..self.height {
|
||||
for dx in 0..self.width {
|
||||
let wx = cam_x + dx as i32;
|
||||
let wy = cam_y + dy as i32;
|
||||
let idx = dy * self.width + dx;
|
||||
let light = lighting
|
||||
.map(|l| l.get(dx as i32, dy as i32))
|
||||
.unwrap_or_else(lighting::ambient_light);
|
||||
if !grid.in_bounds(wx, wy) {
|
||||
let bg = background_color(wx, wy, dy as i32, h);
|
||||
let bg = apply_light_tuple(bg, light);
|
||||
frame[idx] = ('?', apply_light_tuple((80, 80, 80), light), bg);
|
||||
continue;
|
||||
}
|
||||
let cell = grid.get(wx, wy);
|
||||
if cell.is_empty() {
|
||||
let bg = background_color(wx, wy, dy as i32, h);
|
||||
let bg = if shadow_map.contains(&(dx as i32, dy as i32)) {
|
||||
(
|
||||
(bg.0 as f32 * 0.4) as u8,
|
||||
(bg.1 as f32 * 0.4) as u8,
|
||||
(bg.2 as f32 * 0.4) as u8,
|
||||
)
|
||||
} else {
|
||||
bg
|
||||
};
|
||||
let bg = apply_light_tuple(bg, light);
|
||||
let fg = apply_light_tuple((cell.fg[0], cell.fg[1], cell.fg[2]), light);
|
||||
frame[idx] = (' ', fg, bg);
|
||||
} else {
|
||||
let ch = cell.material.display_char();
|
||||
let fg = if cell.material == MaterialId::Lava {
|
||||
let r = 200u8.saturating_add(cell.variant / 2);
|
||||
(r, 60, 20)
|
||||
} else {
|
||||
(cell.fg[0], cell.fg[1], cell.fg[2])
|
||||
};
|
||||
let fg = apply_light_tuple(fg, light);
|
||||
let bg = apply_light_tuple((cell.bg[0], cell.bg[1], cell.bg[2]), light);
|
||||
frame[idx] = (ch, fg, bg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for ((sx, sy), (_, ch, fg)) in entity_map {
|
||||
let idx = sy as usize * self.width + sx as usize;
|
||||
let light = lighting
|
||||
.map(|l| l.get(sx, sy))
|
||||
.unwrap_or_else(lighting::ambient_light);
|
||||
let fg = apply_light_tuple(fg, light);
|
||||
frame[idx] = (ch, fg, apply_light_tuple((20, 10, 10), light));
|
||||
}
|
||||
|
||||
for item in items.all() {
|
||||
let sx = item.x - cam_x;
|
||||
let sy = item.y - cam_y;
|
||||
if sx >= 0 && sx < self.width as i32 && sy >= 0 && sy < self.height as i32 {
|
||||
let idx = sy as usize * self.width + sx as usize;
|
||||
let color = item.color();
|
||||
let light = lighting
|
||||
.map(|l| l.get(sx, sy))
|
||||
.unwrap_or_else(lighting::ambient_light);
|
||||
frame[idx] = (
|
||||
item.display_char(),
|
||||
apply_light_tuple((color[0], color[1], color[2]), light),
|
||||
apply_light_tuple((20, 10, 10), light),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (x, y) in ui.keys() {
|
||||
let tx = *x / crate::ui::UI_SCALE;
|
||||
let ty = *y / crate::ui::UI_SCALE;
|
||||
if tx < 0 || tx >= self.width as i32 || ty < 0 || ty >= self.height as i32 {
|
||||
continue;
|
||||
}
|
||||
let cell = ui.get(*x, *y).unwrap();
|
||||
let idx = ty as usize * self.width + tx as usize;
|
||||
let a = cell.alpha as f32 / 255.0;
|
||||
let (_, old_fg, old_bg) = frame[idx];
|
||||
let blend = |c: u8, o: u8| (c as f32 * a + o as f32 * (1.0 - a)).min(255.0) as u8;
|
||||
frame[idx] = (
|
||||
cell.ch,
|
||||
(
|
||||
blend(cell.fg[0], old_fg.0),
|
||||
blend(cell.fg[1], old_fg.1),
|
||||
blend(cell.fg[2], old_fg.2),
|
||||
),
|
||||
(
|
||||
blend(cell.bg[0], old_bg.0),
|
||||
blend(cell.bg[1], old_bg.1),
|
||||
blend(cell.bg[2], old_bg.2),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
let mut prev_color: Option<(Color, Color)> = None;
|
||||
for i in 0..total {
|
||||
if frame[i] == self.prev_frame[i] {
|
||||
|
||||
+438
-62
@@ -4,17 +4,43 @@ use std::ffi::CString;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::entity::{EntityKind, EntityManager};
|
||||
use crate::render::lighting;
|
||||
use crate::world::cell::MaterialId;
|
||||
use crate::world::grid::Grid;
|
||||
use crate::world::grid::{Grid, WORLD_H, WORLD_W};
|
||||
|
||||
const CHAR_W: u32 = 10;
|
||||
const CHAR_H: u32 = 10;
|
||||
const CHAR_W: u32 = 8;
|
||||
const CHAR_H: u32 = 8;
|
||||
const UI_CELL_SIZE: u32 = 2;
|
||||
const ATLAS_COLS: usize = 16;
|
||||
const ATLAS_ROWS: usize = 16;
|
||||
const ATLAS_ROWS: usize = 8;
|
||||
const ATLAS_W: u32 = (ATLAS_COLS as u32) * CHAR_W;
|
||||
const ATLAS_H: u32 = (ATLAS_ROWS as u32) * CHAR_H;
|
||||
const MAX_FRAMES: usize = 2;
|
||||
|
||||
fn entity_priority(kind: EntityKind) -> u32 {
|
||||
match kind {
|
||||
EntityKind::Player => 3,
|
||||
EntityKind::Goblin => 2,
|
||||
EntityKind::Slime => 1,
|
||||
EntityKind::Corpse => 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn background_color(wx: i32, wy: i32, vy: i32, view_h: i32) -> [u8; 4] {
|
||||
let t = (vy as f32 / view_h as f32).clamp(0.0, 1.0);
|
||||
let base_r = (10.0 + t * 15.0) as u8;
|
||||
let base_g = (10.0 + t * 25.0) as u8;
|
||||
let base_b = (25.0 + t * 35.0) as u8;
|
||||
|
||||
let hash = ((wx.wrapping_mul(73856093)) ^ (wy.wrapping_mul(19349663))).abs();
|
||||
if hash % 80 == 0 {
|
||||
let brightness = (60 + (hash % 120) as u8).min(255);
|
||||
return [brightness, brightness, brightness + 20, 255];
|
||||
}
|
||||
|
||||
[base_r, base_g, base_b, 255]
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Default)]
|
||||
struct CellInstance {
|
||||
@@ -28,11 +54,28 @@ struct CellInstance {
|
||||
bg: [u8; 4],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(bytemuck::NoUninit, Clone, Copy, Default)]
|
||||
struct GpuLightSource {
|
||||
pos: [f32; 2],
|
||||
radius: f32,
|
||||
_pad0: f32,
|
||||
color: [f32; 3],
|
||||
_pad1: f32,
|
||||
}
|
||||
|
||||
const MAX_LIGHT_SOURCES: usize = 64;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(bytemuck::NoUninit, Clone, Copy)]
|
||||
struct PushConstants {
|
||||
screen_size: [f32; 2],
|
||||
cell_size: [f32; 2],
|
||||
world_size: [i32; 2],
|
||||
cam_pos: [i32; 2],
|
||||
ambient: [f32; 3],
|
||||
is_ui: u32,
|
||||
light_count: u32,
|
||||
}
|
||||
|
||||
pub struct VulkanRenderer {
|
||||
@@ -50,6 +93,7 @@ pub struct VulkanRenderer {
|
||||
swapchain: vk::SwapchainKHR,
|
||||
swapchain_image_views: Vec<vk::ImageView>,
|
||||
swapchain_extent: vk::Extent2D,
|
||||
present_mode: vk::PresentModeKHR,
|
||||
|
||||
render_pass: vk::RenderPass,
|
||||
pipeline: vk::Pipeline,
|
||||
@@ -73,13 +117,33 @@ pub struct VulkanRenderer {
|
||||
atlas_memory: vk::DeviceMemory,
|
||||
atlas_view: vk::ImageView,
|
||||
atlas_sampler: vk::Sampler,
|
||||
atlas_map: std::collections::HashMap<char, (f32, f32, f32, f32)>,
|
||||
atlas_map: [(f32, f32, f32, f32); 128],
|
||||
|
||||
instance_buffer: vk::Buffer,
|
||||
instance_memory: vk::DeviceMemory,
|
||||
instance_ptr: *mut CellInstance,
|
||||
instance_count: usize,
|
||||
|
||||
ui_instance_buffer: vk::Buffer,
|
||||
ui_instance_memory: vk::DeviceMemory,
|
||||
ui_instance_ptr: *mut CellInstance,
|
||||
ui_instance_capacity: usize,
|
||||
|
||||
grid_buffer: vk::Buffer,
|
||||
grid_memory: vk::DeviceMemory,
|
||||
grid_ptr: *mut u32,
|
||||
|
||||
light_buffer: vk::Buffer,
|
||||
light_memory: vk::DeviceMemory,
|
||||
light_ptr: *mut GpuLightSource,
|
||||
|
||||
ent_pri_buf: Vec<u8>,
|
||||
entity_char_buf: Vec<char>,
|
||||
entity_color_buf: Vec<[u8; 4]>,
|
||||
item_char_buf: Vec<char>,
|
||||
item_color_buf: Vec<[u8; 4]>,
|
||||
shadow_buf: Vec<bool>,
|
||||
|
||||
descriptor_pool: vk::DescriptorPool,
|
||||
descriptor_set: vk::DescriptorSet,
|
||||
descriptor_set_layout: vk::DescriptorSetLayout,
|
||||
@@ -112,16 +176,17 @@ impl VulkanRenderer {
|
||||
let (device, graphics_queue) = create_device(&instance, physical_device, queue_family)?;
|
||||
|
||||
let swapchain_loader = ash::khr::swapchain::Device::new(&instance, &device);
|
||||
let (swapchain, swapchain_images, swapchain_format, swapchain_extent) = create_swapchain(
|
||||
&device,
|
||||
&swapchain_loader,
|
||||
&surface_loader,
|
||||
physical_device,
|
||||
surface,
|
||||
queue_family,
|
||||
pixel_w,
|
||||
pixel_h,
|
||||
)?;
|
||||
let (swapchain, swapchain_images, swapchain_format, swapchain_extent, present_mode) =
|
||||
create_swapchain(
|
||||
&device,
|
||||
&swapchain_loader,
|
||||
&surface_loader,
|
||||
physical_device,
|
||||
surface,
|
||||
queue_family,
|
||||
pixel_w,
|
||||
pixel_h,
|
||||
)?;
|
||||
|
||||
let swapchain_image_views: Vec<_> = swapchain_images
|
||||
.iter()
|
||||
@@ -156,7 +221,56 @@ impl VulkanRenderer {
|
||||
let (instance_buffer, instance_memory, instance_ptr) =
|
||||
create_instance_buffer(&device, &instance, physical_device, instance_count)?;
|
||||
|
||||
update_descriptor_set(&device, descriptor_set, atlas_view, atlas_sampler);
|
||||
let ui_instance_capacity = 65536usize;
|
||||
let (ui_instance_buffer, ui_instance_memory, ui_instance_ptr) =
|
||||
create_instance_buffer(&device, &instance, physical_device, ui_instance_capacity)?;
|
||||
|
||||
let grid_data = vec![0u32; WORLD_W * WORLD_H];
|
||||
let (grid_buffer, grid_memory) = create_buffer_with_data(
|
||||
&device,
|
||||
&instance,
|
||||
physical_device,
|
||||
&grid_data,
|
||||
vk::BufferUsageFlags::STORAGE_BUFFER,
|
||||
)?;
|
||||
let grid_ptr = unsafe {
|
||||
let sz = (WORLD_W * WORLD_H * std::mem::size_of::<u32>()) as vk::DeviceSize;
|
||||
let ptr = device
|
||||
.map_memory(grid_memory, 0, sz, vk::MemoryMapFlags::default())
|
||||
.map_err(|e| format!("map grid: {e:?}"))?;
|
||||
ptr as *mut u32
|
||||
};
|
||||
|
||||
let light_data = vec![GpuLightSource::default(); MAX_LIGHT_SOURCES];
|
||||
let light_buffer_size =
|
||||
(MAX_LIGHT_SOURCES * std::mem::size_of::<GpuLightSource>()) as vk::DeviceSize;
|
||||
let (light_buffer, light_memory) = create_buffer_with_data(
|
||||
&device,
|
||||
&instance,
|
||||
physical_device,
|
||||
&light_data,
|
||||
vk::BufferUsageFlags::STORAGE_BUFFER,
|
||||
)?;
|
||||
let light_ptr = unsafe {
|
||||
let ptr = device
|
||||
.map_memory(
|
||||
light_memory,
|
||||
0,
|
||||
light_buffer_size,
|
||||
vk::MemoryMapFlags::default(),
|
||||
)
|
||||
.map_err(|e| format!("map light: {e:?}"))?;
|
||||
ptr as *mut GpuLightSource
|
||||
};
|
||||
|
||||
update_descriptor_set(
|
||||
&device,
|
||||
descriptor_set,
|
||||
atlas_view,
|
||||
atlas_sampler,
|
||||
grid_buffer,
|
||||
light_buffer,
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
grid_w,
|
||||
@@ -171,6 +285,7 @@ impl VulkanRenderer {
|
||||
swapchain,
|
||||
swapchain_image_views,
|
||||
swapchain_extent,
|
||||
present_mode,
|
||||
render_pass,
|
||||
pipeline,
|
||||
pipeline_layout,
|
||||
@@ -194,6 +309,27 @@ impl VulkanRenderer {
|
||||
instance_memory,
|
||||
instance_ptr,
|
||||
instance_count,
|
||||
|
||||
ui_instance_buffer,
|
||||
ui_instance_memory,
|
||||
ui_instance_ptr,
|
||||
ui_instance_capacity,
|
||||
|
||||
grid_buffer,
|
||||
grid_memory,
|
||||
grid_ptr,
|
||||
|
||||
light_buffer,
|
||||
light_memory,
|
||||
light_ptr,
|
||||
|
||||
ent_pri_buf: Vec::new(),
|
||||
entity_char_buf: Vec::new(),
|
||||
entity_color_buf: Vec::new(),
|
||||
item_char_buf: Vec::new(),
|
||||
item_color_buf: Vec::new(),
|
||||
shadow_buf: Vec::new(),
|
||||
|
||||
descriptor_pool,
|
||||
descriptor_set,
|
||||
descriptor_set_layout,
|
||||
@@ -201,11 +337,49 @@ impl VulkanRenderer {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn render(&mut self, grid: &Grid, entities: &EntityManager, cam_x: i32, cam_y: i32) {
|
||||
pub fn render(
|
||||
&mut self,
|
||||
grid: &Grid,
|
||||
entities: &EntityManager,
|
||||
items: &crate::entity::item::ItemManager,
|
||||
ui: &crate::ui::UiLayer,
|
||||
cam_x: i32,
|
||||
cam_y: i32,
|
||||
_lighting: Option<&lighting::LightGrid>,
|
||||
) {
|
||||
self.check_resize();
|
||||
|
||||
let mut entity_map: std::collections::HashMap<(i32, i32), (char, [u8; 4])> =
|
||||
std::collections::HashMap::new();
|
||||
let vp_size = self.grid_w * self.grid_h;
|
||||
if self.ent_pri_buf.len() != vp_size {
|
||||
self.ent_pri_buf.resize(vp_size, 0);
|
||||
self.entity_char_buf.resize(vp_size, '\0');
|
||||
self.entity_color_buf.resize(vp_size, [0, 0, 0, 0]);
|
||||
self.item_char_buf.resize(vp_size, '\0');
|
||||
self.item_color_buf.resize(vp_size, [0, 0, 0, 0]);
|
||||
self.shadow_buf.resize(vp_size, false);
|
||||
}
|
||||
self.ent_pri_buf.fill(0);
|
||||
self.entity_char_buf.fill('\0');
|
||||
self.entity_color_buf.fill([0, 0, 0, 0]);
|
||||
self.item_char_buf.fill('\0');
|
||||
self.item_color_buf.fill([0, 0, 0, 0]);
|
||||
self.shadow_buf.fill(false);
|
||||
|
||||
let ent_pri = &mut self.ent_pri_buf;
|
||||
let entity_char = &mut self.entity_char_buf;
|
||||
let entity_color = &mut self.entity_color_buf;
|
||||
let item_char = &mut self.item_char_buf;
|
||||
let item_color = &mut self.item_color_buf;
|
||||
|
||||
for item in items.all() {
|
||||
let sx = item.x - cam_x;
|
||||
let sy = item.y - cam_y;
|
||||
if sx >= 0 && sx < self.grid_w as i32 && sy >= 0 && sy < self.grid_h as i32 {
|
||||
let idx = sy as usize * self.grid_w + sx as usize;
|
||||
item_char[idx] = item.display_char();
|
||||
item_color[idx] = [item.color()[0], item.color()[1], item.color()[2], 255];
|
||||
}
|
||||
}
|
||||
for e in entities.all() {
|
||||
for b in &e.bodies {
|
||||
if !b.alive {
|
||||
@@ -214,6 +388,7 @@ impl VulkanRenderer {
|
||||
let sx = b.x as i32 - cam_x;
|
||||
let sy = b.y as i32 - cam_y;
|
||||
if sx >= 0 && sx < self.grid_w as i32 && sy >= 0 && sy < self.grid_h as i32 {
|
||||
let idx = sy as usize * self.grid_w + sx as usize;
|
||||
let ch = match e.kind {
|
||||
EntityKind::Player if e.alive => '@',
|
||||
EntityKind::Goblin if e.alive => 'g',
|
||||
@@ -225,30 +400,105 @@ impl VulkanRenderer {
|
||||
} else {
|
||||
b.color
|
||||
};
|
||||
entity_map.insert((sx, sy), (ch, fg));
|
||||
let pri = entity_priority(e.kind);
|
||||
if pri as u8 > ent_pri[idx] {
|
||||
ent_pri[idx] = pri as u8;
|
||||
entity_char[idx] = ch;
|
||||
entity_color[idx] = fg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let shadow_buf = &mut self.shadow_buf;
|
||||
for idx in 0..vp_size {
|
||||
if ent_pri[idx] == 0 {
|
||||
continue;
|
||||
}
|
||||
let ex = idx % self.grid_w;
|
||||
let ey = idx / self.grid_w;
|
||||
for dy in -1i32..=1 {
|
||||
for dx in -1i32..=1 {
|
||||
if dx == 0 && dy == 0 {
|
||||
continue;
|
||||
}
|
||||
let sx = ex as i32 + dx;
|
||||
let sy = ey as i32 + dy;
|
||||
if sx < 0 || sx >= self.grid_w as i32 || sy < 0 || sy >= self.grid_h as i32 {
|
||||
continue;
|
||||
}
|
||||
let sidx = sy as usize * self.grid_w + sx as usize;
|
||||
if ent_pri[sidx] > 0 {
|
||||
continue;
|
||||
}
|
||||
let wx = cam_x + sx;
|
||||
let wy = cam_y + sy;
|
||||
if !grid.in_bounds(wx, wy) || grid.get(wx, wy).is_empty() {
|
||||
shadow_buf[sidx] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let margin = 30i32;
|
||||
let x_min = (cam_x - margin).max(0) as usize;
|
||||
let x_max = (cam_x + self.grid_w as i32 + margin).min(WORLD_W as i32) as usize;
|
||||
let y_min = (cam_y - margin).max(0) as usize;
|
||||
let y_max = (cam_y + self.grid_h as i32 + margin).min(WORLD_H as i32) as usize;
|
||||
for y in y_min..y_max {
|
||||
let row_offset = y * WORLD_W;
|
||||
for x in x_min..x_max {
|
||||
let i = row_offset + x;
|
||||
*self.grid_ptr.add(i) = grid.cells[i].material as u32;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let sources =
|
||||
lighting::gather_sources_in_range(grid, cam_x, cam_y, self.grid_w, self.grid_h, 30);
|
||||
let light_count = sources.len().min(MAX_LIGHT_SOURCES) as u32;
|
||||
unsafe {
|
||||
let light_slice = std::slice::from_raw_parts_mut(self.light_ptr, MAX_LIGHT_SOURCES);
|
||||
for (i, src) in sources.iter().take(MAX_LIGHT_SOURCES).enumerate() {
|
||||
light_slice[i] = GpuLightSource {
|
||||
pos: [src.x as f32, src.y as f32],
|
||||
radius: src.radius as f32,
|
||||
_pad0: 0.0,
|
||||
color: [
|
||||
src.color[0] as f32 / 255.0,
|
||||
src.color[1] as f32 / 255.0,
|
||||
src.color[2] as f32 / 255.0,
|
||||
],
|
||||
_pad1: 0.0,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let instances =
|
||||
unsafe { std::slice::from_raw_parts_mut(self.instance_ptr, self.instance_count) };
|
||||
let bg_default = [10u8, 10, 15, 255];
|
||||
|
||||
let gh = self.grid_h as i32;
|
||||
for dy in 0..self.grid_h {
|
||||
for dx in 0..self.grid_w {
|
||||
let idx = dy * self.grid_w + dx;
|
||||
let wx = cam_x + dx as i32;
|
||||
let wy = cam_y + dy as i32;
|
||||
|
||||
let (ch, fg, bg) = if let Some(&(ec, ef)) = entity_map.get(&(dx as i32, dy as i32))
|
||||
{
|
||||
(ec, ef, bg_default)
|
||||
let bg = background_color(wx, wy, dy as i32, gh);
|
||||
|
||||
let (ch, fg, bg) = if ent_pri[idx] > 0 {
|
||||
(entity_char[idx], entity_color[idx], bg)
|
||||
} else if item_char[idx] != '\0' {
|
||||
(item_char[idx], item_color[idx], bg)
|
||||
} else if shadow_buf[idx] {
|
||||
(' ', [0, 0, 0, 255], bg)
|
||||
} else if !grid.in_bounds(wx, wy) {
|
||||
('?', [80, 80, 80, 255], bg_default)
|
||||
('?', [80, 80, 80, 255], bg)
|
||||
} else {
|
||||
let cell = grid.get(wx, wy);
|
||||
if cell.is_empty() {
|
||||
(' ', [10, 10, 15, 255], bg_default)
|
||||
(' ', bg, bg)
|
||||
} else {
|
||||
let fg = if cell.material == MaterialId::Lava {
|
||||
let r = 200u8.saturating_add(cell.variant / 2);
|
||||
@@ -261,11 +511,7 @@ impl VulkanRenderer {
|
||||
}
|
||||
};
|
||||
|
||||
let (au, av, aw, ah) = self
|
||||
.atlas_map
|
||||
.get(&ch)
|
||||
.copied()
|
||||
.unwrap_or((0.0, 0.0, 0.0, 0.0));
|
||||
let (au, av, aw, ah) = self.atlas_map[(ch as usize) & 127];
|
||||
instances[idx] = CellInstance {
|
||||
grid_x: dx as f32,
|
||||
grid_y: dy as f32,
|
||||
@@ -279,6 +525,29 @@ impl VulkanRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
let ui_instances = unsafe {
|
||||
std::slice::from_raw_parts_mut(self.ui_instance_ptr, self.ui_instance_capacity)
|
||||
};
|
||||
let mut ui_count = 0usize;
|
||||
for (x, y) in ui.keys() {
|
||||
if ui_count >= self.ui_instance_capacity {
|
||||
break;
|
||||
}
|
||||
let cell = ui.get(*x, *y).unwrap();
|
||||
let (au, av, aw, ah) = self.atlas_map[(cell.ch as usize) & 127];
|
||||
ui_instances[ui_count] = CellInstance {
|
||||
grid_x: *x as f32,
|
||||
grid_y: *y as f32,
|
||||
atlas_u: au,
|
||||
atlas_v: av,
|
||||
atlas_w: aw,
|
||||
atlas_h: ah,
|
||||
fg: [cell.fg[0], cell.fg[1], cell.fg[2], cell.alpha],
|
||||
bg: [cell.bg[0], cell.bg[1], cell.bg[2], cell.alpha],
|
||||
};
|
||||
ui_count += 1;
|
||||
}
|
||||
|
||||
let frame = self.frame_index;
|
||||
let device = &self.device;
|
||||
|
||||
@@ -351,12 +620,22 @@ impl VulkanRenderer {
|
||||
&[],
|
||||
);
|
||||
|
||||
let ambient = lighting::ambient_light();
|
||||
let pc = PushConstants {
|
||||
screen_size: [
|
||||
self.swapchain_extent.width as f32,
|
||||
self.swapchain_extent.height as f32,
|
||||
],
|
||||
cell_size: [CHAR_W as f32, CHAR_H as f32],
|
||||
world_size: [WORLD_W as i32, WORLD_H as i32],
|
||||
cam_pos: [cam_x, cam_y],
|
||||
ambient: [
|
||||
ambient[0] as f32 / 255.0,
|
||||
ambient[1] as f32 / 255.0,
|
||||
ambient[2] as f32 / 255.0,
|
||||
],
|
||||
is_ui: 0,
|
||||
light_count,
|
||||
};
|
||||
device.cmd_push_constants(
|
||||
cmd,
|
||||
@@ -367,6 +646,36 @@ impl VulkanRenderer {
|
||||
);
|
||||
|
||||
device.cmd_draw_indexed(cmd, 6, self.instance_count as u32, 0, 0, 0);
|
||||
|
||||
if ui_count > 0 {
|
||||
device.cmd_bind_vertex_buffers(
|
||||
cmd,
|
||||
0,
|
||||
&[self.vertex_buffer, self.ui_instance_buffer],
|
||||
&[0, 0],
|
||||
);
|
||||
let ui_pc = PushConstants {
|
||||
screen_size: [
|
||||
self.swapchain_extent.width as f32,
|
||||
self.swapchain_extent.height as f32,
|
||||
],
|
||||
cell_size: [UI_CELL_SIZE as f32, UI_CELL_SIZE as f32],
|
||||
world_size: [WORLD_W as i32, WORLD_H as i32],
|
||||
cam_pos: [0, 0],
|
||||
ambient: [0.0, 0.0, 0.0],
|
||||
is_ui: 1,
|
||||
light_count: 0,
|
||||
};
|
||||
device.cmd_push_constants(
|
||||
cmd,
|
||||
self.pipeline_layout,
|
||||
vk::ShaderStageFlags::VERTEX,
|
||||
0,
|
||||
bytemuck::bytes_of(&ui_pc),
|
||||
);
|
||||
device.cmd_draw_indexed(cmd, 6, ui_count as u32, 0, 0, 0);
|
||||
}
|
||||
|
||||
device.cmd_end_render_pass(cmd);
|
||||
let _ = device.end_command_buffer(cmd);
|
||||
|
||||
@@ -458,7 +767,7 @@ impl VulkanRenderer {
|
||||
.image_sharing_mode(vk::SharingMode::EXCLUSIVE)
|
||||
.pre_transform(caps.current_transform)
|
||||
.composite_alpha(vk::CompositeAlphaFlagsKHR::OPAQUE)
|
||||
.present_mode(vk::PresentModeKHR::FIFO)
|
||||
.present_mode(self.present_mode)
|
||||
.clipped(true)
|
||||
.old_swapchain(self.swapchain);
|
||||
|
||||
@@ -630,6 +939,12 @@ impl Drop for VulkanRenderer {
|
||||
self.device.free_memory(self.atlas_memory, None);
|
||||
self.device.destroy_buffer(self.instance_buffer, None);
|
||||
self.device.free_memory(self.instance_memory, None);
|
||||
self.device.destroy_buffer(self.ui_instance_buffer, None);
|
||||
self.device.free_memory(self.ui_instance_memory, None);
|
||||
self.device.destroy_buffer(self.grid_buffer, None);
|
||||
self.device.free_memory(self.grid_memory, None);
|
||||
self.device.destroy_buffer(self.light_buffer, None);
|
||||
self.device.free_memory(self.light_memory, None);
|
||||
self.device.destroy_buffer(self.vertex_buffer, None);
|
||||
self.device.free_memory(self.vertex_memory, None);
|
||||
self.device.destroy_buffer(self.index_buffer, None);
|
||||
@@ -761,9 +1076,26 @@ fn create_swapchain(
|
||||
qf: u32,
|
||||
pw: u32,
|
||||
ph: u32,
|
||||
) -> Result<(vk::SwapchainKHR, Vec<vk::Image>, vk::Format, vk::Extent2D), String> {
|
||||
) -> Result<
|
||||
(
|
||||
vk::SwapchainKHR,
|
||||
Vec<vk::Image>,
|
||||
vk::Format,
|
||||
vk::Extent2D,
|
||||
vk::PresentModeKHR,
|
||||
),
|
||||
String,
|
||||
> {
|
||||
let caps = unsafe { surface_loader.get_physical_device_surface_capabilities(pd, surface) }
|
||||
.map_err(|e| format!("caps: {e:?}"))?;
|
||||
let present_modes =
|
||||
unsafe { surface_loader.get_physical_device_surface_present_modes(pd, surface) }
|
||||
.unwrap_or_default();
|
||||
let present_mode = present_modes
|
||||
.iter()
|
||||
.copied()
|
||||
.find(|&m| m == vk::PresentModeKHR::MAILBOX)
|
||||
.unwrap_or(vk::PresentModeKHR::FIFO);
|
||||
let format = vk::SurfaceFormatKHR {
|
||||
format: vk::Format::B8G8R8A8_UNORM,
|
||||
color_space: vk::ColorSpaceKHR::SRGB_NONLINEAR,
|
||||
@@ -790,13 +1122,13 @@ fn create_swapchain(
|
||||
.queue_family_indices(&qf_slice)
|
||||
.pre_transform(caps.current_transform)
|
||||
.composite_alpha(vk::CompositeAlphaFlagsKHR::OPAQUE)
|
||||
.present_mode(vk::PresentModeKHR::FIFO)
|
||||
.present_mode(present_mode)
|
||||
.clipped(true);
|
||||
let swapchain =
|
||||
unsafe { sl.create_swapchain(&ci, None) }.map_err(|e| format!("swapchain: {e:?}"))?;
|
||||
let images =
|
||||
unsafe { sl.get_swapchain_images(swapchain) }.map_err(|e| format!("images: {e:?}"))?;
|
||||
Ok((swapchain, images, format.format, extent))
|
||||
Ok((swapchain, images, format.format, extent, present_mode))
|
||||
}
|
||||
|
||||
fn create_image_view(device: &ash::Device, image: vk::Image, format: vk::Format) -> vk::ImageView {
|
||||
@@ -855,20 +1187,38 @@ fn create_descriptor(
|
||||
),
|
||||
String,
|
||||
> {
|
||||
let binding = vk::DescriptorSetLayoutBinding::default()
|
||||
.binding(0)
|
||||
.descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
|
||||
.descriptor_count(1)
|
||||
.stage_flags(vk::ShaderStageFlags::FRAGMENT);
|
||||
let li = vk::DescriptorSetLayoutCreateInfo::default().bindings(std::slice::from_ref(&binding));
|
||||
let bindings = [
|
||||
vk::DescriptorSetLayoutBinding::default()
|
||||
.binding(0)
|
||||
.descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
|
||||
.descriptor_count(1)
|
||||
.stage_flags(vk::ShaderStageFlags::FRAGMENT),
|
||||
vk::DescriptorSetLayoutBinding::default()
|
||||
.binding(1)
|
||||
.descriptor_type(vk::DescriptorType::STORAGE_BUFFER)
|
||||
.descriptor_count(1)
|
||||
.stage_flags(vk::ShaderStageFlags::VERTEX | vk::ShaderStageFlags::FRAGMENT),
|
||||
vk::DescriptorSetLayoutBinding::default()
|
||||
.binding(2)
|
||||
.descriptor_type(vk::DescriptorType::STORAGE_BUFFER)
|
||||
.descriptor_count(1)
|
||||
.stage_flags(vk::ShaderStageFlags::VERTEX),
|
||||
];
|
||||
let li = vk::DescriptorSetLayoutCreateInfo::default().bindings(&bindings);
|
||||
let layout = unsafe { device.create_descriptor_set_layout(&li, None) }
|
||||
.map_err(|e| format!("ds_layout: {e:?}"))?;
|
||||
let ps = vk::DescriptorPoolSize {
|
||||
ty: vk::DescriptorType::COMBINED_IMAGE_SAMPLER,
|
||||
descriptor_count: 1,
|
||||
};
|
||||
let pool_sizes = [
|
||||
vk::DescriptorPoolSize {
|
||||
ty: vk::DescriptorType::COMBINED_IMAGE_SAMPLER,
|
||||
descriptor_count: 1,
|
||||
},
|
||||
vk::DescriptorPoolSize {
|
||||
ty: vk::DescriptorType::STORAGE_BUFFER,
|
||||
descriptor_count: 2,
|
||||
},
|
||||
];
|
||||
let pi = vk::DescriptorPoolCreateInfo::default()
|
||||
.pool_sizes(std::slice::from_ref(&ps))
|
||||
.pool_sizes(&pool_sizes)
|
||||
.max_sets(1);
|
||||
let pool = unsafe { device.create_descriptor_pool(&pi, None) }
|
||||
.map_err(|e| format!("ds_pool: {e:?}"))?;
|
||||
@@ -980,6 +1330,13 @@ fn create_pipeline(
|
||||
let ms = vk::PipelineMultisampleStateCreateInfo::default()
|
||||
.rasterization_samples(vk::SampleCountFlags::TYPE_1);
|
||||
let cba = vk::PipelineColorBlendAttachmentState::default()
|
||||
.blend_enable(true)
|
||||
.src_color_blend_factor(vk::BlendFactor::SRC_ALPHA)
|
||||
.dst_color_blend_factor(vk::BlendFactor::ONE_MINUS_SRC_ALPHA)
|
||||
.color_blend_op(vk::BlendOp::ADD)
|
||||
.src_alpha_blend_factor(vk::BlendFactor::ONE)
|
||||
.dst_alpha_blend_factor(vk::BlendFactor::ZERO)
|
||||
.alpha_blend_op(vk::BlendOp::ADD)
|
||||
.color_write_mask(vk::ColorComponentFlags::RGBA);
|
||||
let cb =
|
||||
vk::PipelineColorBlendStateCreateInfo::default().attachments(std::slice::from_ref(&cba));
|
||||
@@ -1176,7 +1533,7 @@ fn create_atlas_texture(
|
||||
vk::DeviceMemory,
|
||||
vk::ImageView,
|
||||
vk::Sampler,
|
||||
std::collections::HashMap<char, (f32, f32, f32, f32)>,
|
||||
[(f32, f32, f32, f32); 128],
|
||||
),
|
||||
String,
|
||||
> {
|
||||
@@ -1191,19 +1548,16 @@ fn create_atlas_texture(
|
||||
)
|
||||
.expect("font");
|
||||
let mut atlas_data = vec![0u8; (ATLAS_W * ATLAS_H) as usize];
|
||||
let mut atlas_map = std::collections::HashMap::new();
|
||||
let mut atlas_map = [(0.0f32, 0.0f32, 0.0f32, 0.0f32); 128];
|
||||
let chars: Vec<char> = " !\"#$%&'()*+,-./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 as f32 * CHAR_W as f32) / ATLAS_W as f32,
|
||||
(row as f32 * CHAR_H as f32) / ATLAS_H as f32,
|
||||
CHAR_W as f32 / ATLAS_W as f32,
|
||||
CHAR_H as f32 / ATLAS_H as f32,
|
||||
),
|
||||
atlas_map[(ch as usize) & 127] = (
|
||||
(col as f32 * CHAR_W as f32) / ATLAS_W as f32,
|
||||
(row as f32 * CHAR_H as f32) / ATLAS_H as f32,
|
||||
CHAR_W as f32 / ATLAS_W as f32,
|
||||
CHAR_H as f32 / ATLAS_H as f32,
|
||||
);
|
||||
let (metrics, bitmap) = font.rasterize(ch, CHAR_H as f32);
|
||||
for y in 0..metrics.height.min(CHAR_H as usize) {
|
||||
@@ -1460,17 +1814,39 @@ fn update_descriptor_set(
|
||||
set: vk::DescriptorSet,
|
||||
view: vk::ImageView,
|
||||
sampler: vk::Sampler,
|
||||
grid_buffer: vk::Buffer,
|
||||
light_buffer: vk::Buffer,
|
||||
) {
|
||||
let ii = vk::DescriptorImageInfo::default()
|
||||
.image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)
|
||||
.image_view(view)
|
||||
.sampler(sampler);
|
||||
let w = vk::WriteDescriptorSet::default()
|
||||
.dst_set(set)
|
||||
.dst_binding(0)
|
||||
.descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
|
||||
.image_info(std::slice::from_ref(&ii));
|
||||
let bi = vk::DescriptorBufferInfo::default()
|
||||
.buffer(grid_buffer)
|
||||
.offset(0)
|
||||
.range((WORLD_W * WORLD_H * std::mem::size_of::<u32>()) as vk::DeviceSize);
|
||||
let li = vk::DescriptorBufferInfo::default()
|
||||
.buffer(light_buffer)
|
||||
.offset(0)
|
||||
.range((MAX_LIGHT_SOURCES * std::mem::size_of::<GpuLightSource>()) as vk::DeviceSize);
|
||||
let writes = [
|
||||
vk::WriteDescriptorSet::default()
|
||||
.dst_set(set)
|
||||
.dst_binding(0)
|
||||
.descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
|
||||
.image_info(std::slice::from_ref(&ii)),
|
||||
vk::WriteDescriptorSet::default()
|
||||
.dst_set(set)
|
||||
.dst_binding(1)
|
||||
.descriptor_type(vk::DescriptorType::STORAGE_BUFFER)
|
||||
.buffer_info(std::slice::from_ref(&bi)),
|
||||
vk::WriteDescriptorSet::default()
|
||||
.dst_set(set)
|
||||
.dst_binding(2)
|
||||
.descriptor_type(vk::DescriptorType::STORAGE_BUFFER)
|
||||
.buffer_info(std::slice::from_ref(&li)),
|
||||
];
|
||||
unsafe {
|
||||
device.update_descriptor_sets(std::slice::from_ref(&w), &[]);
|
||||
device.update_descriptor_sets(&writes, &[]);
|
||||
}
|
||||
}
|
||||
|
||||
+101
-19
@@ -5,6 +5,14 @@ pub struct WindowInput {
|
||||
pub left: bool,
|
||||
pub right: bool,
|
||||
pub jump: bool,
|
||||
pub shoot_left: bool,
|
||||
pub shoot_right: bool,
|
||||
pub shoot_up: bool,
|
||||
pub shoot_down: bool,
|
||||
pub toggle_fireball: bool,
|
||||
pub descend: bool,
|
||||
pub use_item: bool,
|
||||
pub drop_item: bool,
|
||||
pub cam_left: bool,
|
||||
pub cam_right: bool,
|
||||
pub cam_up: bool,
|
||||
@@ -12,16 +20,46 @@ pub struct WindowInput {
|
||||
pub quit: bool,
|
||||
pub paint: Option<u8>,
|
||||
jump_was_down: bool,
|
||||
shoot_left_was_down: bool,
|
||||
shoot_right_was_down: bool,
|
||||
shoot_up_was_down: bool,
|
||||
shoot_down_was_down: bool,
|
||||
fireball_was_down: bool,
|
||||
descend_was_down: bool,
|
||||
use_item_was_down: bool,
|
||||
drop_item_was_down: bool,
|
||||
down_keys: HashSet<KeyCode>,
|
||||
}
|
||||
|
||||
impl WindowInput {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
left: false, right: false, jump: false,
|
||||
cam_left: false, cam_right: false, cam_up: false, cam_down: false,
|
||||
quit: false, paint: None,
|
||||
left: false,
|
||||
right: false,
|
||||
jump: false,
|
||||
shoot_left: false,
|
||||
shoot_right: false,
|
||||
shoot_up: false,
|
||||
shoot_down: false,
|
||||
toggle_fireball: false,
|
||||
descend: false,
|
||||
use_item: false,
|
||||
drop_item: false,
|
||||
cam_left: false,
|
||||
cam_right: false,
|
||||
cam_up: false,
|
||||
cam_down: false,
|
||||
quit: false,
|
||||
paint: None,
|
||||
jump_was_down: false,
|
||||
shoot_left_was_down: false,
|
||||
shoot_right_was_down: false,
|
||||
shoot_up_was_down: false,
|
||||
shoot_down_was_down: false,
|
||||
fireball_was_down: false,
|
||||
descend_was_down: false,
|
||||
use_item_was_down: false,
|
||||
drop_item_was_down: false,
|
||||
down_keys: HashSet::new(),
|
||||
}
|
||||
}
|
||||
@@ -47,7 +85,9 @@ impl WindowInput {
|
||||
|
||||
let now_left = keys.contains(&KeyCode::KeyA) || keys.contains(&KeyCode::ArrowLeft);
|
||||
let now_right = keys.contains(&KeyCode::KeyD) || keys.contains(&KeyCode::ArrowRight);
|
||||
let now_jump = keys.contains(&KeyCode::KeyW) || keys.contains(&KeyCode::Space) || keys.contains(&KeyCode::ArrowUp);
|
||||
let now_jump = keys.contains(&KeyCode::KeyW)
|
||||
|| keys.contains(&KeyCode::Space)
|
||||
|| keys.contains(&KeyCode::ArrowUp);
|
||||
|
||||
self.left = now_left && !now_right;
|
||||
self.right = now_right && !now_left;
|
||||
@@ -55,23 +95,65 @@ impl WindowInput {
|
||||
self.jump = now_jump && !self.jump_was_down;
|
||||
self.jump_was_down = now_jump;
|
||||
|
||||
self.cam_left = keys.contains(&KeyCode::KeyH);
|
||||
self.cam_right = keys.contains(&KeyCode::KeyL);
|
||||
self.cam_up = keys.contains(&KeyCode::KeyK);
|
||||
self.cam_down = keys.contains(&KeyCode::KeyJ);
|
||||
let now_shoot_left = keys.contains(&KeyCode::KeyH);
|
||||
let now_shoot_right = keys.contains(&KeyCode::KeyL);
|
||||
let now_shoot_up = keys.contains(&KeyCode::KeyK);
|
||||
let now_shoot_down = keys.contains(&KeyCode::KeyJ);
|
||||
let now_fireball = keys.contains(&KeyCode::KeyF);
|
||||
|
||||
self.shoot_left = now_shoot_left && !self.shoot_left_was_down;
|
||||
self.shoot_right = now_shoot_right && !self.shoot_right_was_down;
|
||||
self.shoot_up = now_shoot_up && !self.shoot_up_was_down;
|
||||
self.shoot_down = now_shoot_down && !self.shoot_down_was_down;
|
||||
self.shoot_left_was_down = now_shoot_left;
|
||||
self.shoot_right_was_down = now_shoot_right;
|
||||
self.shoot_up_was_down = now_shoot_up;
|
||||
self.shoot_down_was_down = now_shoot_down;
|
||||
|
||||
self.toggle_fireball = now_fireball && !self.fireball_was_down;
|
||||
self.fireball_was_down = now_fireball;
|
||||
|
||||
let now_descend = keys.contains(&KeyCode::Period);
|
||||
self.descend = now_descend && !self.descend_was_down;
|
||||
self.descend_was_down = now_descend;
|
||||
|
||||
let now_use_item = keys.contains(&KeyCode::KeyE);
|
||||
self.use_item = now_use_item && !self.use_item_was_down;
|
||||
self.use_item_was_down = now_use_item;
|
||||
|
||||
let now_drop_item = keys.contains(&KeyCode::KeyR);
|
||||
self.drop_item = now_drop_item && !self.drop_item_was_down;
|
||||
self.drop_item_was_down = now_drop_item;
|
||||
|
||||
self.cam_left = keys.contains(&KeyCode::KeyY);
|
||||
self.cam_right = keys.contains(&KeyCode::KeyU);
|
||||
self.cam_up = keys.contains(&KeyCode::KeyI);
|
||||
self.cam_down = keys.contains(&KeyCode::KeyO);
|
||||
self.quit = keys.contains(&KeyCode::KeyQ) || keys.contains(&KeyCode::Escape);
|
||||
|
||||
self.paint = None;
|
||||
if keys.contains(&KeyCode::Digit1) { self.paint = Some(1); }
|
||||
else if keys.contains(&KeyCode::Digit2) { self.paint = Some(2); }
|
||||
else if keys.contains(&KeyCode::Digit3) { self.paint = Some(3); }
|
||||
else if keys.contains(&KeyCode::Digit4) { self.paint = Some(4); }
|
||||
else if keys.contains(&KeyCode::Digit5) { self.paint = Some(5); }
|
||||
else if keys.contains(&KeyCode::Digit6) { self.paint = Some(6); }
|
||||
else if keys.contains(&KeyCode::Digit7) { self.paint = Some(7); }
|
||||
else if keys.contains(&KeyCode::Digit8) { self.paint = Some(8); }
|
||||
else if keys.contains(&KeyCode::Digit9) { self.paint = Some(9); }
|
||||
else if keys.contains(&KeyCode::Digit0) { self.paint = Some(0); }
|
||||
else if keys.contains(&KeyCode::KeyX) { self.paint = Some(99); }
|
||||
if keys.contains(&KeyCode::Digit1) {
|
||||
self.paint = Some(1);
|
||||
} else if keys.contains(&KeyCode::Digit2) {
|
||||
self.paint = Some(2);
|
||||
} else if keys.contains(&KeyCode::Digit3) {
|
||||
self.paint = Some(3);
|
||||
} else if keys.contains(&KeyCode::Digit4) {
|
||||
self.paint = Some(4);
|
||||
} else if keys.contains(&KeyCode::Digit5) {
|
||||
self.paint = Some(5);
|
||||
} else if keys.contains(&KeyCode::Digit6) {
|
||||
self.paint = Some(6);
|
||||
} else if keys.contains(&KeyCode::Digit7) {
|
||||
self.paint = Some(7);
|
||||
} else if keys.contains(&KeyCode::Digit8) {
|
||||
self.paint = Some(8);
|
||||
} else if keys.contains(&KeyCode::Digit9) {
|
||||
self.paint = Some(9);
|
||||
} else if keys.contains(&KeyCode::Digit0) {
|
||||
self.paint = Some(0);
|
||||
} else if keys.contains(&KeyCode::KeyX) {
|
||||
self.paint = Some(99);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user