diff --git a/meson.build b/meson.build index 22fd378..41d41fd 100644 --- a/meson.build +++ b/meson.build @@ -37,7 +37,12 @@ deps += imgui_dep executable( 'graphics-test', - sources: files('src/camera/camera.cpp', 'src/main.cpp'), + sources: [ + 'src/main.cpp', + 'src/camera/camera.cpp', + 'src/init/vulkan_instance.cpp', + 'src/init/debug_messenger.cpp', + ], include_directories: include_directories('include', is_system: true), dependencies: deps, ) \ No newline at end of file diff --git a/src/camera/camera.hpp b/src/camera/camera.hpp index eee79f8..e55ae58 100644 --- a/src/camera/camera.hpp +++ b/src/camera/camera.hpp @@ -5,10 +5,6 @@ #include "../util/types.hpp" -struct CameraInfo { - alignas(16) glm::vec3 position; ///< Camera position -}; - /** * @brief Camera class for handling 3D camera movement and perspective */ diff --git a/src/init/debug_messenger.cpp b/src/init/debug_messenger.cpp new file mode 100644 index 0000000..ad7696a --- /dev/null +++ b/src/init/debug_messenger.cpp @@ -0,0 +1,32 @@ +#include + +#include "debug_messenger.hpp" + +fn DebugMessenger::create(const vk::Instance& instance) -> vk::UniqueDebugUtilsMessengerEXT { +#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, + }; + + return instance.createDebugUtilsMessengerEXTUnique(createInfo); +#endif +} + +VKAPI_ATTR VkBool32 VKAPI_CALL DebugMessenger::debugCallback( + VkDebugUtilsMessageSeverityFlagBitsEXT /*messageSeverity*/, + VkDebugUtilsMessageTypeFlagsEXT /*messageType*/, + const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, + void* /*pUserData*/ +) { + fmt::println("validation layer: {}", pCallbackData->pMessage); + return VK_FALSE; +} diff --git a/src/init/debug_messenger.hpp b/src/init/debug_messenger.hpp new file mode 100644 index 0000000..1d008e1 --- /dev/null +++ b/src/init/debug_messenger.hpp @@ -0,0 +1,27 @@ +#pragma once + +#define VULKAN_HPP_DISPATCH_LOADER_DYNAMIC 1 // Use dynamic dispatch for Vulkan functions +#define VULKAN_HPP_NO_CONSTRUCTORS +#include + +#include "../util/types.hpp" + +class DebugMessenger { + public: + DebugMessenger() = default; + DebugMessenger(const DebugMessenger&) = default; + DebugMessenger(DebugMessenger&&) = delete; + fn operator=(const DebugMessenger&)->DebugMessenger& = default; + fn operator=(DebugMessenger&&)->DebugMessenger& = delete; + ~DebugMessenger() = default; + + static fn create(const vk::Instance& instance) -> vk::UniqueDebugUtilsMessengerEXT; + + private: + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback( + VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, + VkDebugUtilsMessageTypeFlagsEXT messageType, + const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, + void* pUserData + ); +}; diff --git a/src/init/vulkan_instance.cpp b/src/init/vulkan_instance.cpp new file mode 100644 index 0000000..b2849e6 --- /dev/null +++ b/src/init/vulkan_instance.cpp @@ -0,0 +1,101 @@ +#include + +#include "vulkan_instance.hpp" + +#include "../util/constants.hpp" + +VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE + +using namespace constants; + +fn VulkanInstance::create() -> vk::UniqueInstance { +#ifndef NDEBUG + // Make sure validation layers are supported + if (!checkValidationLayerSupport()) + throw std::runtime_error("Validation layers requested, but not available!"); + +#endif + + // Application metadata + vk::ApplicationInfo appInfo { + .pApplicationName = "Vulkan App", + .applicationVersion = 1, + .pEngineName = "No Engine", + .engineVersion = 1, + .apiVersion = vk::ApiVersion13, + }; + + // Get required extensions + auto extensions = getRequiredExtensions(); + +#ifndef NDEBUG + fmt::println("Available extensions:"); + for (const char* extension : extensions) { fmt::println("\t{}", extension); } +#endif + + // Create the instance + vk::InstanceCreateInfo createInfo { +#ifdef __APPLE__ + .flags = vk::InstanceCreateFlagBits::eEnumeratePortabilityKHR, +#endif + .pApplicationInfo = &appInfo, +#ifdef NDEBUG + .enabledLayerCount = 0, + .ppEnabledLayerNames = nullptr, +#else + .enabledLayerCount = static_cast(validationLayers.size()), + .ppEnabledLayerNames = validationLayers.data(), +#endif + .enabledExtensionCount = static_cast(extensions.size()), + .ppEnabledExtensionNames = extensions.data() + }; + + auto instance = vk::createInstanceUnique(createInfo); + + // Initialize the dynamic dispatcher with the instance + VULKAN_HPP_DEFAULT_DISPATCHER.init(instance.get()); + + return instance; +} + +fn VulkanInstance::getRequiredExtensions() -> std::vector { + // Get the required extensions from GLFW + std::span extensionsSpan = vkfw::getRequiredInstanceExtensions(); + std::vector extensions(extensionsSpan.begin(), extensionsSpan.end()); + +#ifndef NDEBUG + // Add debug utils extension in debug mode + extensions.push_back(vk::EXTDebugUtilsExtensionName); +#endif + +#ifdef __APPLE__ + // Add required macOS extensions + extensions.push_back(vk::KHRPortabilityEnumerationExtensionName); + extensions.push_back("VK_KHR_get_physical_device_properties2"); +#endif + + return extensions; +} + +fn VulkanInstance::checkValidationLayerSupport() -> bool { + // Get available layers + auto availableLayers = vk::enumerateInstanceLayerProperties(); + + // Check if all requested validation layers are available + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; +} diff --git a/src/init/vulkan_instance.hpp b/src/init/vulkan_instance.hpp new file mode 100644 index 0000000..34fcf46 --- /dev/null +++ b/src/init/vulkan_instance.hpp @@ -0,0 +1,25 @@ +#pragma once + +#define VKFW_NO_STRUCT_CONSTRUCTORS // Use aggregate initialization for GLFW structs +#include + +#define VULKAN_HPP_NO_CONSTRUCTORS +#include + +#include "../util/types.hpp" + +class VulkanInstance { + public: + VulkanInstance() = default; + VulkanInstance(const VulkanInstance&) = default; + VulkanInstance(VulkanInstance&&) = delete; + fn operator=(const VulkanInstance&)->VulkanInstance& = default; + fn operator=(VulkanInstance&&)->VulkanInstance& = delete; + ~VulkanInstance() = default; + + static fn create() -> vk::UniqueInstance; + + private: + static fn getRequiredExtensions() -> std::vector; + static fn checkValidationLayerSupport() -> bool; +}; diff --git a/src/main.cpp b/src/main.cpp index e90aa96..2a50fc7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,11 +19,15 @@ #define VULKAN_HPP_NO_CONSTRUCTORS // Use aggregate initialization for Vulkan structs #include // Include Vulkan C++ bindings -// Necessary for dynamic dispatch to work -VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE - // Include custom utility headers -#include "camera/camera.hpp" // Camera class +#include "camera/camera.hpp" // Camera class +#include "init/debug_messenger.hpp" +#include "init/vulkan_instance.hpp" +#include "structs/camera_info.hpp" +#include "structs/light_info.hpp" +#include "structs/queue_family_indices.hpp" +#include "structs/swap_chain_support_details.hpp" +#include "structs/uniform_buffer_object.hpp" #include "util/constants.hpp" // Constants definitions #include "util/crosshair.hpp" // Crosshair definitions #include "util/shaders.hpp" // Compiled shader code @@ -156,23 +160,10 @@ class VulkanApp { Camera mCamera; ///< Camera object // Light settings - struct { - glm::vec3 position = glm::vec3(2.0F, 2.0F, 2.0F); - glm::vec3 color = glm::vec3(1.0F, 1.0F, 1.0F); - float ambient_strength = 0.1F; - float specular_strength = 0.5F; - } mLightSettings; - - std::vector - mImageAvailableSemaphores; ///< Signals that an image is available for rendering - std::vector mRenderFinishedSemaphores; ///< Signals that rendering has finished - std::vector mInFlightFences; ///< Ensures CPU-GPU synchronization - std::vector mImagesInFlight; ///< Tracks which fences are in use by which swap chain images - - bool mFramebufferResized = false; ///< Flag indicating if the framebuffer was resized - u32 mCurrentFrame = 0; ///< Index of the current frame being rendered - - glm::mat4 mView; ///< View matrix + 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 @@ -188,52 +179,16 @@ class VulkanApp { f32 mMaxLineWidth = 1.0F; ///< Maximum supported line width bool mWideLineSupport = false; ///< Whether wide lines are supported - /** - * @brief Struct to store queue family indices. - * - * This struct contains the indices of the graphics and presentation queue families. - */ - struct QueueFamilyIndices { - std::optional graphics_family; ///< Index of graphics queue family - std::optional present_family; ///< Index of presentation queue family + std::vector + mImageAvailableSemaphores; ///< Signals that an image is available for rendering + std::vector mRenderFinishedSemaphores; ///< Signals that rendering has finished + std::vector mInFlightFences; ///< Ensures CPU-GPU synchronization + std::vector mImagesInFlight; ///< Tracks which fences are in use by which swap chain images - /** - * @brief Check if all required queue families are found. - * - * @return True if both graphics and presentation families are found, false otherwise. - */ - fn isComplete() -> bool { return graphics_family.has_value() && present_family.has_value(); } - }; + bool mFramebufferResized = false; ///< Flag indicating if the framebuffer was resized + u32 mCurrentFrame = 0; ///< Index of the current frame being rendered - /** - * @brief Struct to hold swap chain support details. - * - * This struct contains information about the surface capabilities, - * supported formats, and presentation modes. - */ - struct SwapChainSupportDetails { - vk::SurfaceCapabilitiesKHR capabilities; ///< Surface capabilities - std::vector formats; ///< Supported surface formats - std::vector present_modes; ///< Supported presentation modes - }; - - /** - * @brief Struct representing a uniform buffer object. - * - * This struct holds the model, view, and projection matrices for use in shaders. - */ - struct UniformBufferObject { - alignas(16) glm::mat4 model; ///< Model transformation matrix - alignas(16) glm::mat4 view; ///< View transformation matrix - alignas(16) glm::mat4 proj; ///< Projection matrix - }; - - struct LightInfo { - alignas(16) glm::vec3 position; ///< Light position - alignas(16) glm::vec3 color; ///< Light color - alignas(4) float ambient_strength; ///< Ambient strength - alignas(4) float specular_strength; ///< Specular strength - }; + glm::mat4 mView; ///< View matrix static fn processInput(vkfw::Window& window, Camera& camera, const f32& deltaTime, const f32& cameraSpeed) -> void { @@ -699,63 +654,7 @@ class VulkanApp { throw std::runtime_error("Validation layers requested, but not available!"); #endif - // Application metadata - vk::ApplicationInfo appInfo { - .pApplicationName = "Vulkan App", - .applicationVersion = 1, - .pEngineName = "No Engine", - .engineVersion = 1, - .apiVersion = vk::ApiVersion13, - }; - - // Get the required extensions - std::span extensionsSpan = vkfw::getRequiredInstanceExtensions(); - - // Convert the span to a vector - std::vector extensions(extensionsSpan.begin(), extensionsSpan.end()); - -#ifndef NDEBUG - // Add the debug utils extension - extensions.emplace_back(vk::EXTDebugUtilsExtensionName); -#endif - -#ifdef __APPLE__ - // Add the portability extension - extensions.emplace_back(vk::KHRPortabilityEnumerationExtensionName); - // Technically deprecated but Vulkan complains if I don't include it for macOS, - // so instead of using the vk::KHRPortabilitySubsetExtensionName, I just use - // the direct string. - extensions.emplace_back("VK_KHR_get_physical_device_properties2"); -#endif - - // Create the instance - vk::InstanceCreateInfo createInfo { -#ifdef __APPLE__ - // Enable the portability extension - .flags = vk::InstanceCreateFlagBits::eEnumeratePortabilityKHR, -#endif - .pApplicationInfo = &appInfo, -#ifdef NDEBUG - .enabledLayerCount = 0, - .ppEnabledLayerNames = nullptr, -#else - .enabledLayerCount = static_cast(validationLayers.size()), - .ppEnabledLayerNames = validationLayers.data(), -#endif - .enabledExtensionCount = static_cast(extensions.size()), - .ppEnabledExtensionNames = extensions.data() - }; - -#ifndef NDEBUG - fmt::println("Available extensions:"); - for (const char* extension : extensions) fmt::println("\t{}", extension); -#endif - - // Create the instance - mInstance = vk::createInstanceUnique(createInfo); - - // Load the instance functions - VULKAN_HPP_DEFAULT_DISPATCHER.init(mInstance.get()); + mInstance = VulkanInstance::create(); } /** @@ -768,17 +667,7 @@ class VulkanApp { return; #endif - vk::DebugUtilsMessengerCreateInfoEXT messengerCreateInfo { - .messageSeverity = vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | - vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | - vk::DebugUtilsMessageSeverityFlagBitsEXT::eError, - .messageType = vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | - vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation | - vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance, - .pfnUserCallback = debugCallback - }; - - mDebugMessenger = mInstance->createDebugUtilsMessengerEXTUnique(messengerCreateInfo, nullptr); + mDebugMessenger = DebugMessenger::create(mInstance.get()); } /** @@ -1188,7 +1077,6 @@ class VulkanApp { vk::PipelineColorBlendStateCreateInfo colorBlending { .logicOpEnable = vk::False, - .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment, .blendConstants = std::array { 0.0F, 0.0F, 0.0F, 0.0F }, diff --git a/src/structs/camera_info.hpp b/src/structs/camera_info.hpp new file mode 100644 index 0000000..2ca4a32 --- /dev/null +++ b/src/structs/camera_info.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include + +/** + * @brief Struct containing camera information for shading. + */ +struct CameraInfo { + alignas(16) glm::vec3 position; ///< Camera position in world space +}; diff --git a/src/structs/light_info.hpp b/src/structs/light_info.hpp new file mode 100644 index 0000000..9c265d3 --- /dev/null +++ b/src/structs/light_info.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include + +/** + * @brief Struct containing light information for shading. + */ +struct LightInfo { + alignas(16) glm::vec3 position; ///< Light position + alignas(16) glm::vec3 color; ///< Light color + alignas(4) float ambient_strength; ///< Ambient strength + alignas(4) float specular_strength; ///< Specular strength +}; diff --git a/src/structs/queue_family_indices.hpp b/src/structs/queue_family_indices.hpp new file mode 100644 index 0000000..670c403 --- /dev/null +++ b/src/structs/queue_family_indices.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include +#include "../util/types.hpp" + +/** + * @brief Struct to store queue family indices. + * + * This struct contains the indices of the graphics and presentation queue families. + */ +struct QueueFamilyIndices { + std::optional graphics_family; ///< Index of graphics queue family + std::optional present_family; ///< Index of presentation queue family + + /** + * @brief Check if all required queue families are found. + * + * @return True if both graphics and presentation families are found, false otherwise. + */ + [[nodiscard]] fn isComplete() const -> bool { + return graphics_family.has_value() && present_family.has_value(); + } +}; diff --git a/src/structs/swap_chain_support_details.hpp b/src/structs/swap_chain_support_details.hpp new file mode 100644 index 0000000..7d032c2 --- /dev/null +++ b/src/structs/swap_chain_support_details.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +/** + * @brief Struct to hold swap chain support details. + * + * This struct contains information about the surface capabilities, + * supported formats, and presentation modes. + */ +struct SwapChainSupportDetails { + vk::SurfaceCapabilitiesKHR capabilities; ///< Surface capabilities + std::vector formats; ///< Supported surface formats + std::vector present_modes; ///< Supported presentation modes +}; diff --git a/src/structs/uniform_buffer_object.hpp b/src/structs/uniform_buffer_object.hpp new file mode 100644 index 0000000..a14fae8 --- /dev/null +++ b/src/structs/uniform_buffer_object.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include + +/** + * @brief Struct representing a uniform buffer object. + * + * This struct holds the model, view, and projection matrices for use in shaders. + */ +struct UniformBufferObject { + alignas(16) glm::mat4 model; ///< Model transformation matrix + alignas(16) glm::mat4 view; ///< View transformation matrix + alignas(16) glm::mat4 proj; ///< Projection matrix +};