use ash::vk; use std::ffi::CString; use std::sync::Arc; use crate::entity::EntityManager; use crate::render::lighting; use crate::world::cell::MaterialId; use crate::world::grid::{Grid, WORLD_H, WORLD_W}; 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 { grid_x: f32, grid_y: f32, 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 { grid_w: usize, grid_h: usize, entry: ash::Entry, instance: ash::Instance, surface: vk::SurfaceKHR, physical_device: vk::PhysicalDevice, device: ash::Device, graphics_queue: vk::Queue, swapchain_loader: ash::khr::swapchain::Device, swapchain: vk::SwapchainKHR, swapchain_image_views: Vec, swapchain_extent: vk::Extent2D, present_mode: vk::PresentModeKHR, render_pass: vk::RenderPass, pipeline: vk::Pipeline, pipeline_layout: vk::PipelineLayout, framebuffers: Vec, command_pool: vk::CommandPool, command_buffers: Vec, image_available: Vec, render_finished: Vec, in_flight: Vec, frame_index: usize, vertex_buffer: vk::Buffer, vertex_memory: vk::DeviceMemory, index_buffer: vk::Buffer, index_memory: vk::DeviceMemory, instance_buffer: vk::Buffer, instance_memory: vk::DeviceMemory, 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, entity_color_buf: Vec<[u8; 4]>, item_color_buf: Vec<[u8; 4]>, shadow_buf: Vec, descriptor_set_layout: vk::DescriptorSetLayout, descriptor_pool: vk::DescriptorPool, descriptor_set: vk::DescriptorSet, window: Arc, } impl GraphicsRenderer { pub fn new(window: Arc) -> Result { let grid_w = 160usize; let grid_h = 50usize; let entry = unsafe { ash::Entry::load().map_err(|e| format!("Vulkan load: {e}"))? }; use raw_window_handle::HasDisplayHandle; let dh = window .display_handle() .map_err(|e| format!("display_handle: {e}"))?; let required_exts = ash_window::enumerate_required_extensions(dh.as_raw()) .map_err(|e| format!("enumerate_required_extensions: {e:?}"))?; let app_name = CString::new("Verbatim").unwrap(); let app_info = vk::ApplicationInfo::default() .application_name(&app_name) .api_version(vk::API_VERSION_1_2); let mut ext_ptrs: Vec<*const i8> = required_exts.iter().map(|&p| p as *const i8).collect(); let avail_exts = unsafe { entry.enumerate_instance_extension_properties(None) }.unwrap_or_default(); let has_debug = avail_exts.iter().any(|e| { let name = unsafe { std::ffi::CStr::from_ptr(e.extension_name.as_ptr() as *const i8) }; name.to_str().unwrap_or("") == "VK_EXT_debug_utils" }); if has_debug { ext_ptrs.push(b"VK_EXT_debug_utils\0".as_ptr() as *const i8); } let ci = vk::InstanceCreateInfo::default() .application_info(&app_info) .enabled_extension_names(&ext_ptrs); let instance = unsafe { entry.create_instance(&ci, None) }.map_err(|e| format!("instance: {e:?}"))?; let surface = { use raw_window_handle::HasWindowHandle; let wh = window.window_handle().map_err(|e| format!("wh: {e}"))?; let dh = window.display_handle().map_err(|e| format!("dh: {e}"))?; unsafe { ash_window::create_surface(&entry, &instance, dh.as_raw(), wh.as_raw(), None) } .map_err(|e| format!("surface: {e:?}"))? }; let sl = ash::khr::surface::Instance::new(&entry, &instance); let devices = unsafe { instance.enumerate_physical_devices() }.map_err(|e| format!("enum: {e:?}"))?; let mut physical_device = vk::PhysicalDevice::null(); let mut qf = 0u32; for &pd in &devices { let props = unsafe { instance.get_physical_device_properties(pd) }; if props.device_type == vk::PhysicalDeviceType::CPU { continue; } let qfs = unsafe { instance.get_physical_device_queue_family_properties(pd) }; for (i, q) in qfs.iter().enumerate() { if q.queue_flags.contains(vk::QueueFlags::GRAPHICS) { let ok = unsafe { sl.get_physical_device_surface_support(pd, i as u32, surface) } .unwrap_or(false); if ok { physical_device = pd; qf = i as u32; break; } } } if physical_device != vk::PhysicalDevice::null() { break; } } if physical_device == vk::PhysicalDevice::null() { return Err("No GPU".to_string()); } let qp = [1.0f32]; let qi = vk::DeviceQueueCreateInfo::default() .queue_family_index(qf) .queue_priorities(&qp); let dev_ext_names: Vec = vec![CString::new("VK_KHR_swapchain").unwrap()]; let dev_ext_ptrs: Vec<*const i8> = dev_ext_names.iter().map(|n| n.as_ptr()).collect(); let dci = vk::DeviceCreateInfo::default() .queue_create_infos(std::slice::from_ref(&qi)) .enabled_extension_names(&dev_ext_ptrs); let device = unsafe { instance.create_device(physical_device, &dci, None) } .map_err(|e| format!("device: {e:?}"))?; let graphics_queue = unsafe { device.get_device_queue(qf, 0) }; 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, }; let extent = if caps.current_extent.width != u32::MAX { caps.current_extent } else { vk::Extent2D { width: grid_w as u32 * CHAR_W, height: grid_h as u32 * CHAR_H, } }; let ic = caps.min_image_count.max(2); let qf_slice = [qf]; let sci = vk::SwapchainCreateInfoKHR::default() .surface(surface) .min_image_count(ic) .image_format(format.format) .image_color_space(format.color_space) .image_extent(extent) .image_array_layers(1) .image_usage(vk::ImageUsageFlags::COLOR_ATTACHMENT) .image_sharing_mode(vk::SharingMode::EXCLUSIVE) .queue_family_indices(&qf_slice) .pre_transform(caps.current_transform) .composite_alpha(vk::CompositeAlphaFlagsKHR::OPAQUE) .present_mode(present_mode) .clipped(true); let swapchain = unsafe { swapchain_loader.create_swapchain(&sci, None) } .map_err(|e| format!("swapchain: {e:?}"))?; let swapchain_images = unsafe { swapchain_loader.get_swapchain_images(swapchain) } .map_err(|e| format!("images: {e:?}"))?; let swapchain_image_views: Vec<_> = swapchain_images .iter() .map(|&img| { let vi = vk::ImageViewCreateInfo::default() .image(img) .view_type(vk::ImageViewType::TYPE_2D) .format(format.format) .subresource_range(vk::ImageSubresourceRange { aspect_mask: vk::ImageAspectFlags::COLOR, base_mip_level: 0, level_count: 1, base_array_layer: 0, layer_count: 1, }); unsafe { device.create_image_view(&vi, None).expect("image_view") } }) .collect(); let att = vk::AttachmentDescription::default() .format(format.format) .samples(vk::SampleCountFlags::TYPE_1) .load_op(vk::AttachmentLoadOp::CLEAR) .store_op(vk::AttachmentStoreOp::STORE) .initial_layout(vk::ImageLayout::UNDEFINED) .final_layout(vk::ImageLayout::PRESENT_SRC_KHR); let att_ref = vk::AttachmentReference::default() .attachment(0) .layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL); let subpass = vk::SubpassDescription::default() .pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS) .color_attachments(std::slice::from_ref(&att_ref)); let dep = vk::SubpassDependency::default() .src_subpass(vk::SUBPASS_EXTERNAL) .dst_subpass(0) .src_stage_mask(vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT) .dst_stage_mask(vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT) .dst_access_mask(vk::AccessFlags::COLOR_ATTACHMENT_WRITE); let rpci = vk::RenderPassCreateInfo::default() .attachments(std::slice::from_ref(&att)) .subpasses(std::slice::from_ref(&subpass)) .dependencies(std::slice::from_ref(&dep)); let render_pass = unsafe { device.create_render_pass(&rpci, None) } .map_err(|e| format!("render_pass: {e:?}"))?; // Pipeline — no atlas, no descriptor set, just colored quads let vert_spv = include_bytes!("../../assets/shaders/graphics_vert.spv"); let frag_spv = include_bytes!("../../assets/shaders/graphics_frag.spv"); let vert_code: Vec = vert_spv .chunks_exact(4) .map(|c| u32::from_ne_bytes([c[0], c[1], c[2], c[3]])) .collect(); let frag_code: Vec = frag_spv .chunks_exact(4) .map(|c| u32::from_ne_bytes([c[0], c[1], c[2], c[3]])) .collect(); let vm = unsafe { device.create_shader_module( &vk::ShaderModuleCreateInfo::default().code(&vert_code), None, ) } .map_err(|e| format!("vert: {e:?}"))?; let fm = unsafe { device.create_shader_module( &vk::ShaderModuleCreateInfo::default().code(&frag_code), None, ) } .map_err(|e| format!("frag: {e:?}"))?; let main = CString::new("main").unwrap(); let vs = vk::PipelineShaderStageCreateInfo::default() .stage(vk::ShaderStageFlags::VERTEX) .module(vm) .name(&main); let fs = vk::PipelineShaderStageCreateInfo::default() .stage(vk::ShaderStageFlags::FRAGMENT) .module(fm) .name(&main); let bindings = [ vk::VertexInputBindingDescription { binding: 0, stride: 8, input_rate: vk::VertexInputRate::VERTEX, }, vk::VertexInputBindingDescription { binding: 1, stride: std::mem::size_of::() as u32, input_rate: vk::VertexInputRate::INSTANCE, }, ]; let attrs = [ vk::VertexInputAttributeDescription { location: 0, binding: 0, format: vk::Format::R32G32_SFLOAT, offset: 0, }, vk::VertexInputAttributeDescription { location: 1, binding: 1, format: vk::Format::R32G32_SFLOAT, offset: 0, }, vk::VertexInputAttributeDescription { location: 2, binding: 1, format: vk::Format::R8G8B8A8_UNORM, offset: 8, }, ]; let vi = vk::PipelineVertexInputStateCreateInfo::default() .vertex_binding_descriptions(&bindings) .vertex_attribute_descriptions(&attrs); let ia = vk::PipelineInputAssemblyStateCreateInfo::default() .topology(vk::PrimitiveTopology::TRIANGLE_LIST); let dynamic_states = [vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR]; let dynamic_state = vk::PipelineDynamicStateCreateInfo::default().dynamic_states(&dynamic_states); let vs_state = vk::PipelineViewportStateCreateInfo::default() .viewport_count(1) .scissor_count(1); let rs = vk::PipelineRasterizationStateCreateInfo::default() .line_width(1.0) .cull_mode(vk::CullModeFlags::NONE); 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::() 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:?}"))?; let stages = [vs, fs]; let gpci = vk::GraphicsPipelineCreateInfo::default() .stages(&stages) .vertex_input_state(&vi) .input_assembly_state(&ia) .viewport_state(&vs_state) .rasterization_state(&rs) .multisample_state(&ms) .color_blend_state(&cb) .dynamic_state(&dynamic_state) .layout(pipeline_layout) .render_pass(render_pass) .subpass(0); let pipes = unsafe { device.create_graphics_pipelines( vk::PipelineCache::null(), std::slice::from_ref(&gpci), None, ) } .map_err(|(_, e)| format!("pipeline: {e:?}"))?; unsafe { device.destroy_shader_module(vm, None); device.destroy_shader_module(fm, None); } let pipeline = pipes[0]; let framebuffers: Vec<_> = swapchain_image_views .iter() .map(|&view| { let fci = vk::FramebufferCreateInfo::default() .render_pass(render_pass) .attachments(std::slice::from_ref(&view)) .width(extent.width) .height(extent.height) .layers(1); unsafe { device.create_framebuffer(&fci, None).expect("fb") } }) .collect(); let command_pool = { let cpci = vk::CommandPoolCreateInfo::default() .queue_family_index(qf) .flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER); unsafe { device.create_command_pool(&cpci, None) } .map_err(|e| format!("cmd_pool: {e:?}"))? }; let command_buffers = { let cai = vk::CommandBufferAllocateInfo::default() .command_pool(command_pool) .level(vk::CommandBufferLevel::PRIMARY) .command_buffer_count(framebuffers.len() as u32); unsafe { device.allocate_command_buffers(&cai) } .map_err(|e| format!("cmd_bufs: {e:?}"))? }; let mut image_available = Vec::new(); let mut render_finished = Vec::new(); let mut in_flight = Vec::new(); for _ in 0..MAX_FRAMES { unsafe { image_available.push( device .create_semaphore(&vk::SemaphoreCreateInfo::default(), None) .map_err(|e| format!("sem: {e:?}"))?, ); render_finished.push( device .create_semaphore(&vk::SemaphoreCreateInfo::default(), None) .map_err(|e| format!("sem: {e:?}"))?, ); in_flight.push( device .create_fence( &vk::FenceCreateInfo::default().flags(vk::FenceCreateFlags::SIGNALED), None, ) .map_err(|e| format!("fence: {e:?}"))?, ); } } // Vertex + index buffers let find_mem = |filter: u32, props: vk::MemoryPropertyFlags| -> Result { let mp = unsafe { instance.get_physical_device_memory_properties(physical_device) }; for (i, mt) in mp.memory_types.iter().enumerate() { if (filter & (1 << i)) != 0 && mt.property_flags.contains(props) { return Ok(i as u32); } } Err("No memory type".to_string()) }; let make_buf = |data: &[u8], usage: vk::BufferUsageFlags| -> Result<(vk::Buffer, vk::DeviceMemory), String> { let sz = data.len() as vk::DeviceSize; let bi = vk::BufferCreateInfo::default() .size(sz) .usage(usage) .sharing_mode(vk::SharingMode::EXCLUSIVE); let buf = unsafe { device.create_buffer(&bi, None) }.map_err(|e| format!("buf: {e:?}"))?; let req = unsafe { device.get_buffer_memory_requirements(buf) }; let mt = find_mem( req.memory_type_bits, vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT, )?; let mem = unsafe { device.allocate_memory( &vk::MemoryAllocateInfo::default() .allocation_size(req.size) .memory_type_index(mt), None, ) } .map_err(|e| format!("mem: {e:?}"))?; unsafe { device.bind_buffer_memory(buf, mem, 0).expect("bind"); let ptr = device .map_memory(mem, 0, sz, vk::MemoryMapFlags::default()) .expect("map"); std::ptr::copy_nonoverlapping(data.as_ptr(), ptr as *mut u8, data.len()); device.unmap_memory(mem); } 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::()) 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::()) 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::()) as vk::DeviceSize); let light_info = vk::DescriptorBufferInfo::default() .buffer(light_buffer) .offset(0) .range((MAX_LIGHT_SOURCES * std::mem::size_of::()) 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( bytemuck::cast_slice(&verts), vk::BufferUsageFlags::VERTEX_BUFFER, )?; let (index_buffer, index_memory) = make_buf( bytemuck::cast_slice(&indices), vk::BufferUsageFlags::INDEX_BUFFER, )?; // Instance buffer let instance_count = grid_w * grid_h; let inst_sz = (instance_count * std::mem::size_of::()) as vk::DeviceSize; let ibi = vk::BufferCreateInfo::default() .size(inst_sz) .usage(vk::BufferUsageFlags::VERTEX_BUFFER) .sharing_mode(vk::SharingMode::EXCLUSIVE); let instance_buffer = unsafe { device.create_buffer(&ibi, None) }.map_err(|e| format!("inst buf: {e:?}"))?; let ireq = unsafe { device.get_buffer_memory_requirements(instance_buffer) }; let imt = find_mem( ireq.memory_type_bits, vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT, )?; let instance_memory = unsafe { device.allocate_memory( &vk::MemoryAllocateInfo::default() .allocation_size(ireq.size) .memory_type_index(imt), None, ) } .map_err(|e| format!("inst mem: {e:?}"))?; let instance_ptr = unsafe { device .bind_buffer_memory(instance_buffer, instance_memory, 0) .expect("bind inst"); let ptr = device .map_memory(instance_memory, 0, inst_sz, vk::MemoryMapFlags::default()) .expect("map inst"); ptr as *mut ColorInstance }; let ui_capacity = 65536usize; let ui_inst_sz = (ui_capacity * std::mem::size_of::()) 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, entry, instance, surface, physical_device, device, graphics_queue, swapchain_loader, swapchain, swapchain_image_views, swapchain_extent: extent, present_mode, render_pass, pipeline, pipeline_layout, framebuffers, command_pool, command_buffers, image_available, render_finished, in_flight, frame_index: 0, vertex_buffer, vertex_memory, index_buffer, index_memory, instance_buffer, 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, items: &crate::entity::item::ItemManager, ui: &crate::ui::UiLayer, cam_x: i32, cam_y: i32, _lighting: Option<&lighting::LightGrid>, ) { self.check_resize(); 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 { continue; } 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 }; 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 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 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() { 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 { [cell.fg[0], cell.fg[1], cell.fg[2], 255] } }; instances[idx] = ColorInstance { grid_x: dx as f32, grid_y: dy as f32, color, }; } } 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; unsafe { let _ = device.wait_for_fences(&[self.in_flight[frame]], true, u64::MAX); let _ = device.reset_fences(&[self.in_flight[frame]]); let image_index = match self.swapchain_loader.acquire_next_image( self.swapchain, u64::MAX, self.image_available[frame], vk::Fence::null(), ) { Ok((idx, _)) => idx as usize, Err(e) => { eprintln!("acquire: {e:?}"); return; } }; let cmd = self.command_buffers[frame]; let _ = device.reset_command_buffer(cmd, vk::CommandBufferResetFlags::default()); let _ = device.begin_command_buffer(cmd, &vk::CommandBufferBeginInfo::default()); let clear = vk::ClearValue { color: vk::ClearColorValue { float32: [10.0 / 255.0, 10.0 / 255.0, 15.0 / 255.0, 1.0], }, }; let rp_info = vk::RenderPassBeginInfo::default() .render_pass(self.render_pass) .framebuffer(self.framebuffers[image_index]) .render_area(vk::Rect2D { offset: vk::Offset2D::default(), extent: self.swapchain_extent, }) .clear_values(std::slice::from_ref(&clear)); device.cmd_begin_render_pass(cmd, &rp_info, vk::SubpassContents::INLINE); device.cmd_bind_pipeline(cmd, vk::PipelineBindPoint::GRAPHICS, self.pipeline); let viewport = vk::Viewport { x: 0.0, y: 0.0, width: self.swapchain_extent.width as f32, height: self.swapchain_extent.height as f32, min_depth: 0.0, max_depth: 1.0, }; let scissor = vk::Rect2D { offset: vk::Offset2D::default(), extent: self.swapchain_extent, }; 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, &[self.vertex_buffer, self.instance_buffer], &[0, 0], ); device.cmd_bind_index_buffer(cmd, self.index_buffer, 0, vk::IndexType::UINT16); 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, is_ui: 0, light_count, }; device.cmd_push_constants( cmd, self.pipeline_layout, vk::ShaderStageFlags::VERTEX, 0, bytemuck::bytes_of(&pc), ); 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); let wait_stages = [vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT]; let submit_info = vk::SubmitInfo::default() .wait_semaphores(std::slice::from_ref(&self.image_available[frame])) .wait_dst_stage_mask(&wait_stages) .command_buffers(std::slice::from_ref(&cmd)) .signal_semaphores(std::slice::from_ref(&self.render_finished[frame])); let _ = device.queue_submit( self.graphics_queue, std::slice::from_ref(&submit_info), self.in_flight[frame], ); let img_idx = image_index as u32; let present_info = vk::PresentInfoKHR::default() .wait_semaphores(std::slice::from_ref(&self.render_finished[frame])) .swapchains(std::slice::from_ref(&self.swapchain)) .image_indices(std::slice::from_ref(&img_idx)); let _ = self .swapchain_loader .queue_present(self.graphics_queue, &present_info); } self.frame_index = (self.frame_index + 1) % MAX_FRAMES; } pub fn grid_w(&self) -> usize { self.grid_w } pub fn grid_h(&self) -> usize { self.grid_h } fn check_resize(&mut self) { let sl = ash::khr::surface::Instance::new(&self.entry, &self.instance); let caps = match unsafe { sl.get_physical_device_surface_capabilities(self.physical_device, self.surface) } { Ok(c) => c, Err(_) => return, }; let new_extent = if caps.current_extent.width != u32::MAX { caps.current_extent } else { // Wayland: surface extent is undefined, use window inner size let inner = self.window.inner_size(); vk::Extent2D { width: inner.width.max(1), height: inner.height.max(1), } }; if new_extent.width == self.swapchain_extent.width && new_extent.height == self.swapchain_extent.height { return; } if new_extent.width == 0 || new_extent.height == 0 { return; } unsafe { let _ = self.device.device_wait_idle(); } // Destroy old swapchain resources for &fb in &self.framebuffers { unsafe { self.device.destroy_framebuffer(fb, None); } } for &v in &self.swapchain_image_views { unsafe { self.device.destroy_image_view(v, None); } } // Recreate swapchain let sci = vk::SwapchainCreateInfoKHR::default() .surface(self.surface) .min_image_count(caps.min_image_count.max(2)) .image_format(vk::Format::B8G8R8A8_UNORM) .image_color_space(vk::ColorSpaceKHR::SRGB_NONLINEAR) .image_extent(new_extent) .image_array_layers(1) .image_usage(vk::ImageUsageFlags::COLOR_ATTACHMENT) .image_sharing_mode(vk::SharingMode::EXCLUSIVE) .pre_transform(caps.current_transform) .composite_alpha(vk::CompositeAlphaFlagsKHR::OPAQUE) .present_mode(self.present_mode) .clipped(true) .old_swapchain(self.swapchain); let new_swapchain = match unsafe { self.swapchain_loader.create_swapchain(&sci, None) } { Ok(s) => s, Err(_) => return, }; let new_images = match unsafe { self.swapchain_loader.get_swapchain_images(new_swapchain) } { Ok(i) => i, Err(_) => return, }; let new_views: Vec<_> = new_images .iter() .map(|&img| { let vi = vk::ImageViewCreateInfo::default() .image(img) .view_type(vk::ImageViewType::TYPE_2D) .format(vk::Format::B8G8R8A8_UNORM) .subresource_range(vk::ImageSubresourceRange { aspect_mask: vk::ImageAspectFlags::COLOR, base_mip_level: 0, level_count: 1, base_array_layer: 0, layer_count: 1, }); unsafe { self.device .create_image_view(&vi, None) .expect("image_view") } }) .collect(); let new_framebuffers: Vec<_> = new_views .iter() .map(|&view| { let fci = vk::FramebufferCreateInfo::default() .render_pass(self.render_pass) .attachments(std::slice::from_ref(&view)) .width(new_extent.width) .height(new_extent.height) .layers(1); unsafe { self.device.create_framebuffer(&fci, None).expect("fb") } }) .collect(); // Reallocate command buffers for new count unsafe { self.device .free_command_buffers(self.command_pool, &self.command_buffers); } let cai = vk::CommandBufferAllocateInfo::default() .command_pool(self.command_pool) .level(vk::CommandBufferLevel::PRIMARY) .command_buffer_count(new_framebuffers.len() as u32); let new_cmd_bufs = unsafe { self.device .allocate_command_buffers(&cai) .expect("cmd_bufs") }; // Reallocate instance buffer if grid size changed let new_grid_w = (new_extent.width / CHAR_W) as usize; let new_grid_h = (new_extent.height / CHAR_H) as usize; let new_count = new_grid_w * new_grid_h; if new_count != self.instance_count { unsafe { self.device.unmap_memory(self.instance_memory); self.device.destroy_buffer(self.instance_buffer, None); self.device.free_memory(self.instance_memory, None); } let inst_sz = (new_count * std::mem::size_of::()) as vk::DeviceSize; let ibi = vk::BufferCreateInfo::default() .size(inst_sz) .usage(vk::BufferUsageFlags::VERTEX_BUFFER) .sharing_mode(vk::SharingMode::EXCLUSIVE); self.instance_buffer = unsafe { self.device.create_buffer(&ibi, None) }.expect("inst buf"); let ireq = unsafe { self.device .get_buffer_memory_requirements(self.instance_buffer) }; let find_mem = |filter: u32, props: vk::MemoryPropertyFlags| -> u32 { let mp = unsafe { self.instance .get_physical_device_memory_properties(self.physical_device) }; for (i, mt) in mp.memory_types.iter().enumerate() { if (filter & (1 << i)) != 0 && mt.property_flags.contains(props) { return i as u32; } } 0 }; let imt = find_mem( ireq.memory_type_bits, vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT, ); self.instance_memory = unsafe { self.device.allocate_memory( &vk::MemoryAllocateInfo::default() .allocation_size(ireq.size) .memory_type_index(imt), None, ) } .expect("inst mem"); self.instance_ptr = unsafe { self.device .bind_buffer_memory(self.instance_buffer, self.instance_memory, 0) .expect("bind"); let ptr = self .device .map_memory( self.instance_memory, 0, inst_sz, vk::MemoryMapFlags::default(), ) .expect("map"); ptr as *mut ColorInstance }; self.instance_count = new_count; } // Destroy old swapchain unsafe { self.swapchain_loader .destroy_swapchain(self.swapchain, None); } self.swapchain = new_swapchain; self.swapchain_image_views = new_views; self.framebuffers = new_framebuffers; self.command_buffers = new_cmd_bufs; self.swapchain_extent = new_extent; self.grid_w = new_grid_w; self.grid_h = new_grid_h; } } impl Drop for GraphicsRenderer { fn drop(&mut self) { unsafe { let _ = self.device.device_wait_idle(); for &f in &self.in_flight { self.device.destroy_fence(f, None); } for &s in &self.image_available { self.device.destroy_semaphore(s, None); } for &s in &self.render_finished { self.device.destroy_semaphore(s, None); } self.device.destroy_command_pool(self.command_pool, None); for &fb in &self.framebuffers { self.device.destroy_framebuffer(fb, None); } self.device.destroy_pipeline(self.pipeline, None); self.device .destroy_pipeline_layout(self.pipeline_layout, None); 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); } self.swapchain_loader .destroy_swapchain(self.swapchain, None); let sl = ash::khr::surface::Instance::new(&self.entry, &self.instance); sl.destroy_surface(self.surface, None); self.device.destroy_device(None); self.instance.destroy_instance(None); } } }