i ❤️ segfaulting

This commit is contained in:
Mars 2024-11-18 15:03:50 -05:00
parent 0f4d0d72d9
commit bca2a4e999
5 changed files with 323 additions and 184 deletions

View file

@ -42,6 +42,7 @@ executable(
'src/camera/camera.cpp', 'src/camera/camera.cpp',
'src/init/vulkan_instance.cpp', 'src/init/vulkan_instance.cpp',
'src/init/debug_messenger.cpp', 'src/init/debug_messenger.cpp',
'src/init/device_manager.cpp',
'src/window/window_manager.cpp', 'src/window/window_manager.cpp',
], ],
include_directories: include_directories('include', is_system: true), include_directories: include_directories('include', is_system: true),

209
src/init/device_manager.cpp Normal file
View file

@ -0,0 +1,209 @@
#include <fmt/format.h>
#include <set>
#include <tuple>
#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<const char*>& requiredExtensions
) -> std::tuple<vk::PhysicalDevice, vk::SampleCountFlagBits, f32, bool> {
// Get all available physical devices
std::vector<vk::PhysicalDevice> 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<const char*>& requiredExtensions,
const bool enableValidationLayers,
const std::vector<const char*>& validationLayers
) -> std::tuple<vk::UniqueDevice, vk::Queue, vk::Queue> {
QueueFamilyIndices indices = findQueueFamilies(physicalDevice, surface);
// Create a set of unique queue families needed
std::set<u32> uniqueQueueFamilies = { indices.graphics_family.value(), indices.present_family.value() };
// Create queue create infos for each unique queue family
std::vector<vk::DeviceQueueCreateInfo> 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<u32>(queueCreateInfos.size()),
.pQueueCreateInfos = queueCreateInfos.data(),
.enabledExtensionCount = static_cast<u32>(requiredExtensions.size()),
.ppEnabledExtensionNames = requiredExtensions.data(),
.pEnabledFeatures = &deviceFeatures,
};
// Only enable validation layers if requested
if (enableValidationLayers) {
createInfo.enabledLayerCount = static_cast<u32>(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<vk::QueueFamilyProperties> 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<u32>(i);
// Check for presentation support
if (device.getSurfaceSupportKHR(static_cast<u32>(i), surface))
indices.present_family = static_cast<u32>(i);
if (indices.isComplete())
break;
}
return indices;
}
fn DeviceManager::isDeviceSuitable(
const vk::PhysicalDevice& device,
const vk::SurfaceKHR& surface,
const std::vector<const char*>& 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<const char*>& requiredExtensions
) -> bool {
// Get available extensions
std::vector<vk::ExtensionProperties> availableExtensions = device.enumerateDeviceExtensionProperties();
// Convert available extensions to a set of strings for easier lookup
std::set<std::string> 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);
});
}

View file

@ -0,0 +1,89 @@
#pragma once
#define VULKAN_HPP_NO_CONSTRUCTORS
#include <vulkan/vulkan.hpp>
#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<vk::PhysicalDevice, vk::SampleCountFlagBits, f32, bool> Physical device, MSAA samples,
* max line width, wide line support
*/
static fn pickPhysicalDevice(
const vk::Instance& instance,
const vk::SurfaceKHR& surface,
const std::vector<const char*>& requiredExtensions
) -> std::tuple<vk::PhysicalDevice, vk::SampleCountFlagBits, f32, bool>;
/**
* @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<vk::UniqueDevice, vk::Queue, vk::Queue> Logical device and queues
*/
static fn createLogicalDevice(
const vk::PhysicalDevice& physicalDevice,
const vk::SurfaceKHR& surface,
const std::vector<const char*>& requiredExtensions,
bool enableValidationLayers,
const std::vector<const char*>& validationLayers
) -> std::tuple<vk::UniqueDevice, vk::Queue, vk::Queue>;
/**
* @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<const char*>& 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<const char*>& requiredExtensions
) -> bool;
};

View file

@ -3,6 +3,7 @@
#include <fmt/format.h> // For string formatting #include <fmt/format.h> // For string formatting
#include <shaderc/shaderc.hpp> // For shader compilation #include <shaderc/shaderc.hpp> // For shader compilation
#include <unordered_set> // For unordered_set container #include <unordered_set> // For unordered_set container
#include <vector>
// GLM (OpenGL Mathematics) configuration // 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) #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 custom utility headers
#include "camera/camera.hpp" // Camera class #include "camera/camera.hpp" // Camera class
#include "init/debug_messenger.hpp" #include "init/debug_messenger.hpp"
#include "init/device_manager.hpp"
#include "init/vulkan_instance.hpp" #include "init/vulkan_instance.hpp"
#include "structs/camera_info.hpp" #include "structs/camera_info.hpp"
#include "structs/light_info.hpp" #include "structs/light_info.hpp"
@ -378,10 +380,11 @@ class VulkanApp {
mImGuiDescriptorPool = mDevice->createDescriptorPoolUnique(poolInfo); mImGuiDescriptorPool = mDevice->createDescriptorPoolUnique(poolInfo);
ImGui_ImplVulkan_InitInfo initInfo = { ImGui_ImplVulkan_InitInfo initInfo = {
.Instance = mInstance.get(), .Instance = mInstance.get(),
.PhysicalDevice = mPhysicalDevice, .PhysicalDevice = mPhysicalDevice,
.Device = mDevice.get(), .Device = mDevice.get(),
.QueueFamily = findQueueFamilies(mPhysicalDevice).graphics_family.value(), .QueueFamily =
DeviceManager::findQueueFamilies(mPhysicalDevice, mSurface.get()).graphics_family.value(),
.Queue = mGraphicsQueue, .Queue = mGraphicsQueue,
.DescriptorPool = mImGuiDescriptorPool.get(), .DescriptorPool = mImGuiDescriptorPool.get(),
.RenderPass = mRenderPass.get(), .RenderPass = mRenderPass.get(),
@ -653,45 +656,10 @@ class VulkanApp {
* the application's requirements. * the application's requirements.
*/ */
fn pickPhysicalDevice() -> void { fn pickPhysicalDevice() -> void {
// Get all physical devices std::tie(mPhysicalDevice, mMsaaSamples, mMaxLineWidth, mWideLineSupport) =
std::vector<vk::PhysicalDevice> devices = mInstance->enumeratePhysicalDevices(); DeviceManager::pickPhysicalDevice(
mInstance.get(), mSurface.get(), std::vector(deviceExtensions.begin(), deviceExtensions.end())
// 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!");
} }
/** /**
@ -701,50 +669,13 @@ class VulkanApp {
* features and retrieving handles to the graphics and presentation queues. * features and retrieving handles to the graphics and presentation queues.
*/ */
fn createLogicalDevice() -> void { fn createLogicalDevice() -> void {
// Get the queue families std::tie(mDevice, mGraphicsQueue, mPresentQueue) = DeviceManager::createLogicalDevice(
QueueFamilyIndices qfIndices = findQueueFamilies(mPhysicalDevice); mPhysicalDevice,
mSurface.get(),
std::vector<vk::DeviceQueueCreateInfo> queueCreateInfos; std::vector(deviceExtensions.begin(), deviceExtensions.end()),
enableValidationLayers,
std::set<u32> uniqueQueueFamilies = { std::vector(validationLayers.begin(), validationLayers.end())
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<u32>(queueCreateInfos.size()),
.pQueueCreateInfos = queueCreateInfos.data(),
.enabledExtensionCount = static_cast<u32>(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);
} }
/** /**
@ -766,7 +697,7 @@ class VulkanApp {
imageCount > swapChainSupport.capabilities.maxImageCount) imageCount > swapChainSupport.capabilities.maxImageCount)
imageCount = swapChainSupport.capabilities.maxImageCount; imageCount = swapChainSupport.capabilities.maxImageCount;
QueueFamilyIndices qfIndices = findQueueFamilies(mPhysicalDevice); QueueFamilyIndices qfIndices = DeviceManager::findQueueFamilies(mPhysicalDevice, mSurface.get());
std::array<u32, 2> queueFamilyIndices = { std::array<u32, 2> queueFamilyIndices = {
qfIndices.graphics_family.value(), qfIndices.graphics_family.value(),
qfIndices.present_family.value(), qfIndices.present_family.value(),
@ -1272,7 +1203,7 @@ class VulkanApp {
* the buffers from which command buffer memory is allocated. * the buffers from which command buffer memory is allocated.
*/ */
fn createCommandPool() -> void { fn createCommandPool() -> void {
QueueFamilyIndices queueFamilyIndices = findQueueFamilies(mPhysicalDevice); QueueFamilyIndices queueFamilyIndices = DeviceManager::findQueueFamilies(mPhysicalDevice, mSurface.get());
vk::CommandPoolCreateInfo poolInfo { .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, vk::CommandPoolCreateInfo poolInfo { .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer,
.queueFamilyIndex = queueFamilyIndices.graphics_family.value() }; .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<vk::ExtensionProperties> availableExtensions = device.enumerateDeviceExtensionProperties();
// Create a set of required extension names
std::set<string> 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<vk::QueueFamilyProperties> 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. * @brief Checks if all requested validation layers are available.
* *

View file

@ -24,7 +24,10 @@ namespace constants {
// Validation layers // Validation layers
#ifndef NDEBUG #ifndef NDEBUG
constexpr std::array<const char*, 1> validationLayers = { "VK_LAYER_KHRONOS_validation" }; constexpr bool enableValidationLayers = true;
constexpr std::array<const char*, 1> validationLayers = { "VK_LAYER_KHRONOS_validation" };
#else
constexpr bool enableValidationLayers = false;
#endif #endif
// Device extensions // Device extensions