diff --git a/meson.build b/meson.build index 1af556e..4e243f8 100644 --- a/meson.build +++ b/meson.build @@ -14,6 +14,7 @@ common_cpp_args = [ '-Wno-c++98-compat-pedantic', '-Wno-pre-c++20-compat-pedantic', '-Wno-padded', + '-Wno-switch-default', '-mavx2', ] diff --git a/src/init/debug_messenger.cpp b/src/init/debug_messenger.cpp index ad7696a..7a94313 100644 --- a/src/init/debug_messenger.cpp +++ b/src/init/debug_messenger.cpp @@ -1,32 +1,97 @@ +#include #include #include "debug_messenger.hpp" -fn DebugMessenger::create(const vk::Instance& instance) -> vk::UniqueDebugUtilsMessengerEXT { +fn DebugMessenger::create(const vk::Instance& instance, Config config) + -> std::expected { #ifdef NDEBUG return nullptr; #else - vk::DebugUtilsMessengerCreateInfoEXT createInfo { - .messageSeverity = vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | - vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | - vk::DebugUtilsMessageSeverityFlagBitsEXT::eError, - .messageType = vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | - vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation | - vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance, - .pfnUserCallback = debugCallback, - .pUserData = nullptr, - }; + try { + vk::DebugUtilsMessengerCreateInfoEXT createInfo { + .messageSeverity = config.severity_flags, + .messageType = config.type_flags, + .pfnUserCallback = debugCallback, + .pUserData = &config, + }; - return instance.createDebugUtilsMessengerEXTUnique(createInfo); + return instance.createDebugUtilsMessengerEXTUnique(createInfo); + } catch (const vk::SystemError& e) { + return std::unexpected(fmt::format("Failed to create debug messenger: {}", e.what())); + } #endif } VKAPI_ATTR VkBool32 VKAPI_CALL DebugMessenger::debugCallback( - VkDebugUtilsMessageSeverityFlagBitsEXT /*messageSeverity*/, - VkDebugUtilsMessageTypeFlagsEXT /*messageType*/, + VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, + VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, - void* /*pUserData*/ + void* pUserData ) { - fmt::println("validation layer: {}", pCallbackData->pMessage); + const auto* config = static_cast(pUserData); + + Message msg { .message = pCallbackData->pMessage, + .severity = static_cast(messageSeverity), + .type = static_cast(messageType), + .function_name = pCallbackData->pMessageIdName + ? std::optional(pCallbackData->pMessageIdName) + : std::nullopt, + .id = pCallbackData->messageIdNumber }; + + const auto formattedMessage = formatMessage(msg); + + if (config && config->use_stderr_for_errors && + (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT)) { + fmt::println(stderr, "{}", formattedMessage); + } else { + fmt::println("{}", formattedMessage); + } + return VK_FALSE; } + +fn DebugMessenger::formatMessage(const Message& msg) -> std::string { + // Color based on severity + fmt::color textColor = fmt::color::white; + std::string_view severityText; + + switch (msg.severity) { + case vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose: + textColor = fmt::color::light_blue; + severityText = "VERBOSE"; + break; + case vk::DebugUtilsMessageSeverityFlagBitsEXT::eInfo: + textColor = fmt::color::white; + severityText = "INFO"; + break; + case vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning: + textColor = fmt::color::yellow; + severityText = "WARNING"; + break; + case vk::DebugUtilsMessageSeverityFlagBitsEXT::eError: + textColor = fmt::color::red; + severityText = "ERROR"; + break; + } + + // Build message type string + std::string typeStr; + if (msg.type & vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral) + typeStr += "GENERAL "; + if (msg.type & vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation) + typeStr += "VALIDATION "; + if (msg.type & vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance) + typeStr += "PERFORMANCE "; + + // Format the message with color and structure + return fmt::format( + fmt::emphasis::bold | fg(textColor), + "[{}] {} {}{}: {}", + severityText, + typeStr, + msg.function_name.has_value() ? fmt::format("in {} ", *msg.function_name) : "", + msg.id, + msg.message + ); +} diff --git a/src/init/debug_messenger.hpp b/src/init/debug_messenger.hpp index 1d008e1..f8b59a9 100644 --- a/src/init/debug_messenger.hpp +++ b/src/init/debug_messenger.hpp @@ -2,12 +2,35 @@ #define VULKAN_HPP_DISPATCH_LOADER_DYNAMIC 1 // Use dynamic dispatch for Vulkan functions #define VULKAN_HPP_NO_CONSTRUCTORS +#include +#include +#include #include #include "../util/types.hpp" class DebugMessenger { public: + /** + * @brief Configuration for debug messenger + */ + struct Config { + vk::DebugUtilsMessageSeverityFlagsEXT severity_flags; + vk::DebugUtilsMessageTypeFlagsEXT type_flags; + bool use_stderr_for_errors; + }; + + /** + * @brief Debug message information + */ + struct Message { + std::string_view message; + vk::DebugUtilsMessageSeverityFlagBitsEXT severity; + vk::DebugUtilsMessageTypeFlagsEXT type; + std::optional function_name; + i32 id; + }; + DebugMessenger() = default; DebugMessenger(const DebugMessenger&) = default; DebugMessenger(DebugMessenger&&) = delete; @@ -15,7 +38,22 @@ class DebugMessenger { fn operator=(DebugMessenger&&)->DebugMessenger& = delete; ~DebugMessenger() = default; - static fn create(const vk::Instance& instance) -> vk::UniqueDebugUtilsMessengerEXT; + /** + * @brief Creates a debug messenger with the specified configuration + * @param instance Vulkan instance + * @param config Debug messenger configuration + * @return Expected containing the debug messenger or an error message + */ + static fn create( + const vk::Instance& instance, + Config config = { .severity_flags = vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | + vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | + vk::DebugUtilsMessageSeverityFlagBitsEXT::eError, + .type_flags = vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | + vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation | + vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance, + .use_stderr_for_errors = true } + ) -> std::expected; private: static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback( @@ -24,4 +62,11 @@ class DebugMessenger { const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData ); + + /** + * @brief Formats a debug message for output + * @param msg Message information + * @return Formatted message string + */ + static fn formatMessage(const Message& msg) -> std::string; }; diff --git a/src/init/device_manager.cpp b/src/init/device_manager.cpp index 09e0005..f054178 100644 --- a/src/init/device_manager.cpp +++ b/src/init/device_manager.cpp @@ -1,118 +1,118 @@ +#include #include #include -#include +#include #include "device_manager.hpp" #include "../structs/swap_chain_support_details.hpp" +struct PhysicalDeviceInfo { + vk::PhysicalDevice physical_device; + vk::SampleCountFlagBits msaa_samples; + f32 max_line_width; + bool wide_line_support; +}; + +struct LogicalDeviceInfo { + vk::UniqueDevice device; + vk::Queue graphics_queue; + vk::Queue present_queue; +}; + fn DeviceManager::pickPhysicalDevice( - const vk::Instance& instance, - const vk::SurfaceKHR& surface, - const std::vector& requiredExtensions -) -> std::tuple { + const vk::Instance& instance, + const vk::SurfaceKHR& surface, + std::span requiredExtensions +) -> std::expected { // Get all available physical devices - std::vector devices = instance.enumeratePhysicalDevices(); + auto devices = instance.enumeratePhysicalDevices(); if (devices.empty()) - throw std::runtime_error("Failed to find GPUs with Vulkan support!"); + return std::unexpected("Failed to find GPUs with Vulkan support!"); #ifndef NDEBUG fmt::println("Available devices:"); #endif - vk::PhysicalDevice physicalDevice; - vk::SampleCountFlagBits msaaSamples = vk::SampleCountFlagBits::e1; - f32 maxLineWidth = 1.0F; - bool wideLineSupport = false; + // Find the first suitable device using ranges + auto suitableDevice = std::ranges::find_if(devices, [&](const auto& device) { + return isDeviceSuitable(device, surface, requiredExtensions); + }); - // For each device, - for (const vk::PhysicalDevice& device : devices) { -#ifndef NDEBUG - vk::PhysicalDeviceProperties properties = device.getProperties(); - fmt::println("\t{}", properties.deviceName.data()); -#endif + if (suitableDevice == devices.end()) + return std::unexpected("Failed to find a suitable GPU!"); - // Set the first suitable device as the physical device - if (isDeviceSuitable(device, surface, requiredExtensions)) { - physicalDevice = device; - msaaSamples = getMaxUsableSampleCount(physicalDevice); - - // Get the device properties for line width limits - vk::PhysicalDeviceProperties deviceProperties = device.getProperties(); - maxLineWidth = deviceProperties.limits.lineWidthRange[1]; - wideLineSupport = deviceProperties.limits.lineWidthRange[1] > 1.0F; + vk::PhysicalDeviceProperties deviceProperties = suitableDevice->getProperties(); #ifndef NDEBUG - fmt::println("Maximum supported line width: {}", maxLineWidth); - fmt::println("Wide lines supported: {}", wideLineSupport ? "yes" : "no"); + fmt::println("\t{}", deviceProperties.deviceName.data()); + fmt::println("Maximum supported line width: {}", deviceProperties.limits.lineWidthRange[1]); + fmt::println("Wide lines supported: {}", deviceProperties.limits.lineWidthRange[1] > 1.0F ? "yes" : "no"); #endif - break; - } - } - // If no suitable device was found, throw an error - if (!physicalDevice) - throw std::runtime_error("Failed to find a suitable GPU!"); - - return std::make_tuple(physicalDevice, msaaSamples, maxLineWidth, wideLineSupport); + return PhysicalDeviceInfo { + .physical_device = *suitableDevice, + .msaa_samples = getMaxUsableSampleCount(*suitableDevice), + .max_line_width = deviceProperties.limits.lineWidthRange[1], + .wide_line_support = deviceProperties.limits.lineWidthRange[1] > 1.0F, + }; } fn DeviceManager::createLogicalDevice( - const vk::PhysicalDevice& physicalDevice, - const vk::SurfaceKHR& surface, - const std::vector& requiredExtensions, - const bool enableValidationLayers, - const std::vector& validationLayers -) -> std::tuple { - QueueFamilyIndices indices = findQueueFamilies(physicalDevice, surface); + const vk::PhysicalDevice& physicalDevice, + const vk::SurfaceKHR& surface, + std::span requiredExtensions, + bool enableValidationLayers, + std::span validationLayers +) -> std::expected { + try { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice, surface); - // Create a set of unique queue families needed - std::set uniqueQueueFamilies = { indices.graphics_family.value(), indices.present_family.value() }; + std::set uniqueQueueFamilies = { indices.graphics_family.value(), indices.present_family.value() }; - // Create queue create infos for each unique queue family - std::vector queueCreateInfos; - f32 queuePriority = 1.0F; + std::vector queueCreateInfos; + f32 queuePriority = 1.0F; - for (u32 queueFamily : uniqueQueueFamilies) { - vk::DeviceQueueCreateInfo queueCreateInfo { - .queueFamilyIndex = queueFamily, - .queueCount = 1, - .pQueuePriorities = &queuePriority, + // Use ranges to transform unique queue families into create infos + std::ranges::transform(uniqueQueueFamilies, std::back_inserter(queueCreateInfos), [&](u32 queueFamily) { + return vk::DeviceQueueCreateInfo { + .queueFamilyIndex = queueFamily, + .queueCount = 1, + .pQueuePriorities = &queuePriority, + }; + }); + + vk::PhysicalDeviceFeatures deviceFeatures { + .wideLines = vk::True, + .samplerAnisotropy = vk::True, }; - queueCreateInfos.push_back(queueCreateInfo); + + vk::DeviceCreateInfo createInfo { + .queueCreateInfoCount = static_cast(queueCreateInfos.size()), + .pQueueCreateInfos = queueCreateInfos.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data(), + .pEnabledFeatures = &deviceFeatures, + }; + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } + + auto device = physicalDevice.createDeviceUnique(createInfo); + auto graphicsQueue = device->getQueue(indices.graphics_family.value(), 0); + auto presentQueue = device->getQueue(indices.present_family.value(), 0); + + return LogicalDeviceInfo { + .device = std::move(device), + .graphics_queue = graphicsQueue, + .present_queue = presentQueue, + }; + } catch (const vk::SystemError& e) { + return std::unexpected(fmt::format("Failed to create logical device: {}", e.what())); } - - // Specify device features - vk::PhysicalDeviceFeatures deviceFeatures { - .fillModeNonSolid = true, - .wideLines = true, - .samplerAnisotropy = true, - }; - - // Create the logical device - vk::DeviceCreateInfo createInfo { - .queueCreateInfoCount = static_cast(queueCreateInfos.size()), - .pQueueCreateInfos = queueCreateInfos.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data(), - .pEnabledFeatures = &deviceFeatures, - }; - - // Only enable validation layers if requested - if (enableValidationLayers) { - createInfo.enabledLayerCount = static_cast(validationLayers.size()); - createInfo.ppEnabledLayerNames = validationLayers.data(); - } - - // Create the logical device - vk::UniqueDevice device = physicalDevice.createDeviceUnique(createInfo); - - // Get queue handles - vk::Queue graphicsQueue = device->getQueue(indices.graphics_family.value(), 0); - vk::Queue presentQueue = device->getQueue(indices.present_family.value(), 0); - - return std::make_tuple(std::move(device), graphicsQueue, presentQueue); } fn DeviceManager::getMaxUsableSampleCount(const vk::PhysicalDevice& physicalDevice @@ -162,9 +162,9 @@ fn DeviceManager::findQueueFamilies(const vk::PhysicalDevice& device, const vk:: } fn DeviceManager::isDeviceSuitable( - const vk::PhysicalDevice& device, - const vk::SurfaceKHR& surface, - const std::vector& requiredExtensions + const vk::PhysicalDevice& device, + const vk::SurfaceKHR& surface, + std::span requiredExtensions ) -> bool { // Check queue family support QueueFamilyIndices indices = findQueueFamilies(device, surface); @@ -190,20 +190,15 @@ fn DeviceManager::isDeviceSuitable( } fn DeviceManager::checkDeviceExtensionSupport( - const vk::PhysicalDevice& device, - const std::vector& requiredExtensions + const vk::PhysicalDevice& device, + std::span requiredExtensions ) -> bool { - // Get available extensions std::vector availableExtensions = device.enumerateDeviceExtensionProperties(); - // Convert available extensions to a set of strings for easier lookup - std::set availableExtensionNames; - for (const auto& extension : availableExtensions) { - availableExtensionNames.insert(extension.extensionName); - } - - // Check if all required extensions are available + // Use ranges to check if all required extensions are available return std::ranges::all_of(requiredExtensions, [&](const char* required) { - return availableExtensionNames.contains(required); + return std::ranges::any_of(availableExtensions, [&](const vk::ExtensionProperties& available) { + return strcmp(required, available.extensionName) == 0; + }); }); } diff --git a/src/init/device_manager.hpp b/src/init/device_manager.hpp index a736ce2..0fb9ea3 100644 --- a/src/init/device_manager.hpp +++ b/src/init/device_manager.hpp @@ -2,6 +2,8 @@ #define VULKAN_HPP_DISPATCH_LOADER_DYNAMIC 1 #define VULKAN_HPP_NO_CONSTRUCTORS +#include +#include #include #include "../structs/queue_family_indices.hpp" @@ -11,20 +13,38 @@ class DeviceManager { public: DeviceManager() = default; + /** + * @brief Result type for physical device selection + */ + struct PhysicalDeviceInfo { + vk::PhysicalDevice physical_device; + vk::SampleCountFlagBits msaa_samples; + f32 max_line_width; + bool wide_line_support; + }; + + /** + * @brief Result type for logical device creation + */ + struct LogicalDeviceInfo { + vk::UniqueDevice device; + vk::Queue graphics_queue; + vk::Queue present_queue; + }; + /** * @brief Picks a suitable physical device (GPU) for the application. * * @param instance Vulkan instance to use for device selection * @param surface Surface to check for compatibility * @param requiredExtensions List of required device extensions - * @return std::tuple Physical device, MSAA samples, - * max line width, wide line support + * @return std::expected Physical device info or error message */ static fn pickPhysicalDevice( - const vk::Instance& instance, - const vk::SurfaceKHR& surface, - const std::vector& requiredExtensions - ) -> std::tuple; + const vk::Instance& instance, + const vk::SurfaceKHR& surface, + std::span requiredExtensions + ) -> std::expected; /** * @brief Creates a logical device with the specified features. @@ -34,15 +54,15 @@ class DeviceManager { * @param requiredExtensions Device extensions to enable * @param enableValidationLayers Whether to enable validation layers * @param validationLayers Validation layers to enable if enabled - * @return std::tuple Logical device and queues + * @return std::expected Logical device info or error message */ static fn createLogicalDevice( - const vk::PhysicalDevice& physicalDevice, - const vk::SurfaceKHR& surface, - const std::vector& requiredExtensions, - bool enableValidationLayers, - const std::vector& validationLayers - ) -> std::tuple; + const vk::PhysicalDevice& physicalDevice, + const vk::SurfaceKHR& surface, + std::span requiredExtensions, + bool enableValidationLayers, + std::span validationLayers + ) -> std::expected; /** * @brief Gets the maximum supported MSAA sample count. @@ -71,20 +91,16 @@ class DeviceManager { * @return bool True if device is suitable */ static fn isDeviceSuitable( - const vk::PhysicalDevice& device, - const vk::SurfaceKHR& surface, - const std::vector& requiredExtensions + const vk::PhysicalDevice& device, + const vk::SurfaceKHR& surface, + std::span requiredExtensions ) -> bool; /** - * @brief Checks if a physical device supports required extensions. - * - * @param device Physical device to check - * @param requiredExtensions Required device extensions - * @return bool True if device supports all required extensions + * @brief Checks if the device supports the required extensions. */ static fn checkDeviceExtensionSupport( - const vk::PhysicalDevice& device, - const std::vector& requiredExtensions + const vk::PhysicalDevice& device, + std::span requiredExtensions ) -> bool; }; diff --git a/src/main.cpp b/src/main.cpp index 0f45f7e..f457587 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -161,10 +161,12 @@ class VulkanApp { Camera mCamera; ///< Camera object // Light settings - LightInfo mLightSettings { .position = glm::vec3(2.0F, 2.0F, 2.0F), - .color = glm::vec3(1.0F, 1.0F, 1.0F), - .ambient_strength = 0.1F, - .specular_strength = 0.5F }; + LightInfo mLightSettings { + .position = glm::vec3(2.0F, 2.0F, 2.0F), + .color = glm::vec3(1.0F, 1.0F, 1.0F), + .ambient_strength = 0.1F, + .specular_strength = 0.5F, + }; // Mouse input tracking bool mFirstMouse = true; ///< Flag for first mouse movement @@ -641,7 +643,21 @@ class VulkanApp { return; #endif - mDebugMessenger = DebugMessenger::create(mInstance.get()); + DebugMessenger::Config config { + .severity_flags = vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | + vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | + vk::DebugUtilsMessageSeverityFlagBitsEXT::eError, + .type_flags = vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | + vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation | + vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance, + .use_stderr_for_errors = true, + }; + + auto result = DebugMessenger::create(mInstance.get(), config); + if (!result) { + throw std::runtime_error(result.error()); + } + mDebugMessenger = std::move(*result); } /** @@ -658,10 +674,18 @@ class VulkanApp { * the application's requirements. */ fn pickPhysicalDevice() -> void { - std::tie(mPhysicalDevice, mMsaaSamples, mMaxLineWidth, mWideLineSupport) = - DeviceManager::pickPhysicalDevice( - mInstance.get(), mSurface.get(), std::vector(deviceExtensions.begin(), deviceExtensions.end()) - ); + auto result = + DeviceManager::pickPhysicalDevice(mInstance.get(), mSurface.get(), std::span(deviceExtensions)); + + if (!result) { + throw std::runtime_error(result.error()); + } + + const auto& info = *result; + mPhysicalDevice = info.physical_device; + mMsaaSamples = info.msaa_samples; + mMaxLineWidth = info.max_line_width; + mWideLineSupport = info.wide_line_support; } /** @@ -671,13 +695,22 @@ class VulkanApp { * features and retrieving handles to the graphics and presentation queues. */ fn createLogicalDevice() -> void { - std::tie(mDevice, mGraphicsQueue, mPresentQueue) = DeviceManager::createLogicalDevice( + auto result = DeviceManager::createLogicalDevice( mPhysicalDevice, mSurface.get(), - std::vector(deviceExtensions.begin(), deviceExtensions.end()), + std::span(deviceExtensions), enableValidationLayers, - std::vector(validationLayers.begin(), validationLayers.end()) + std::span(validationLayers) ); + + if (!result) { + throw std::runtime_error(result.error()); + } + + auto info = std::move(*result); + mDevice = std::move(info.device); + mGraphicsQueue = info.graphics_queue; + mPresentQueue = info.present_queue; } /**