diff --git a/private/raid/application.cpp b/private/raid/application.cpp index 9f3682d..16dd302 100644 --- a/private/raid/application.cpp +++ b/private/raid/application.cpp @@ -459,6 +459,7 @@ bool Application::init() { return false; } + break; } if (!initImGui()) { @@ -501,6 +502,10 @@ void Application::cleanup() vk.DeviceWaitIdle(vk.device); vk.DestroyDevice(vk.device, nullptr); } + if (vk.debugUtilsMessenger != nullptr) + { + vk.DestroyDebugUtilsMessengerEXT(vk.instance, vk.debugUtilsMessenger, /* pAllocator = */ nullptr); + } if (vk.instance != nullptr) { vk.DestroyInstance(vk.instance, nullptr); @@ -592,18 +597,90 @@ bool Application::initOpenGL() bool Application::initVulkan() { vk.GetInstanceProc = reinterpret_cast(SDL_Vulkan_GetVkGetInstanceProcAddr()); + vk.EnumerateInstanceLayerProperties = reinterpret_cast(vk.GetInstanceProc(nullptr, "vkEnumerateInstanceLayerProperties")); + vk.EnumerateInstanceExtensionProperties = reinterpret_cast(vk.GetInstanceProc(nullptr, "vkEnumerateInstanceExtensionProperties")); vk.CreateInstance = reinterpret_cast(vk.GetInstanceProc(nullptr, "vkCreateInstance")); + std::uint32_t numInstanceLayers = 0; + if (const VkResult result = vk.EnumerateInstanceLayerProperties(&numInstanceLayers, nullptr); result != VK_SUCCESS && result != VK_INCOMPLETE) + { + msgError("Error enumerating instance layers: 0x{:x}.", static_cast(result)); + return false; + } + std::vector instanceLayers; + instanceLayers.resize(numInstanceLayers); + if (const VkResult result = vk.EnumerateInstanceLayerProperties(&numInstanceLayers, instanceLayers.data()); result != VK_SUCCESS && result != VK_INCOMPLETE) + { + msgError("Error enumerating instance layers: 0x{:x}.", static_cast(result)); + return false; + } + + std::vector enabledInstanceLayers; + for (const VkLayerProperties& props : instanceLayers) { + if (std::strcmp(props.layerName, "VK_LAYER_KHRONOS_validation") == 0) { + enabledInstanceLayers.push_back("VK_LAYER_KHRONOS_validation"); + } + } + + std::uint32_t numInstanceExtensions = 0; + if (const VkResult result = vk.EnumerateInstanceExtensionProperties(nullptr, &numInstanceExtensions, nullptr); result != VK_SUCCESS && result != VK_INCOMPLETE) + { + msgError("Error enumerating instance extensions: 0x{:x}.", static_cast(result)); + return false; + } + std::vector instanceExtensions; + instanceExtensions.resize(numInstanceExtensions); + if (const VkResult result = vk.EnumerateInstanceExtensionProperties(nullptr, &numInstanceExtensions, instanceExtensions.data()); result != VK_SUCCESS && result != VK_INCOMPLETE) + { + msgError("Error enumerating instance extensions: 0x{:x}.", static_cast(result)); + return false; + } + + std::uint32_t numSdlExtensions = 0; + const char* const* sdlExtensions = SDL_Vulkan_GetInstanceExtensions(&numSdlExtensions); + std::uint32_t numSupportedSdlExtensions = 0; + + bool hasDebugUtils = false; + std::vector enabledInstanceExtensions; + for (const VkExtensionProperties& props : instanceExtensions) { + if (std::strcmp(props.extensionName, "VK_EXT_debug_utils") == 0) + { + enabledInstanceExtensions.push_back("VK_EXT_debug_utils"); + hasDebugUtils = true; + } + else + { + for (std::uint32_t idx = 0; idx < numSdlExtensions; ++idx) + { + if (std::strncmp(props.extensionName, sdlExtensions[idx], VK_MAX_EXTENSION_NAME_SIZE) == 0) + { + enabledInstanceExtensions.push_back(sdlExtensions[idx]); + ++numSupportedSdlExtensions; + } + } + } + } + + if (numSupportedSdlExtensions != numSdlExtensions) + { + msgError("Cannot create Vulkan device, not all required instance extensions are supported."); + return false; + } + const VkApplicationInfo applicationInfo = { .apiVersion = vkMakeApiVersion(0, 1, 3, 0) // TODO: probably should let the user specify this? }; const VkInstanceCreateInfo instanceCreateInfo = { - .pApplicationInfo = &applicationInfo + .pApplicationInfo = &applicationInfo, + .enabledLayerCount = static_cast(enabledInstanceLayers.size()), + .ppEnabledLayerNames = enabledInstanceLayers.data(), + .enabledExtensionCount = static_cast(enabledInstanceExtensions.size()), + .ppEnabledExtensionNames = enabledInstanceExtensions.data() }; if (const VkResult result = vk.CreateInstance(&instanceCreateInfo, nullptr, &vk.instance); result != VK_SUCCESS) { - msgError("Error creating Vulkan instance: {:x}.", static_cast(result)); + msgError("Error creating Vulkan instance: 0x{:x}.", static_cast(result)); return false; } @@ -615,11 +692,35 @@ bool Application::initVulkan() vk.DeviceWaitIdle = reinterpret_cast(vk.GetInstanceProc(vk.instance, "vkDeviceWaitIdle")); vk.GetDeviceQueue = reinterpret_cast(vk.GetInstanceProc(vk.instance, "vkGetDeviceQueue")); + if (hasDebugUtils) + { + vk.CreateDebugUtilsMessengerEXT = reinterpret_cast(vk.GetInstanceProc(vk.instance, "vkCreateDebugUtilsMessengerEXT")); + vk.DestroyDebugUtilsMessengerEXT = reinterpret_cast(vk.GetInstanceProc(vk.instance, "vkDestroyDebugUtilsMessengerEXT")); + + const VkDebugUtilsMessengerCreateInfoEXT messengerCreateInfo = { + .messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT, + .messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT, + .pfnUserCallback = [](VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageTypes, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + return static_cast(pUserData)->handleDebugUtilsMessage(messageSeverity, messageTypes, pCallbackData); + }, + .pUserData = this + }; + + if (const VkResult result = vk.CreateDebugUtilsMessengerEXT(vk.instance, &messengerCreateInfo, /* pAllocator = */ nullptr, &vk.debugUtilsMessenger); result != VK_SUCCESS) + { + msgWarning("Error creating Vulkan debug utils messenger: 0x{:x}.", static_cast(result)); + vk.debugUtilsMessenger = nullptr; + } + } + else { + vk.debugUtilsMessenger = nullptr; + } + // TODO: this is really cheap... (but should be sufficient in most cases) std::uint32_t physicalDeviceCount = 1; if (const VkResult result = vk.EnumeratePhysicalDevices(vk.instance, &physicalDeviceCount, &vk.physicalDevice); result != VK_SUCCESS) { - msgError("Error enumerating Vulkan physical devices: {:x}.", static_cast(result)); + msgError("Error enumerating Vulkan physical devices: 0x{:x}.", static_cast(result)); return false; } @@ -634,7 +735,10 @@ bool Application::initVulkan() std::uint32_t queueFamilyIndex = 0; for (; queueFamilyIndex < queueFamilyPropertyCount; ++queueFamilyIndex) { - if (queueFamilyProperties[queueFamilyIndex].queueFamilyProperties.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + const bool supportsGraphics = queueFamilyProperties[queueFamilyIndex].queueFamilyProperties.queueFlags & VK_QUEUE_GRAPHICS_BIT; + const bool supportsPresent = SDL_Vulkan_GetPresentationSupport(vk.instance, vk.physicalDevice, queueFamilyIndex); + + if (supportsGraphics && supportsPresent) { break; } } @@ -652,7 +756,7 @@ bool Application::initVulkan() .pQueuePriorities = &queuePriority }; static const std::array DEVICE_EXTENSIONS = { - "VK_KHR_surface" + "VK_KHR_swapchain" }; const VkDeviceCreateInfo deviceCreateInfo = { .queueCreateInfoCount = 1, @@ -662,7 +766,7 @@ bool Application::initVulkan() }; if (const VkResult result = vk.CreateDevice(vk.physicalDevice, &deviceCreateInfo, nullptr, &vk.device); result != VK_SUCCESS) { - msgError("Error creating Vulkan device: {:x}.", static_cast(result)); + msgError("Error creating Vulkan device: 0x{:x}.", static_cast(result)); return false; } vk.GetDeviceQueue(vk.device, /* queueFamilyIndex = */ 0, /* queueIndex = */ 0, &vk.queue); @@ -788,6 +892,25 @@ void Application::saveImGuiConfig() } } +auto Application::handleDebugUtilsMessage(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, + VkDebugUtilsMessageTypeFlagsEXT /* messageTypes */, + const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData) -> VkBool32 +{ + switch (messageSeverity) + { + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT: + msgError("Vulkan debug error message: {}.", pCallbackData->pMessage); + break; + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT: + msgWarning("Vulkan debug warning message: {}.", pCallbackData->pMessage); + break; + default: + msgInfo("Vulkan debug message: {}.", pCallbackData->pMessage); + break; + } + return false; +} + void QuickApp::preInit(QuickAppOptions options) { MIJIN_ASSERT_FATAL(options.callbacks.render, "Missing render callback."); diff --git a/public/raid/application.hpp b/public/raid/application.hpp index 3cf67bd..774d6c8 100644 --- a/public/raid/application.hpp +++ b/public/raid/application.hpp @@ -157,23 +157,23 @@ protected: void msgInfo(const char* text) { handleMessage({ - .severity = MessageSeverity::INFO, - .text = text - }); + .severity = MessageSeverity::INFO, + .text = text + }); } void msgWarning(const char* text) { handleMessage({ - .severity = MessageSeverity::WARNING, - .text = text - }); + .severity = MessageSeverity::WARNING, + .text = text + }); } void msgError(const char* text) { handleMessage({ - .severity = MessageSeverity::ERROR, - .text = text - }); + .severity = MessageSeverity::ERROR, + .text = text + }); } void msgInfo(const std::string& text) { @@ -215,6 +215,10 @@ private: void handleSDLEvents(); void loadImGuiConfig(); void saveImGuiConfig(); + + VkBool32 handleDebugUtilsMessage(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, + VkDebugUtilsMessageTypeFlagsEXT messageTypes, + const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData); }; using render_cb_t = std::function; diff --git a/public/raid/internal/vulkan.hpp b/public/raid/internal/vulkan.hpp index 025ef90..8bc83c1 100644 --- a/public/raid/internal/vulkan.hpp +++ b/public/raid/internal/vulkan.hpp @@ -19,17 +19,29 @@ namespace raid struct MixinVulkanApplication { #if !RAID_USE_VULKAN_H + // basic types + using VkBool32 = std::uint32_t; + // flags using VkFlags = std::uint32_t; using VkInstanceCreateFlags = VkFlags; + using VkDebugUtilsMessengerCreateFlagsEXT = VkFlags; + using VkDebugUtilsMessageSeverityFlagsEXT = VkFlags; + using VkDebugUtilsMessageTypeFlagsEXT = VkFlags; + using VkDebugUtilsMessengerCallbackDataFlagsEXT = VkFlags; using VkQueueFlags = VkFlags; using VkDeviceCreateFlags = VkFlags; using VkDeviceQueueCreateFlags = VkFlags; + // constants + static constexpr std::uint32_t VK_MAX_EXTENSION_NAME_SIZE = 256; + static constexpr std::uint32_t VK_MAX_DESCRIPTION_SIZE = 256; + // enums enum VkResult { - VK_SUCCESS = 0 + VK_SUCCESS = 0, + VK_INCOMPLETE = 5 }; enum VkStructureType { @@ -37,10 +49,28 @@ struct MixinVulkanApplication VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO = 1, VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO = 2, VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO = 3, - VK_STRUCTURE_TYPE_QUEUE_FAMILY_PROPERTIES_2 = 1000059005 + VK_STRUCTURE_TYPE_QUEUE_FAMILY_PROPERTIES_2 = 1000059005, + VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT = 1000128000, + VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT = 1000128002, + VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CALLBACK_DATA_EXT = 1000128003, + VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT = 1000128004 }; enum VkSystemAllocationScope {}; enum VkInternalAllocationType {}; + enum VkObjectType {}; + enum VkDebugUtilsMessageSeverityFlagBitsEXT : VkFlags + { + VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT = 0x00000001, + VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT = 0x00000010, + VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT = 0x00000100, + VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT = 0x00001000 + }; + enum VkDebugUtilsMessageTypeFlagBitsEXT : VkFlags + { + VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT = 0x00000001, + VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT = 0x00000002, + VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT = 0x00000004 + }; enum VkQueueFlagBits : VkFlags { VK_QUEUE_GRAPHICS_BIT = 0x00000001 @@ -49,6 +79,19 @@ struct MixinVulkanApplication // handles using VkDevice = struct VkDevice_*; using VkQueue = struct VkQueue_*; + using VkDebugUtilsMessengerEXT = struct VkDebugUtilsMessengerEXT_*; + + struct VkDebugUtilsMessengerCallbackDataEXT; + + // Vulkan function pointer types + using PFN_vkVoidFunction = void (*)(); + // TODO: VKAPI_PTR? + using PFN_vkAllocationFunction = void* (*)(void* pUserData, std::size_t size, std::size_t alignment, VkSystemAllocationScope allocationScope); + using PFN_vkReallocationFunction = void* (*)(void* pUserData, void* pOriginal, std::size_t size, std::size_t alignment, VkSystemAllocationScope allocationScope); + using PFN_vkFreeFunction = void (*)(void* pUserData, void* pMemory); + using PFN_vkInternalAllocationNotification = void (*)(void* pUserData, std::size_t size, VkInternalAllocationType allocationType, VkSystemAllocationScope allocationScope); + using PFN_vkInternalFreeNotification = void (*)(void* pUserData, std::size_t size, VkInternalAllocationType allocationType, VkSystemAllocationScope allocationScope); + using PFN_vkDebugUtilsMessengerCallbackEXT = VkBool32 (*)(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageTypes, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData); // structs struct VkExtent3D @@ -58,6 +101,20 @@ struct MixinVulkanApplication std::uint32_t depth = 0; }; + struct VkLayerProperties + { + char layerName[VK_MAX_EXTENSION_NAME_SIZE] = {0}; + std::uint32_t specVersion = 0; + std::uint32_t implementationVersion = 0; + char description[VK_MAX_DESCRIPTION_SIZE] = {0}; + }; + + struct VkExtensionProperties + { + char extensionName[VK_MAX_EXTENSION_NAME_SIZE] = {0}; + std::uint32_t specVersion = 0; + }; + struct VkApplicationInfo { VkStructureType sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; @@ -80,6 +137,50 @@ struct MixinVulkanApplication const char* const* ppEnabledExtensionNames = nullptr; }; + struct VkDebugUtilsMessengerCreateInfoEXT + { + VkStructureType sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + const void* pNext = nullptr; + VkDebugUtilsMessengerCreateFlagsEXT flags = 0; + VkDebugUtilsMessageSeverityFlagsEXT messageSeverity = 0; + VkDebugUtilsMessageTypeFlagsEXT messageType = 0; + PFN_vkDebugUtilsMessengerCallbackEXT pfnUserCallback; + void* pUserData = nullptr; + }; + + struct VkDebugUtilsLabelEXT + { + VkStructureType sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT; + const void* pNext = nullptr; + const char* pLabelName = nullptr; + float color[4] = {0.f, 0.f, 0.f, 0.f}; + }; + + struct VkDebugUtilsObjectNameInfoEXT + { + VkStructureType sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT; + const void* pNext = nullptr; + VkObjectType objectType = static_cast(0); + std::uint64_t objectHandle = 0; + const char* pObjectName = nullptr; + }; + + struct VkDebugUtilsMessengerCallbackDataEXT + { + VkStructureType sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CALLBACK_DATA_EXT; + const void* pNext = nullptr; + VkDebugUtilsMessengerCallbackDataFlagsEXT flags = 0; + const char* pMessageIdName; + std::int32_t messageIdNumber; + const char* pMessage; + std::uint32_t queueLabelCount; + const VkDebugUtilsLabelEXT* pQueueLabels; + std::uint32_t cmdBufLabelCount; + const VkDebugUtilsLabelEXT* pCmdBufLabels; + std::uint32_t objectCount; + const VkDebugUtilsObjectNameInfoEXT* pObjects; + }; + struct VkQueueFamilyProperties { VkQueueFlags queueFlags = 0; @@ -120,14 +221,6 @@ struct MixinVulkanApplication const VkPhysicalDeviceFeatures* pEnabledFeatures = nullptr; }; - // Vulkan function pointer types - using PFN_vkVoidFunction = void (*)(); - // TODO: VKAPI_PTR? - using PFN_vkAllocationFunction = void* (*)(void* pUserData, std::size_t size, std::size_t alignment, VkSystemAllocationScope allocationScope); - using PFN_vkReallocationFunction = void* (*)(void* pUserData, void* pOriginal, std::size_t size, std::size_t alignment, VkSystemAllocationScope allocationScope); - using PFN_vkFreeFunction = void (*)(void* pUserData, void* pMemory); - using PFN_vkInternalAllocationNotification = void (*)(void* pUserData, std::size_t size, VkInternalAllocationType allocationType, VkSystemAllocationScope allocationScope); - using PFN_vkInternalFreeNotification = void (*)(void* pUserData, std::size_t size, VkInternalAllocationType allocationType, VkSystemAllocationScope allocationScope); struct VkAllocationCallbacks { void* pUserData = nullptr; @@ -138,27 +231,41 @@ struct MixinVulkanApplication PFN_vkInternalFreeNotification pfnInternalFree = nullptr; }; - // function pointers + // -- function pointers + // instance creation using vkGetInstanceProcAddr_fn_t = PFN_vkVoidFunction (*)(VkInstance instance, const char* pName); + using vkEnumerateInstanceLayerProperties_fn_t = VkResult (*)(std::uint32_t* pPropertyCount, VkLayerProperties* pProperties); + using vkEnumerateInstanceExtensionProperties_fn_t = VkResult (*)(const char* pLayerName, std::uint32_t* pPropertyCount, VkExtensionProperties* pProperties); + using vkCreateDebugUtilsMessengerEXT_fn_t = VkResult (*)(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pMessenger); + using vkDestroyDebugUtilsMessengerEXT_fn_t = void (*)(VkInstance instance, VkDebugUtilsMessengerEXT messenger, const VkAllocationCallbacks* pAllocator); using vkCreateInstance_fn_t = VkResult (*)(const VkInstanceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkInstance* pInstance); using vkDestroyInstance_fn_t = void (*)(VkInstance instance, const VkAllocationCallbacks* pAllocator); + + // device creation using vkEnumeratePhysicalDevices_fn_t = VkResult (*)(VkInstance instance, std::uint32_t* pPhysicalDeviceCount, VkPhysicalDevice* pPhysicalDevices); using vkGetPhysicalDeviceQueueFamilyProperties2_fn_t = void (*)(VkPhysicalDevice physicalDevice, std::uint32_t* pQueueFamilyPropertyCount, VkQueueFamilyProperties2* pQueueFamilyProperties); using vkCreateDevice_fn_t = VkResult (*)(VkPhysicalDevice physicalDevice, const VkDeviceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDevice* pDevice); using vkDestroyDevice_fn_t = void (*)(VkDevice device, const VkAllocationCallbacks* pAllocator); using vkDeviceWaitIdle_fn_t = VkResult (*)(VkDevice device); + + // other creation using vkGetDeviceQueue_fn_t = void (*)(VkDevice device, std::uint32_t queueFamilyIndex, std::uint32_t queueIndex, VkQueue* pQueue); #endif // !RAID_USE_VULKAN_H struct VulkanData { VkInstance instance; + VkDebugUtilsMessengerEXT debugUtilsMessenger; VkPhysicalDevice physicalDevice; VkDevice device; VkQueue queue; VkSurfaceKHR surface; vkGetInstanceProcAddr_fn_t GetInstanceProc; + vkEnumerateInstanceLayerProperties_fn_t EnumerateInstanceLayerProperties; + vkEnumerateInstanceExtensionProperties_fn_t EnumerateInstanceExtensionProperties; + vkCreateDebugUtilsMessengerEXT_fn_t CreateDebugUtilsMessengerEXT; + vkDestroyDebugUtilsMessengerEXT_fn_t DestroyDebugUtilsMessengerEXT; vkCreateInstance_fn_t CreateInstance; vkDestroyInstance_fn_t DestroyInstance; vkEnumeratePhysicalDevices_fn_t EnumeratePhysicalDevices;