diff --git a/meson.build b/meson.build index 0dfd5b0..1af556e 100644 --- a/meson.build +++ b/meson.build @@ -42,6 +42,7 @@ executable( 'src/camera/camera.cpp', 'src/init/vulkan_instance.cpp', 'src/init/debug_messenger.cpp', + 'src/init/device_manager.cpp', 'src/window/window_manager.cpp', ], include_directories: include_directories('include', is_system: true), diff --git a/src/init/device_manager.cpp b/src/init/device_manager.cpp new file mode 100644 index 0000000..09e0005 --- /dev/null +++ b/src/init/device_manager.cpp @@ -0,0 +1,209 @@ +#include +#include +#include + +#include "device_manager.hpp" + +#include "../structs/swap_chain_support_details.hpp" + +fn DeviceManager::pickPhysicalDevice( + const vk::Instance& instance, + const vk::SurfaceKHR& surface, + const std::vector& requiredExtensions +) -> std::tuple { + // Get all available physical devices + std::vector devices = instance.enumeratePhysicalDevices(); + + if (devices.empty()) + throw std::runtime_error("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; + + // For each device, + for (const vk::PhysicalDevice& device : devices) { +#ifndef NDEBUG + vk::PhysicalDeviceProperties properties = device.getProperties(); + fmt::println("\t{}", properties.deviceName.data()); +#endif + + // 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; + +#ifndef NDEBUG + fmt::println("Maximum supported line width: {}", maxLineWidth); + fmt::println("Wide lines supported: {}", wideLineSupport ? "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); +} + +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); + + // Create a set of unique queue families needed + 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; + + for (u32 queueFamily : uniqueQueueFamilies) { + vk::DeviceQueueCreateInfo queueCreateInfo { + .queueFamilyIndex = queueFamily, + .queueCount = 1, + .pQueuePriorities = &queuePriority, + }; + queueCreateInfos.push_back(queueCreateInfo); + } + + // 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 +) -> vk::SampleCountFlagBits { + vk::PhysicalDeviceProperties physicalDeviceProperties = physicalDevice.getProperties(); + + vk::SampleCountFlags counts = physicalDeviceProperties.limits.framebufferColorSampleCounts & + physicalDeviceProperties.limits.framebufferDepthSampleCounts; + + if (counts & vk::SampleCountFlagBits::e64) + return vk::SampleCountFlagBits::e64; + if (counts & vk::SampleCountFlagBits::e32) + return vk::SampleCountFlagBits::e32; + if (counts & vk::SampleCountFlagBits::e16) + return vk::SampleCountFlagBits::e16; + if (counts & vk::SampleCountFlagBits::e8) + return vk::SampleCountFlagBits::e8; + if (counts & vk::SampleCountFlagBits::e4) + return vk::SampleCountFlagBits::e4; + if (counts & vk::SampleCountFlagBits::e2) + return vk::SampleCountFlagBits::e2; + + return vk::SampleCountFlagBits::e1; +} + +fn DeviceManager::findQueueFamilies(const vk::PhysicalDevice& device, const vk::SurfaceKHR& surface) + -> QueueFamilyIndices { + QueueFamilyIndices indices; + + // Get queue family properties + std::vector queueFamilies = device.getQueueFamilyProperties(); + + // Find queue family with graphics support + for (size_t i = 0; i < queueFamilies.size(); i++) { + if (queueFamilies[i].queueFlags & vk::QueueFlagBits::eGraphics) + indices.graphics_family = static_cast(i); + + // Check for presentation support + if (device.getSurfaceSupportKHR(static_cast(i), surface)) + indices.present_family = static_cast(i); + + if (indices.isComplete()) + break; + } + + return indices; +} + +fn DeviceManager::isDeviceSuitable( + const vk::PhysicalDevice& device, + const vk::SurfaceKHR& surface, + const std::vector& requiredExtensions +) -> bool { + // Check queue family support + QueueFamilyIndices indices = findQueueFamilies(device, surface); + + // Check extension support + bool extensionsSupported = checkDeviceExtensionSupport(device, requiredExtensions); + + // Check swap chain support + bool swapChainAdequate = false; + if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport; + swapChainSupport.capabilities = device.getSurfaceCapabilitiesKHR(surface); + swapChainSupport.formats = device.getSurfaceFormatsKHR(surface); + swapChainSupport.present_modes = device.getSurfacePresentModesKHR(surface); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.present_modes.empty(); + } + + // Check device features + vk::PhysicalDeviceFeatures supportedFeatures = device.getFeatures(); + + return indices.isComplete() && extensionsSupported && swapChainAdequate && + supportedFeatures.samplerAnisotropy; +} + +fn DeviceManager::checkDeviceExtensionSupport( + const vk::PhysicalDevice& device, + const std::vector& 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 + return std::ranges::all_of(requiredExtensions, [&](const char* required) { + return availableExtensionNames.contains(required); + }); +} diff --git a/src/init/device_manager.hpp b/src/init/device_manager.hpp new file mode 100644 index 0000000..4ed28b7 --- /dev/null +++ b/src/init/device_manager.hpp @@ -0,0 +1,89 @@ +#pragma once + +#define VULKAN_HPP_NO_CONSTRUCTORS +#include + +#include "../structs/queue_family_indices.hpp" +#include "../util/types.hpp" + +class DeviceManager { + public: + DeviceManager() = default; + + /** + * @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 + */ + static fn pickPhysicalDevice( + const vk::Instance& instance, + const vk::SurfaceKHR& surface, + const std::vector& requiredExtensions + ) -> std::tuple; + + /** + * @brief Creates a logical device with the specified features. + * + * @param physicalDevice Physical device to create logical device from + * @param surface Surface to create device with + * @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 + */ + static fn createLogicalDevice( + const vk::PhysicalDevice& physicalDevice, + const vk::SurfaceKHR& surface, + const std::vector& requiredExtensions, + bool enableValidationLayers, + const std::vector& validationLayers + ) -> std::tuple; + + /** + * @brief Gets the maximum supported MSAA sample count. + * + * @param physicalDevice Physical device to check + * @return vk::SampleCountFlagBits Maximum supported sample count + */ + static fn getMaxUsableSampleCount(const vk::PhysicalDevice& physicalDevice) -> vk::SampleCountFlagBits; + + /** + * @brief Finds queue families that support required operations. + * + * @param device Physical device to check + * @param surface Surface to check presentation support + * @return QueueFamilyIndices Indices of suitable queue families + */ + static fn findQueueFamilies(const vk::PhysicalDevice& device, const vk::SurfaceKHR& surface) + -> QueueFamilyIndices; + + /** + * @brief Checks if a physical device is suitable for the application. + * + * @param device Physical device to check + * @param surface Surface to check compatibility with + * @param requiredExtensions Required device extensions + * @return bool True if device is suitable + */ + static fn isDeviceSuitable( + const vk::PhysicalDevice& device, + const vk::SurfaceKHR& surface, + const std::vector& 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 + */ + static fn checkDeviceExtensionSupport( + const vk::PhysicalDevice& device, + const std::vector& requiredExtensions + ) -> bool; +}; diff --git a/src/main.cpp b/src/main.cpp index f02a43f..62a5b66 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,6 +3,7 @@ #include // For string formatting #include // For shader compilation #include // For unordered_set container +#include // GLM (OpenGL Mathematics) configuration #define GLM_FORCE_DEPTH_ZERO_TO_ONE // Use Vulkan's depth range (0 to 1) instead of OpenGL's (-1 to 1) @@ -22,6 +23,7 @@ // Include custom utility headers #include "camera/camera.hpp" // Camera class #include "init/debug_messenger.hpp" +#include "init/device_manager.hpp" #include "init/vulkan_instance.hpp" #include "structs/camera_info.hpp" #include "structs/light_info.hpp" @@ -378,10 +380,11 @@ class VulkanApp { mImGuiDescriptorPool = mDevice->createDescriptorPoolUnique(poolInfo); ImGui_ImplVulkan_InitInfo initInfo = { - .Instance = mInstance.get(), - .PhysicalDevice = mPhysicalDevice, - .Device = mDevice.get(), - .QueueFamily = findQueueFamilies(mPhysicalDevice).graphics_family.value(), + .Instance = mInstance.get(), + .PhysicalDevice = mPhysicalDevice, + .Device = mDevice.get(), + .QueueFamily = + DeviceManager::findQueueFamilies(mPhysicalDevice, mSurface.get()).graphics_family.value(), .Queue = mGraphicsQueue, .DescriptorPool = mImGuiDescriptorPool.get(), .RenderPass = mRenderPass.get(), @@ -653,45 +656,10 @@ class VulkanApp { * the application's requirements. */ fn pickPhysicalDevice() -> void { - // Get all physical devices - std::vector devices = mInstance->enumeratePhysicalDevices(); - - // Make sure there are supported devices - if (devices.empty()) - throw std::runtime_error("Failed to find GPUs with Vulkan support!"); - -#ifndef NDEBUG - fmt::println("Available devices:"); -#endif - - // For each device, - for (const vk::PhysicalDevice& device : devices) { -#ifndef NDEBUG - vk::PhysicalDeviceProperties properties = device.getProperties(); - fmt::println("\t{}", properties.deviceName.data()); -#endif - - // Set the first suitable device as the physical device - if (isDeviceSuitable(device)) { - mPhysicalDevice = device; - mMsaaSamples = getMaxUsableSampleCount(); - - // Get the device properties for line width limits - vk::PhysicalDeviceProperties deviceProperties = device.getProperties(); - mMaxLineWidth = deviceProperties.limits.lineWidthRange[1]; - mWideLineSupport = deviceProperties.limits.lineWidthRange[1] > 1.0F; - -#ifndef NDEBUG - fmt::println("Maximum supported line width: {}", mMaxLineWidth); - fmt::println("Wide lines supported: {}", mWideLineSupport ? "yes" : "no"); -#endif - break; - } - } - - // If no suitable device was found, throw an error - if (!mPhysicalDevice) - throw std::runtime_error("Failed to find a suitable GPU!"); + std::tie(mPhysicalDevice, mMsaaSamples, mMaxLineWidth, mWideLineSupport) = + DeviceManager::pickPhysicalDevice( + mInstance.get(), mSurface.get(), std::vector(deviceExtensions.begin(), deviceExtensions.end()) + ); } /** @@ -701,50 +669,13 @@ class VulkanApp { * features and retrieving handles to the graphics and presentation queues. */ fn createLogicalDevice() -> void { - // Get the queue families - QueueFamilyIndices qfIndices = findQueueFamilies(mPhysicalDevice); - - std::vector queueCreateInfos; - - std::set uniqueQueueFamilies = { - qfIndices.graphics_family.value(), - qfIndices.present_family.value(), - }; - - // Set the queue priority - f32 queuePriority = 1.0F; - - // For each unique queue family, create a queue create info - queueCreateInfos.reserve(uniqueQueueFamilies.size()); - - for (u32 queueFamily : uniqueQueueFamilies) - queueCreateInfos.push_back({ - .queueFamilyIndex = queueFamily, - .queueCount = 1, - .pQueuePriorities = &queuePriority, - }); - - // Enable required features - vk::PhysicalDeviceFeatures deviceFeatures { - .fillModeNonSolid = vk::True, // Required for wireframe rendering - .wideLines = vk::True, // Required for line width > 1.0 - .samplerAnisotropy = vk::True, - }; - - // Create the logical device - vk::DeviceCreateInfo createInfo { - .queueCreateInfoCount = static_cast(queueCreateInfos.size()), - .pQueueCreateInfos = queueCreateInfos.data(), - .enabledExtensionCount = static_cast(deviceExtensions.size()), - .ppEnabledExtensionNames = deviceExtensions.data(), - .pEnabledFeatures = &deviceFeatures, - }; - - mDevice = mPhysicalDevice.createDeviceUnique(createInfo); - - // Get the graphics and present queues - mGraphicsQueue = mDevice->getQueue(qfIndices.graphics_family.value(), 0); - mPresentQueue = mDevice->getQueue(qfIndices.present_family.value(), 0); + std::tie(mDevice, mGraphicsQueue, mPresentQueue) = DeviceManager::createLogicalDevice( + mPhysicalDevice, + mSurface.get(), + std::vector(deviceExtensions.begin(), deviceExtensions.end()), + enableValidationLayers, + std::vector(validationLayers.begin(), validationLayers.end()) + ); } /** @@ -766,7 +697,7 @@ class VulkanApp { imageCount > swapChainSupport.capabilities.maxImageCount) imageCount = swapChainSupport.capabilities.maxImageCount; - QueueFamilyIndices qfIndices = findQueueFamilies(mPhysicalDevice); + QueueFamilyIndices qfIndices = DeviceManager::findQueueFamilies(mPhysicalDevice, mSurface.get()); std::array queueFamilyIndices = { qfIndices.graphics_family.value(), qfIndices.present_family.value(), @@ -1272,7 +1203,7 @@ class VulkanApp { * the buffers from which command buffer memory is allocated. */ fn createCommandPool() -> void { - QueueFamilyIndices queueFamilyIndices = findQueueFamilies(mPhysicalDevice); + QueueFamilyIndices queueFamilyIndices = DeviceManager::findQueueFamilies(mPhysicalDevice, mSurface.get()); vk::CommandPoolCreateInfo poolInfo { .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, .queueFamilyIndex = queueFamilyIndices.graphics_family.value() }; @@ -2690,100 +2621,6 @@ class VulkanApp { }; } - /** - * @brief Checks if a physical device is suitable for the application. - * - * @param device The physical device to check. - * @return True if the device is suitable, false otherwise. - * - * This function checks if a physical device meets all the requirements - * of the application, including queue families, extensions, and features. - */ - fn isDeviceSuitable(const vk::PhysicalDevice& device) -> bool { - // Get the queue families that support the required operations - QueueFamilyIndices qfIndices = findQueueFamilies(device); - - // Check if the device supports the required extensions - bool extensionsSupported = checkDeviceExtensionSupport(device); - - bool swapChainAdequate = false; - - if (extensionsSupported) { - SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); - // Check if the swap chain is adequate (make sure it has - // at least one supported format and presentation mode) - swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.present_modes.empty(); - } - - // Check if the device supports the required features - vk::PhysicalDeviceFeatures supportedFeatures = device.getFeatures(); - - // If the device supports everything required, return true - return qfIndices.isComplete() && extensionsSupported && swapChainAdequate && - supportedFeatures.samplerAnisotropy; - } - - /** - * @brief Checks if a device supports all required extensions. - * - * @param device The physical device to check. - * @return True if all required extensions are supported, false otherwise. - * - * This function verifies that a physical device supports all the - * extensions required by the application. - */ - static fn checkDeviceExtensionSupport(const vk::PhysicalDevice& device) -> bool { - // Get the available extensions - std::vector availableExtensions = device.enumerateDeviceExtensionProperties(); - - // Create a set of required extension names - std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); - - // Remove each required extension from the set of available extensions - for (const vk::ExtensionProperties& extension : availableExtensions) - requiredExtensions.erase(extension.extensionName); - - // If the set is empty, all required extensions are supported - return requiredExtensions.empty(); - } - - /** - * @brief Finds queue families that support required operations. - * - * @param device The physical device to check. - * @return A QueueFamilyIndices struct with the found queue family indices. - * - * This function finds queue families that support graphics operations - * and presentation to the window surface. - */ - fn findQueueFamilies(const vk::PhysicalDevice& device) -> QueueFamilyIndices { - // Create a struct to store the queue family indices - QueueFamilyIndices qfIndices; - - // Get the queue family properties - std::vector queueFamilies = device.getQueueFamilyProperties(); - - // For every queue family, - for (u32 i = 0; i < queueFamilies.size(); i++) { - // Check if the queue family supports the required operations - if (queueFamilies[i].queueFlags & vk::QueueFlagBits::eGraphics) - qfIndices.graphics_family = i; - - // Check if the queue family supports presentation - vk::Bool32 queuePresentSupport = device.getSurfaceSupportKHR(i, mSurface.get()); - - // If the queue family supports presentation, set the present family index - if (queuePresentSupport) - qfIndices.present_family = i; - - // If the queue family supports both operations, we're done - if (qfIndices.isComplete()) - break; - } - - return qfIndices; - } - /** * @brief Checks if all requested validation layers are available. * diff --git a/src/util/constants.hpp b/src/util/constants.hpp index e0064fd..d23c019 100644 --- a/src/util/constants.hpp +++ b/src/util/constants.hpp @@ -24,7 +24,10 @@ namespace constants { // Validation layers #ifndef NDEBUG - constexpr std::array validationLayers = { "VK_LAYER_KHRONOS_validation" }; + constexpr bool enableValidationLayers = true; + constexpr std::array validationLayers = { "VK_LAYER_KHRONOS_validation" }; +#else + constexpr bool enableValidationLayers = false; #endif // Device extensions