use std::ffi::{CStr, CString}; use ash::{ Device, Entry, Instance, vk::{self}, }; use raw_window_handle::{HasDisplayHandle as _, HasWindowHandle as _}; use sdl2::{Sdl, video::Window}; use crate::{APP_NAME, Game, RenderAvailable, RenderCtx, Unstarted}; impl Game { pub fn init_render(self, sdl_context: Sdl, window: Window) -> Game { let (width, height) = window.size(); let entry = Entry::linked(); let instance = create_instance(&window, &entry); let surface = make_surface(&entry, &instance, &window); let pdev = find_pdev_dgpu(&instance); let queue_idx = find_rcs_queue_inx(&instance, &pdev).expect("No RCS queue found"); let dev = make_dev( &instance, &pdev, queue_idx, vec![c"VK_KHR_swapchain", c"VK_EXT_extended_dynamic_state3"], ); let queue = unsafe { dev.get_device_queue(queue_idx, 0) }; let cmd_pool = make_command_pool(&dev, queue_idx); let cmd_buf = alloc_cmd_buf(&dev, cmd_pool); let host_vis_idx = find_host_visible(&instance, &pdev).expect("No host visible memory found"); let host_invis_idx = find_host_invisible(&instance, &pdev).expect("No host invisible memory found"); Game { state: RenderAvailable { sdl_context, window: crate::Window { width, height, _sdl: window, }, ctx: RenderCtx { entry, instance, pdev, dev, surface, queue, queue_idx, cmd_buf, host_vis_idx, host_invis_idx, }, }, } } } fn create_instance(window: &Window, entry: &Entry) -> Instance { let n_app_name = CString::new(APP_NAME).unwrap(); let n_engine_name = CString::new(format!("{} Engine", APP_NAME)).unwrap(); let appinfo = vk::ApplicationInfo::default() .application_name(n_app_name.as_c_str()) .application_version(0) .engine_name(n_engine_name.as_c_str()) .engine_version(0) .api_version(vk::make_api_version(0, 1, 3, 0)); let default_exts = window .vulkan_instance_extensions() .expect("Failed to get list of required extensions from SDL2"); let mut exts = vec![]; exts.extend(default_exts); let exts: Vec<*const i8> = exts .iter() .map(|s| s.as_ptr() as *const i8) .collect::>(); let exts: &[*const i8] = exts.as_slice(); let insinfo = vk::InstanceCreateInfo::default() .application_info(&appinfo) .enabled_extension_names(exts); let instance = unsafe { entry.create_instance(&insinfo, None) }.expect("Failed to create instance"); println!("Created instance"); instance } fn make_surface(entry: &Entry, instance: &Instance, window: &Window) -> vk::SurfaceKHR { let surface = unsafe { ash_window::create_surface( &entry, &instance, window.display_handle().unwrap().as_raw(), window.window_handle().unwrap().as_raw(), None, ) } .expect("Failed to create surface"); println!("Created surface"); surface } fn find_pdev_dgpu(instance: &Instance) -> vk::PhysicalDevice { let mut pdevs = unsafe { instance.enumerate_physical_devices() }.expect("Failed to enumerate devices"); for (i, d) in pdevs.iter().enumerate() { let props = unsafe { instance.get_physical_device_properties(*d) }; if props.device_type == vk::PhysicalDeviceType::DISCRETE_GPU { // TODO this assumes that the first discrete GPU will have an RCS queue family // This is not guaranteed by the spec (example, a compute-only GPU) // Fix. println!( "Found discrete GPU: {}", unsafe { std::str::from_utf8(std::mem::transmute(props.device_name.as_slice())) } .unwrap() ); return pdevs.remove(i); } } println!("No discrete GPU found"); assert!(pdevs.len() > 0, "No GPU found"); return pdevs.remove(0); } // Using Intel GPU hardware terminology 🥴 fn find_rcs_queue_inx(instance: &Instance, pdev: &vk::PhysicalDevice) -> Option { let qfams = unsafe { instance.get_physical_device_queue_family_properties(*pdev) }; for (inx, qfam) in qfams.iter().enumerate() { if qfam .queue_flags .contains(vk::QueueFlags::GRAPHICS | vk::QueueFlags::COMPUTE) { println!("Found RCS queue at index {}", inx); return Some(inx as u32); } } println!("No RCS queue found"); return None; } fn make_dev( instance: &Instance, pdev: &vk::PhysicalDevice, rcs_queue_inx: u32, exts: Vec<&CStr>, ) -> Device { unsafe { let dev_queue = vec![ vk::DeviceQueueCreateInfo::default() .queue_family_index(rcs_queue_inx) .queue_priorities(&[1.0]), ]; let exts = exts.iter().map(|&s| s.as_ptr()).collect::>(); let exts = exts.as_slice(); // Enable all features let features = instance.get_physical_device_features(*pdev); let dev_info = vk::DeviceCreateInfo::default() .queue_create_infos(&dev_queue) .enabled_extension_names(exts) .enabled_features(&features); let dev = instance .create_device(*pdev, &dev_info, None) .expect("Failed to create device"); println!("Created device"); dev } } fn make_command_pool(dev: &Device, rcs_queue_inx: u32) -> vk::CommandPool { unsafe { let pool_info = vk::CommandPoolCreateInfo::default() .queue_family_index(rcs_queue_inx) .flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER); let pool = dev .create_command_pool(&pool_info, None) .expect("Failed to create command pool"); println!("Created command pool"); pool } } fn alloc_cmd_buf(dev: &Device, pool: vk::CommandPool) -> vk::CommandBuffer { unsafe { let alloc_info = vk::CommandBufferAllocateInfo::default() .command_pool(pool) .level(vk::CommandBufferLevel::PRIMARY) .command_buffer_count(1); let cmd_buf = dev .allocate_command_buffers(&alloc_info) .expect("Failed to allocate command buffer") .remove(0); println!("Allocated command buffer"); cmd_buf } } fn find_host_visible(instance: &Instance, pdev: &vk::PhysicalDevice) -> Option { let mem_props = unsafe { instance.get_physical_device_memory_properties(*pdev) }; for (i, mem_type) in mem_props.memory_types.iter().enumerate() { if mem_type.property_flags.contains( vk::MemoryPropertyFlags::DEVICE_LOCAL | vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT, ) { println!("Found host visible memory at index {}", i); return Some(i as u32); } } println!("No host visible memory found"); return None; } fn find_host_invisible(instance: &Instance, pdev: &vk::PhysicalDevice) -> Option { let mem_props = unsafe { instance.get_physical_device_memory_properties(*pdev) }; for (i, mem_type) in mem_props.memory_types.iter().enumerate() { if mem_type .property_flags .contains(vk::MemoryPropertyFlags::DEVICE_LOCAL) && !mem_type.property_flags.contains( vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT, ) { println!("Found host invisible memory at index {}", i); return Some(i as u32); } } println!("No host invisible memory found"); return None; }