This commit is contained in:
Mars 2024-11-18 19:34:19 -05:00
parent 32a3d21488
commit e72e9bd51f
6 changed files with 306 additions and 151 deletions

View file

@ -14,6 +14,7 @@ common_cpp_args = [
'-Wno-c++98-compat-pedantic',
'-Wno-pre-c++20-compat-pedantic',
'-Wno-padded',
'-Wno-switch-default',
'-mavx2',
]

View file

@ -1,32 +1,97 @@
#include <fmt/color.h>
#include <fmt/format.h>
#include "debug_messenger.hpp"
fn DebugMessenger::create(const vk::Instance& instance) -> vk::UniqueDebugUtilsMessengerEXT {
fn DebugMessenger::create(const vk::Instance& instance, Config config)
-> std::expected<vk::UniqueDebugUtilsMessengerEXT, std::string> {
#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<const Config*>(pUserData);
Message msg { .message = pCallbackData->pMessage,
.severity = static_cast<vk::DebugUtilsMessageSeverityFlagBitsEXT>(messageSeverity),
.type = static_cast<vk::DebugUtilsMessageTypeFlagsEXT>(messageType),
.function_name = pCallbackData->pMessageIdName
? std::optional<std::string_view>(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
);
}

View file

@ -2,12 +2,35 @@
#define VULKAN_HPP_DISPATCH_LOADER_DYNAMIC 1 // Use dynamic dispatch for Vulkan functions
#define VULKAN_HPP_NO_CONSTRUCTORS
#include <expected>
#include <string>
#include <string_view>
#include <vulkan/vulkan.hpp>
#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<std::string_view> 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<vk::UniqueDebugUtilsMessengerEXT, std::string>;
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;
};

View file

@ -1,118 +1,118 @@
#include <expected>
#include <fmt/format.h>
#include <set>
#include <tuple>
#include <span>
#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<const char*>& requiredExtensions
) -> std::tuple<vk::PhysicalDevice, vk::SampleCountFlagBits, f32, bool> {
const vk::Instance& instance,
const vk::SurfaceKHR& surface,
std::span<const char* const> requiredExtensions
) -> std::expected<PhysicalDeviceInfo, std::string> {
// Get all available physical devices
std::vector<vk::PhysicalDevice> 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<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);
const vk::PhysicalDevice& physicalDevice,
const vk::SurfaceKHR& surface,
std::span<const char* const> requiredExtensions,
bool enableValidationLayers,
std::span<const char* const> validationLayers
) -> std::expected<LogicalDeviceInfo, std::string> {
try {
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() };
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;
std::vector<vk::DeviceQueueCreateInfo> 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<u32>(queueCreateInfos.size()),
.pQueueCreateInfos = queueCreateInfos.data(),
.enabledExtensionCount = static_cast<u32>(requiredExtensions.size()),
.ppEnabledExtensionNames = requiredExtensions.data(),
.pEnabledFeatures = &deviceFeatures,
};
if (enableValidationLayers) {
createInfo.enabledLayerCount = static_cast<u32>(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<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
@ -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<const char*>& requiredExtensions
const vk::PhysicalDevice& device,
const vk::SurfaceKHR& surface,
std::span<const char* const> 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<const char*>& requiredExtensions
const vk::PhysicalDevice& device,
std::span<const char* const> 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
// 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;
});
});
}

View file

@ -2,6 +2,8 @@
#define VULKAN_HPP_DISPATCH_LOADER_DYNAMIC 1
#define VULKAN_HPP_NO_CONSTRUCTORS
#include <expected>
#include <span>
#include <vulkan/vulkan.hpp>
#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<vk::PhysicalDevice, vk::SampleCountFlagBits, f32, bool> Physical device, MSAA samples,
* max line width, wide line support
* @return std::expected<PhysicalDeviceInfo, std::string> Physical device info or error message
*/
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>;
const vk::Instance& instance,
const vk::SurfaceKHR& surface,
std::span<const char* const> requiredExtensions
) -> std::expected<PhysicalDeviceInfo, std::string>;
/**
* @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<vk::UniqueDevice, vk::Queue, vk::Queue> Logical device and queues
* @return std::expected<LogicalDeviceInfo, std::string> Logical device info or error message
*/
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>;
const vk::PhysicalDevice& physicalDevice,
const vk::SurfaceKHR& surface,
std::span<const char* const> requiredExtensions,
bool enableValidationLayers,
std::span<const char* const> validationLayers
) -> std::expected<LogicalDeviceInfo, std::string>;
/**
* @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<const char*>& requiredExtensions
const vk::PhysicalDevice& device,
const vk::SurfaceKHR& surface,
std::span<const char* const> 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<const char*>& requiredExtensions
const vk::PhysicalDevice& device,
std::span<const char* const> requiredExtensions
) -> bool;
};

View file

@ -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;
}
/**