aboutsummaryrefslogtreecommitdiff
path: root/adapters/fparkan-render-vulkan/src/ffi/validation.rs
diff options
context:
space:
mode:
Diffstat (limited to 'adapters/fparkan-render-vulkan/src/ffi/validation.rs')
-rw-r--r--adapters/fparkan-render-vulkan/src/ffi/validation.rs125
1 files changed, 125 insertions, 0 deletions
diff --git a/adapters/fparkan-render-vulkan/src/ffi/validation.rs b/adapters/fparkan-render-vulkan/src/ffi/validation.rs
new file mode 100644
index 0000000..2045373
--- /dev/null
+++ b/adapters/fparkan-render-vulkan/src/ffi/validation.rs
@@ -0,0 +1,125 @@
+#![allow(unsafe_code)]
+
+use ash::vk;
+use std::collections::BTreeSet;
+use std::ffi::CStr;
+use std::sync::{
+ atomic::{AtomicU32, Ordering},
+ Mutex,
+};
+
+use super::{VulkanInstanceProbe, VulkanSmokeRendererError, VulkanValidationReport};
+
+struct VulkanValidationShared {
+ warning_count: AtomicU32,
+ error_count: AtomicU32,
+ vuids: Mutex<BTreeSet<String>>,
+}
+
+impl Default for VulkanValidationShared {
+ fn default() -> Self {
+ Self {
+ warning_count: AtomicU32::new(0),
+ error_count: AtomicU32::new(0),
+ vuids: Mutex::new(BTreeSet::new()),
+ }
+ }
+}
+
+pub(super) struct VulkanValidationMessenger {
+ loader: ash::ext::debug_utils::Instance,
+ messenger: vk::DebugUtilsMessengerEXT,
+ shared: Box<VulkanValidationShared>,
+}
+
+impl VulkanValidationMessenger {
+ pub(super) fn report(&self) -> VulkanValidationReport {
+ let vuids = self
+ .shared
+ .vuids
+ .lock()
+ .map(|values| values.iter().cloned().collect::<Vec<_>>())
+ .unwrap_or_default();
+ VulkanValidationReport {
+ warning_count: self.shared.warning_count.load(Ordering::Relaxed),
+ error_count: self.shared.error_count.load(Ordering::Relaxed),
+ vuids,
+ }
+ }
+}
+
+impl Drop for VulkanValidationMessenger {
+ fn drop(&mut self) {
+ // SAFETY: The messenger belongs to this instance-level loader and is destroyed once.
+ unsafe {
+ self.loader
+ .destroy_debug_utils_messenger(self.messenger, None);
+ };
+ }
+}
+
+unsafe extern "system" fn vulkan_validation_callback(
+ message_severity: vk::DebugUtilsMessageSeverityFlagsEXT,
+ _message_types: vk::DebugUtilsMessageTypeFlagsEXT,
+ callback_data: *const vk::DebugUtilsMessengerCallbackDataEXT<'_>,
+ user_data: *mut std::ffi::c_void,
+) -> vk::Bool32 {
+ // SAFETY: The debug messenger stores a stable pointer to `VulkanValidationShared` for the messenger lifetime.
+ let Some(shared) = (unsafe { (user_data as *const VulkanValidationShared).as_ref() }) else {
+ return vk::FALSE;
+ };
+ if message_severity.contains(vk::DebugUtilsMessageSeverityFlagsEXT::ERROR) {
+ shared.error_count.fetch_add(1, Ordering::Relaxed);
+ } else if message_severity.contains(vk::DebugUtilsMessageSeverityFlagsEXT::WARNING) {
+ shared.warning_count.fetch_add(1, Ordering::Relaxed);
+ }
+ // SAFETY: Vulkan invokes the callback with either a null pointer or a valid callback-data payload.
+ let Some(callback_data) = (unsafe { callback_data.as_ref() }) else {
+ return vk::FALSE;
+ };
+ if let Some(vuid) = (!callback_data.p_message_id_name.is_null()).then(|| {
+ // SAFETY: `p_message_id_name` is a Vulkan-owned NUL-terminated string for the callback duration.
+ unsafe { CStr::from_ptr(callback_data.p_message_id_name) }
+ .to_string_lossy()
+ .into_owned()
+ }) {
+ if vuid.starts_with("VUID-") {
+ if let Ok(mut vuids) = shared.vuids.lock() {
+ vuids.insert(vuid);
+ }
+ }
+ }
+ vk::FALSE
+}
+
+pub(super) fn create_validation_messenger(
+ instance: &VulkanInstanceProbe,
+) -> Result<VulkanValidationMessenger, VulkanSmokeRendererError> {
+ let shared = Box::new(VulkanValidationShared::default());
+ let loader = ash::ext::debug_utils::Instance::new(&instance.entry, &instance.instance);
+ let create_info = vk::DebugUtilsMessengerCreateInfoEXT::default()
+ .message_severity(
+ vk::DebugUtilsMessageSeverityFlagsEXT::WARNING
+ | vk::DebugUtilsMessageSeverityFlagsEXT::ERROR,
+ )
+ .message_type(
+ vk::DebugUtilsMessageTypeFlagsEXT::GENERAL
+ | vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION
+ | vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE,
+ )
+ .pfn_user_callback(Some(vulkan_validation_callback))
+ .user_data((&raw const *shared).cast_mut().cast());
+ let messenger =
+ // SAFETY: The create info points at a stable boxed user-data allocation for the messenger lifetime.
+ unsafe { loader.create_debug_utils_messenger(&create_info, None) }.map_err(|error| {
+ VulkanSmokeRendererError::VulkanOperation {
+ context: "vkCreateDebugUtilsMessengerEXT",
+ result: error,
+ }
+ })?;
+ Ok(VulkanValidationMessenger {
+ loader,
+ messenger,
+ shared,
+ })
+}