diff --git a/crates/renderer/src/debug.rs b/crates/renderer/src/debug.rs new file mode 100644 index 0000000..ac14bf3 --- /dev/null +++ b/crates/renderer/src/debug.rs @@ -0,0 +1,43 @@ +use ash::vk; +use tracing::{event, Level}; + +unsafe fn str_from_raw_parts<'a>(str: *const i8) -> std::borrow::Cow<'a, str> { + use std::{borrow::Cow, ffi}; + if str.is_null() { + Cow::from("") + } else { + ffi::CStr::from_ptr(str).to_string_lossy() + } +} + +pub(super) unsafe extern "system" fn debug_callback( + message_severity: vk::DebugUtilsMessageSeverityFlagsEXT, + message_type: vk::DebugUtilsMessageTypeFlagsEXT, + callback_data: *const vk::DebugUtilsMessengerCallbackDataEXT<'_>, + user_data: *mut core::ffi::c_void, +) -> vk::Bool32 { + _ = user_data; + let callback_data = *callback_data; + let message_id_number = callback_data.message_id_number; + + let message_id_name = str_from_raw_parts(callback_data.p_message_id_name); + let message = str_from_raw_parts(callback_data.p_message); + + match message_severity { + vk::DebugUtilsMessageSeverityFlagsEXT::ERROR => { + event!(target: "VK::DebugUtils", Level::ERROR, "{message_type:?} [{message_id_name}({message_id_number})]: {message}"); + } + vk::DebugUtilsMessageSeverityFlagsEXT::VERBOSE => { + event!(target: "VK::DebugUtils", Level::TRACE, "{message_type:?} [{message_id_name}({message_id_number})]: {message}"); + } + vk::DebugUtilsMessageSeverityFlagsEXT::INFO => { + event!(target: "VK::DebugUtils", Level::INFO, "{message_type:?} [{message_id_name}({message_id_number})]: {message}"); + } + vk::DebugUtilsMessageSeverityFlagsEXT::WARNING => { + event!(target: "VK::DebugUtils", Level::WARN, "{message_type:?} [{message_id_name}({message_id_number})]: {message}"); + } + _ => unreachable!(), + } + + vk::FALSE +} diff --git a/crates/renderer/src/lib.rs b/crates/renderer/src/lib.rs index 215ce52..d62178d 100644 --- a/crates/renderer/src/lib.rs +++ b/crates/renderer/src/lib.rs @@ -9,7 +9,6 @@ )] use std::{ - borrow::Borrow, collections::{BTreeMap, BTreeSet, HashMap}, ffi::{CStr, CString}, fmt::Debug, @@ -17,22 +16,23 @@ use std::{ sync::Arc, }; -use parking_lot::{Mutex, MutexGuard, RwLock}; +use raw_window_handle::{RawDisplayHandle, RawWindowHandle}; + +use parking_lot::{Mutex, MutexGuard}; use ash::{ khr, prelude::VkResult, - vk::{self, Handle}, + vk::{self}, Entry, }; use dyn_clone::DynClone; -use rand::{Rng, SeedableRng}; -use raw_window_handle::RawDisplayHandle; pub use ash; mod buffers; pub mod commands; +mod debug; pub mod device; #[path = "egui.rs"] mod egui_pass; @@ -41,6 +41,7 @@ mod memory; mod pipeline; pub mod render_graph; pub mod rendering; +pub mod swapchain; pub mod sync; pub mod util; @@ -473,8 +474,6 @@ impl AsRef for Instance { } } -pub mod swapchain; - pub struct SamplerCache { device: Device, samplers: HashMap, @@ -502,596 +501,9 @@ impl SamplerCache { } } -pub struct Vulkan { - instance: Arc, - pub device: Device, - samplers: SamplerCache, -} - -impl Drop for Vulkan { - fn drop(&mut self) { - unsafe { - _ = self.device.dev().device_wait_idle(); - } - } -} - -impl Vulkan { - const VALIDATION_LAYER_NAME: &'static core::ffi::CStr = c"VK_LAYER_KHRONOS_validation"; - #[allow(unused)] - const RENDERDOC_LAYER_NAME: &'static core::ffi::CStr = c"VK_LAYER_RENDERDOC_Capture"; - #[allow(unused)] - const NSIGHT_TRACE_LAYER_NAME: &'static core::ffi::CStr = - c"VK_LAYER_NV_GPU_Trace_release_public_2021_4_2"; - #[allow(unused)] - const NSIGHT_INTERCEPTION_LAYER_NAME: &'static core::ffi::CStr = - c"VK_LAYER_NV_nomad_release_public_2021_4_2"; - - pub fn samplers(&self) -> &SamplerCache { - &self.samplers - } - - pub fn samplers_mut(&mut self) -> &mut SamplerCache { - &mut self.samplers - } - - pub fn new( - app_name: &str, - instance_layers: &[&CStr], - instance_extensions: &[&CStr], - display_handle: Option, - ) -> Result { - let entry = unsafe { ash::Entry::load()? }; - - let app_name = CString::new(app_name)?; - let app_info = vk::ApplicationInfo::default() - .api_version(vk::make_api_version(0, 1, 3, 0)) - .application_name(&app_name) - .engine_name(c"PrimalGame") - .application_version(0) - .engine_version(0); - - // TODO: make this a flag somewhere to enable or disable validation layers - // DEBUG LAYERS/VALIDATION - let validation_settings = [ - vk::LayerSettingEXT::default() - .layer_name(Self::VALIDATION_LAYER_NAME) - .setting_name(c"VK_KHRONOS_VALIDATION_VALIDATE_BEST_PRACTICES") - .ty(vk::LayerSettingTypeEXT::BOOL32) - .values(&[1]), - vk::LayerSettingEXT::default() - .layer_name(Self::VALIDATION_LAYER_NAME) - .setting_name(c"VK_KHRONOS_VALIDATION_VALIDATE_BEST_PRACTICES_AMD") - .ty(vk::LayerSettingTypeEXT::BOOL32) - .values(&[1]), - vk::LayerSettingEXT::default() - .layer_name(Self::VALIDATION_LAYER_NAME) - .setting_name(c"VK_KHRONOS_VALIDATION_VALIDATE_SYNC") - .ty(vk::LayerSettingTypeEXT::BOOL32) - .values(&[1]), - ]; - let mut validation_info = - vk::LayerSettingsCreateInfoEXT::default().settings(&validation_settings); - - let layers = Self::get_layers( - &entry, - instance_layers - .into_iter() - .chain(&[ - #[cfg(debug_assertions)] - Self::VALIDATION_LAYER_NAME, - ]) - .cloned(), - ) - .unwrap(); - - let (extensions, unsupported_extensions) = Self::get_extensions( - &entry, - &layers, - instance_extensions - .into_iter() - .chain([ash::ext::debug_utils::NAME, ash::ext::layer_settings::NAME].iter()) - .cloned(), - display_handle, - )?; - - if !unsupported_extensions.is_empty() { - tracing::error!( - "extensions were requested but not supported by instance: {:?}", - unsupported_extensions - ); - } - - let layers = VkNameList::from_strs(&layers); - let extensions = VkNameList::from_strs(&extensions); - - let create_info = vk::InstanceCreateInfo::default() - .application_info(&app_info) - .enabled_extension_names(&extensions.names) - .enabled_layer_names(&layers.names) - .push_next(&mut validation_info); - let instance = unsafe { entry.create_instance(&create_info, None)? }; - - let debug_info = vk::DebugUtilsMessengerCreateInfoEXT::default() - .message_severity( - vk::DebugUtilsMessageSeverityFlagsEXT::ERROR - | vk::DebugUtilsMessageSeverityFlagsEXT::WARNING - | vk::DebugUtilsMessageSeverityFlagsEXT::INFO, - ) - .message_type( - vk::DebugUtilsMessageTypeFlagsEXT::GENERAL - | vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION - | vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE, - ) - .pfn_user_callback(Some(debug::debug_callback)); - - let debug_utils_instance = ash::ext::debug_utils::Instance::new(&entry, &instance); - let debug_utils_messenger = - unsafe { debug_utils_instance.create_debug_utils_messenger(&debug_info, None)? }; - let surface_instance = ash::khr::surface::Instance::new(&entry, &instance); - - let instance = Arc::new(Instance { - instance, - debug_utils: debug_utils_instance, - debug_utils_messenger, - surface: surface_instance, - entry, - }); - - let features = PhysicalDeviceFeatures::all_default() - .version(vk::make_api_version(0, 1, 3, 0)) - .features10( - vk::PhysicalDeviceFeatures::default() - .sampler_anisotropy(true) - .fill_mode_non_solid(true) - .multi_draw_indirect(true), - ) - .features11(vk::PhysicalDeviceVulkan11Features::default().shader_draw_parameters(true)) - .features12( - vk::PhysicalDeviceVulkan12Features::default() - .shader_int8(true) - .runtime_descriptor_array(true) - .descriptor_binding_partially_bound(true) - .shader_sampled_image_array_non_uniform_indexing(true) - .descriptor_binding_sampled_image_update_after_bind(true) - .storage_buffer8_bit_access(true), - ) - .features13( - vk::PhysicalDeviceVulkan13Features::default() - .dynamic_rendering(true) - .maintenance4(true) - .synchronization2(true), - ) - .with_extension( - make_extention_properties( - ash::ext::mesh_shader::NAME, - ash::ext::mesh_shader::SPEC_VERSION, - ), - vk::PhysicalDeviceMeshShaderFeaturesEXT::default() - .mesh_shader(true) - .task_shader(true), - ) - .with_extension( - make_extention_properties( - ash::khr::present_id::NAME, - ash::khr::present_id::SPEC_VERSION, - ), - vk::PhysicalDevicePresentIdFeaturesKHR::default().present_id(true), - ) - .with_extension( - make_extention_properties( - ash::khr::present_wait::NAME, - ash::khr::present_wait::SPEC_VERSION, - ), - vk::PhysicalDevicePresentWaitFeaturesKHR::default().present_wait(true), - ) - .with_extension( - make_extention_properties( - ash::ext::index_type_uint8::NAME, - ash::ext::index_type_uint8::SPEC_VERSION, - ), - vk::PhysicalDeviceIndexTypeUint8FeaturesEXT::default().index_type_uint8(true), - ) - .with_extensions2([ - make_extention_properties(khr::swapchain::NAME, khr::swapchain::SPEC_VERSION), - make_extention_properties(khr::spirv_1_4::NAME, khr::spirv_1_4::SPEC_VERSION), - ]); - - // Consider this: switching physical device in game? - // anything above this point is device agnostic, everything below would have to be recreated - // additionally, pdev would have to be derived from a device and not a scoring function. - - let pdev = Self::choose_physical_device( - &instance, - display_handle, - &features, - vec![Box::new( - vk::PhysicalDeviceMeshShaderPropertiesEXT::default(), - )], - )?; - - tracing::trace!("pdev: {pdev:?}"); - let device = Device::new(instance.clone(), pdev, features)?; - - Ok(Self { - instance, - samplers: SamplerCache::new(device.clone()), - device, - }) - } - - fn queue_family_supports_presentation( - instance: &Instance, - pdev: vk::PhysicalDevice, - queue_family: u32, - display_handle: RawDisplayHandle, - ) -> bool { - unsafe { - match display_handle { - RawDisplayHandle::Xlib(display) => { - let surface = - ash::khr::xlib_surface::Instance::new(&instance.entry, &instance.instance); - surface.get_physical_device_xlib_presentation_support( - pdev, - queue_family, - display.display.unwrap().as_ptr() as _, - display.screen as _, - ) - //todo!("xlib") - } - RawDisplayHandle::Xcb(_xcb_display_handle) => todo!("xcb"), - RawDisplayHandle::Wayland(wayland_display_handle) => { - let surface = ash::khr::wayland_surface::Instance::new( - &instance.entry, - &instance.instance, - ); - surface.get_physical_device_wayland_presentation_support( - pdev, - queue_family, - wayland_display_handle.display.cast().as_mut(), - ) - } - RawDisplayHandle::Drm(_) => { - todo!() - } - RawDisplayHandle::Windows(_) => { - ash::khr::win32_surface::Instance::new(&instance.entry, &instance.instance) - .get_physical_device_win32_presentation_support(pdev, queue_family) - } - _ => panic!("unsupported platform"), - } - } - } - - fn select_pdev_queue_families( - instance: &Instance, - display_handle: Option, - pdev: vk::PhysicalDevice, - ) -> DeviceQueueFamilies { - let queue_families = unsafe { - instance - .instance - .get_physical_device_queue_family_properties(pdev) - }; - - struct QueueFamily { - num_queues: u32, - is_present: bool, - is_compute: bool, - is_graphics: bool, - is_transfer: bool, - } - - impl QueueFamily { - #[allow(dead_code)] - fn is_graphics_and_compute(&self) -> bool { - self.is_compute && self.is_graphics - } - } - - struct QueueFamilies(Vec); - impl QueueFamilies { - fn find_first(&mut self, mut pred: F) -> Option - where - F: FnMut(&QueueFamily) -> bool, - { - if let Some((q, family)) = self - .0 - .iter_mut() - .enumerate() - .filter(|(_, family)| family.num_queues > 0) - .find(|(_, family)| pred(family)) - { - family.num_queues -= 1; - Some(q as u32) - } else { - None - } - } - - fn find_best(&mut self, mut pred: F) -> Option - where - F: FnMut(&QueueFamily) -> Option, - { - let (_, q, family) = self - .0 - .iter_mut() - .enumerate() - .filter_map(|(i, family)| { - if family.num_queues == 0 { - return None; - } - pred(family).map(|score| (score, i, family)) - }) - .max_by_key(|(score, _, _)| *score)?; - family.num_queues -= 1; - Some(q as u32) - } - } - - let mut queue_families = QueueFamilies( - queue_families - .iter() - .enumerate() - .map(|(i, family)| { - let q = i as u32; - let is_graphics = family.queue_flags.contains(vk::QueueFlags::GRAPHICS); - let is_compute = family.queue_flags.contains(vk::QueueFlags::COMPUTE); - let is_transfer = family.queue_flags.contains(vk::QueueFlags::TRANSFER) - || is_compute - || is_graphics; - let is_present = display_handle - .map(|display_handle| { - Self::queue_family_supports_presentation( - instance, - pdev, - q, - display_handle, - ) - }) - .unwrap_or(false); - QueueFamily { - num_queues: family.queue_count, - is_compute, - is_graphics, - is_present, - is_transfer, - } - }) - .collect::>(), - ); - - let graphics = queue_families - .find_best(|family| { - if !family.is_graphics { - return None; - } - // a queue with Graphics+Compute is guaranteed to exist - Some(family.is_compute as u32 * 2 + family.is_present as u32) - }) - .unwrap(); - - // find present queue first because it is rather more important than a secondary compute queue - let present = - if !queue_families.0.get(graphics as usize).unwrap().is_present { - queue_families.find_first(|family| family.is_present) - } else { - None - }.or({ - if display_handle.is_none() { - // in this case the graphics queue will be used by default - tracing::info!("no present queue available, using graphics queue as fallback for headless_surface"); - Some(graphics) - } else { - tracing::warn!("no present queue available, this is unexpected!"); - None} - }); - - let async_compute = queue_families.find_first(|family| family.is_compute); - let transfer = queue_families.find_first(|family| family.is_transfer); - - let mut unique_families = BTreeMap::::new(); - - let mut helper = |family: u32| { - use std::collections::btree_map::Entry; - let index = match unique_families.entry(family) { - Entry::Vacant(vacant_entry) => { - vacant_entry.insert(1); - 0 - } - Entry::Occupied(mut occupied_entry) => { - let idx = occupied_entry.get_mut(); - *idx += 1; - *idx - 1 - } - }; - - (family, index) - }; - - let graphics = helper(graphics); - let async_compute = async_compute.map(|f| helper(f)).unwrap_or(graphics); - let transfer = transfer.map(|f| helper(f)).unwrap_or(async_compute); - let present = present.map(|f| helper(f)).unwrap_or(graphics); - - let families = unique_families - .into_iter() - .filter(|&(_family, count)| count > 0) - .collect::>(); - - // family of each queue, of which one is allocated for each queue, with - // graphics being the fallback queue for compute and transfer, and - // present possibly being `None`, in which case it is Graphics - let queues = DeviceQueueFamilies { - families, - graphics, - async_compute, - transfer, - present, - }; - - queues - } - - fn choose_physical_device( - instance: &Instance, - display_handle: Option, - requirements: &PhysicalDeviceFeatures, - extra_properties: Vec>, - ) -> Result { - let pdevs = unsafe { instance.instance.enumerate_physical_devices()? }; - - let (pdev, properties) = pdevs - .into_iter() - .map(|pdev| { - let mut props = PhysicalDeviceProperties::default().extra_properties( - extra_properties - .iter() - .map(|b| dyn_clone::clone_box(&**b)) - .collect::>(), - ); - props.query(&instance.instance, pdev); - - (pdev, props) - }) - // filter devices which dont support the version of Vulkan we are requesting - .filter(|(_, props)| props.base.api_version >= requirements.version) - // filter devices which don't support the device extensions we - // are requesting - // TODO: figure out a way to fall back to some - // device which doesn't support all of the extensions. - .filter(|(pdev, _)| { - let query_features = - PhysicalDeviceFeatures::query(&instance.instance, *pdev).unwrap(); - - requirements.compatible_with(&query_features) - }) - .max_by_key(|(_, props)| { - let score = match props.base.device_type { - vk::PhysicalDeviceType::DISCRETE_GPU => 5, - vk::PhysicalDeviceType::INTEGRATED_GPU => 4, - vk::PhysicalDeviceType::VIRTUAL_GPU => 3, - vk::PhysicalDeviceType::CPU => 2, - vk::PhysicalDeviceType::OTHER => 1, - _ => unreachable!(), - }; - - // score based on limits or other properties - - score - }) - .ok_or(Error::NoPhysicalDevice)?; - - Ok(PhysicalDevice { - queue_families: Self::select_pdev_queue_families(instance, display_handle, pdev), - pdev, - properties, - }) - } - - fn get_available_extensions( - entry: &ash::Entry, - layers: &[&CStr], - ) -> Result> { - unsafe { - let extensions = core::iter::once(entry.enumerate_instance_extension_properties(None)) - .chain( - layers - .iter() - .map(|&layer| entry.enumerate_instance_extension_properties(Some(layer))), - ) - .filter_map(|result| result.ok()) - .flatten() - .collect::>(); - - Ok(extensions) - } - } - - /// returns a tuple of supported/enabled extensions and unsupported/requested extensions - fn get_extensions<'a>( - entry: &ash::Entry, - layers: &[&'a CStr], - extensions: impl Iterator + 'a, - display_handle: Option, - ) -> Result<(Vec<&'a CStr>, Vec<&'a CStr>)> { - unsafe { - let available_extensions = Self::get_available_extensions(entry, layers)?; - - let available_extension_names = available_extensions - .iter() - .filter_map(|layer| layer.extension_name_as_c_str().ok()) - .collect::>(); - - let mut out_extensions = Vec::new(); - let mut unsupported_extensions = Vec::new(); - - for extension in extensions { - if available_extension_names.contains(extension) { - out_extensions.push(extension); - } else { - unsupported_extensions.push(extension); - } - } - - let required_extension_names = display_handle - .map(|display_handle| ash_window::enumerate_required_extensions(display_handle)) - .unwrap_or(Ok(&[]))?; - - for &extension in required_extension_names { - let extension = core::ffi::CStr::from_ptr(extension); - if available_extension_names.contains(&extension) { - out_extensions.push(extension); - } else { - unsupported_extensions.push(extension); - } - } - - Ok((out_extensions, unsupported_extensions)) - } - } - - fn get_layers<'a>( - entry: &ash::Entry, - wants_layers: impl Iterator + 'a, - ) -> core::result::Result, (Vec<&'a CStr>, Vec<&'a CStr>)> { - unsafe { - let wants_layers = wants_layers.collect::>(); - - let available_layers = entry - .enumerate_instance_layer_properties() - .map_err(|_| (Vec::<&'a CStr>::new(), wants_layers.clone()))?; - let available_layer_names = available_layers - .iter() - .map(|layer| layer.layer_name_as_c_str()) - .collect::, _>>() - .map_err(|_| (Vec::<&'a CStr>::new(), wants_layers.clone()))?; - - let mut out_layers = Vec::new(); - let mut unsupported_layers = Vec::new(); - - for layer in wants_layers { - if available_layer_names.contains(&layer) { - out_layers.push(layer); - } else { - unsupported_layers.push(layer); - } - } - - if !unsupported_layers.is_empty() { - Err((out_layers, unsupported_layers)) - } else { - Ok(out_layers) - } - } - } -} - -use raw_window_handle::RawWindowHandle; - #[derive(Debug)] pub struct EguiState { - pub textures: HashMap, + textures: HashMap, #[allow(unused)] descriptor_pool: pipeline::DescriptorPool, descriptor_set: vk::DescriptorSet, @@ -1102,7 +514,7 @@ pub struct EguiState { } #[derive(Debug, Clone, Copy)] -struct EguiTextureInfo { +pub struct EguiTextureInfo { id: texture::TextureId, options: egui::epaint::textures::TextureOptions, } @@ -1412,52 +824,6 @@ impl Renderer2 { pub use vk::Extent2D; -mod debug { - use ash::vk; - use tracing::{event, Level}; - - unsafe fn str_from_raw_parts<'a>(str: *const i8) -> std::borrow::Cow<'a, str> { - use std::{borrow::Cow, ffi}; - if str.is_null() { - Cow::from("") - } else { - ffi::CStr::from_ptr(str).to_string_lossy() - } - } - - pub(super) unsafe extern "system" fn debug_callback( - message_severity: vk::DebugUtilsMessageSeverityFlagsEXT, - message_type: vk::DebugUtilsMessageTypeFlagsEXT, - callback_data: *const vk::DebugUtilsMessengerCallbackDataEXT<'_>, - user_data: *mut core::ffi::c_void, - ) -> vk::Bool32 { - _ = user_data; - let callback_data = *callback_data; - let message_id_number = callback_data.message_id_number; - - let message_id_name = str_from_raw_parts(callback_data.p_message_id_name); - let message = str_from_raw_parts(callback_data.p_message); - - match message_severity { - vk::DebugUtilsMessageSeverityFlagsEXT::ERROR => { - event!(target: "VK::DebugUtils", Level::ERROR, "{message_type:?} [{message_id_name}({message_id_number})]: {message}"); - } - vk::DebugUtilsMessageSeverityFlagsEXT::VERBOSE => { - event!(target: "VK::DebugUtils", Level::TRACE, "{message_type:?} [{message_id_name}({message_id_number})]: {message}"); - } - vk::DebugUtilsMessageSeverityFlagsEXT::INFO => { - event!(target: "VK::DebugUtils", Level::INFO, "{message_type:?} [{message_id_name}({message_id_number})]: {message}"); - } - vk::DebugUtilsMessageSeverityFlagsEXT::WARNING => { - event!(target: "VK::DebugUtils", Level::WARN, "{message_type:?} [{message_id_name}({message_id_number})]: {message}"); - } - _ => unreachable!(), - } - - vk::FALSE - } -} - pub mod utils { #![allow(dead_code)] diff --git a/crates/renderer/src/util.rs b/crates/renderer/src/util.rs index 7cafc4d..0ff294a 100644 --- a/crates/renderer/src/util.rs +++ b/crates/renderer/src/util.rs @@ -546,6 +546,7 @@ impl Drop for RawMutexGuard<'_, T> { pub struct CStringList { pub strings: Box<[*const i8]>, + #[allow(unused)] bytes: Box<[u8]>, }