diff --git a/flake.nix b/flake.nix index 1b33f41..e6e9d1f 100644 --- a/flake.nix +++ b/flake.nix @@ -21,7 +21,7 @@ stdenv = if pkgs.hostPlatform.isLinux then pkgs.stdenvAdapters.useMoldLinker pkgs.llvmPackages_18.libcxxStdenv - else pkgs.llvmPackages_18.stdenv; + else pkgs.llvmPackages_18.libcxxStdenv; sources = import ./_sources/generated.nix { inherit (pkgs) fetchFromGitHub fetchgit fetchurl dockerTools; diff --git a/meson.build b/meson.build index b1dbb4c..73c5423 100644 --- a/meson.build +++ b/meson.build @@ -2,11 +2,7 @@ project( 'graphics-test', 'cpp', version: '0.1.0', - default_options: [ - 'cpp_std=c++20', - 'warning_level=everything', - 'buildtype=debugoptimized' - ], + default_options: ['cpp_std=c++20', 'warning_level=everything', 'buildtype=debugoptimized'], ) cpp = meson.get_compiler('cpp') @@ -42,10 +38,9 @@ deps = [ dependency('fmt', static: true), dependency('glfw3'), dependency('glm'), - dependency('vulkan') + dependency('vulkan'), ] - executable( 'graphics-test', sources, diff --git a/src/main.cpp b/src/main.cpp index 60d387d..17e2850 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,20 +1,20 @@ -#include -#include -#include -#include -#include - #define GLFW_INCLUDE_NONE #define GLFW_INCLUDE_VULKAN #include +#include +#include +#include +#include +#include +#include +#include -#include "util/magic_enum.hpp" -#include "util/types.h" +#include "src/util/types.h" -constexpr u32 WIDTH = 800; -constexpr u32 HEIGHT = 600; +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; -constexpr std::array validationLayers = { "VK_LAYER_KHRONOS_validation" }; +const std::array validationLayers = { "VK_LAYER_KHRONOS_validation" }; #ifdef NDEBUG const bool enableValidationLayers = false; @@ -32,10 +32,10 @@ fn CreateDebugUtilsMessengerEXT( vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT") ); - if (func == nullptr) - return VK_ERROR_EXTENSION_NOT_PRESENT; + if (func != nullptr) + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); - return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + return VK_ERROR_EXTENSION_NOT_PRESENT; } fn DestroyDebugUtilsMessengerEXT( @@ -51,6 +51,13 @@ fn DestroyDebugUtilsMessengerEXT( func(instance, debugMessenger, pAllocator); } +struct QueueFamilyIndices { + std::optional graphics_family; + std::optional present_family; + + fn isComplete() -> bool { return graphics_family.has_value() && present_family.has_value(); } +}; + class Application { public: fn run() -> void { @@ -61,12 +68,244 @@ class Application { } private: - VkDebugUtilsMessengerEXT mDebugMessenger; + GLFWwindow* mWindow; + VkInstance mInstance; - GLFWwindow* mWindow; + VkDebugUtilsMessengerEXT mDebugMessenger; + VkSurfaceKHR mSurface; + + VkPhysicalDevice mPhysicalDevice = VK_NULL_HANDLE; + VkDevice mDevice; + + VkQueue mGraphicsQueue; + VkQueue mPresentQueue; + + fn initWindow() -> void { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + mWindow = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + } + + fn initVulkan() -> void { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + } + + fn mainLoop() -> void { + while (!glfwWindowShouldClose(mWindow)) { glfwPollEvents(); } + } + + fn cleanup() -> void { + vkDestroyDevice(mDevice, nullptr); + + if (enableValidationLayers) + DestroyDebugUtilsMessengerEXT(mInstance, mDebugMessenger, nullptr); + + vkDestroySurfaceKHR(mInstance, mSurface, nullptr); + vkDestroyInstance(mInstance, nullptr); + + glfwDestroyWindow(mWindow); + + glfwTerminate(); + } + + fn createInstance() -> void { + if (enableValidationLayers && !checkValidationLayerSupport()) + throw std::runtime_error("validation layers requested, but not available!"); + + VkApplicationInfo appInfo {}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_0; + + VkInstanceCreateInfo createInfo {}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + std::vector extensions = getRequiredExtensions(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo {}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = static_cast(&debugCreateInfo); + } else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + extensions.emplace_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); + + createInfo.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; + + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + for (const char* extension : extensions) std::cout << extension << std::endl; + + if (vkCreateInstance(&createInfo, nullptr, &mInstance) != VK_SUCCESS) + throw std::runtime_error("failed to create instance!"); + } + + static fn populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) -> void { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + fn setupDebugMessenger() -> void { + if (!enableValidationLayers) + return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(mInstance, &createInfo, nullptr, &mDebugMessenger) != VK_SUCCESS) + throw std::runtime_error("failed to set up debug messenger!"); + } + + fn createSurface() -> void { + if (glfwCreateWindowSurface(mInstance, mWindow, nullptr, &mSurface) != VK_SUCCESS) + throw std::runtime_error("failed to create window surface!"); + } + + fn pickPhysicalDevice() -> void { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(mInstance, &deviceCount, nullptr); + + if (deviceCount == 0) + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(mInstance, &deviceCount, devices.data()); + + for (const auto& device : devices) + if (isDeviceSuitable(device)) { + mPhysicalDevice = device; + break; + } + + if (mPhysicalDevice == VK_NULL_HANDLE) + throw std::runtime_error("failed to find a suitable GPU!"); + } + + fn createLogicalDevice() -> void { + QueueFamilyIndices indices = findQueueFamilies(mPhysicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = { indices.graphics_family.value(), + indices.present_family.value() }; + + float queuePriority = 1.0F; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo {}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceFeatures deviceFeatures {}; + + VkDeviceCreateInfo createInfo {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pEnabledFeatures = &deviceFeatures; + + createInfo.enabledExtensionCount = 0; + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(mPhysicalDevice, &createInfo, nullptr, &mDevice) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + vkGetDeviceQueue(mDevice, indices.graphics_family.value(), 0, &mGraphicsQueue); + vkGetDeviceQueue(mDevice, indices.present_family.value(), 0, &mPresentQueue); + } + + fn isDeviceSuitable(VkPhysicalDevice device) -> bool { + QueueFamilyIndices indices = findQueueFamilies(device); + + return indices.isComplete(); + } + + fn findQueueFamilies(VkPhysicalDevice device) -> QueueFamilyIndices { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + u32 idx = 0; + + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) + indices.graphics_family = idx; + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, idx, mSurface, &presentSupport); + + if (presentSupport) + indices.present_family = idx; + + if (indices.isComplete()) + break; + + idx++; + } + + return indices; + } + + static fn getRequiredExtensions() -> std::vector { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions = nullptr; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions; + if (glfwExtensions) { + std::span extSpan(glfwExtensions, glfwExtensionCount); + extensions.assign(extSpan.begin(), extSpan.end()); + } + + if (enableValidationLayers) + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + + return extensions; + } static fn checkValidationLayerSupport() -> bool { - u32 layerCount = 0; + uint32_t layerCount = 0; vkEnumerateInstanceLayerProperties(&layerCount, nullptr); std::vector availableLayers(layerCount); @@ -91,158 +330,19 @@ class Application { return true; } - static fn getAvailableExtensions() -> std::vector { - u32 extensionCount = 0; - vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr); - - std::vector extensions(extensionCount); - vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data()); - - return extensions; - } - - static fn getRequiredExtensions() -> std::vector { - u32 glfwExtensionCount = 0; - const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions; - - if (glfwExtensions) { - std::span extSpan(glfwExtensions, glfwExtensionCount); - extensions.assign(extSpan.begin(), extSpan.end()); - } - - if (enableValidationLayers) - extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); - - return extensions; - } - static VKAPI_ATTR fn VKAPI_CALL debugCallback( - // Severity - verbose, info, warning, error VkDebugUtilsMessageSeverityFlagBitsEXT /*messageSeverity*/, - - // Message Type - general, validation, performance VkDebugUtilsMessageTypeFlagsEXT /*messageType*/, - - // Callback Data - Contains details of the message - // * pMessage - The message as a null-terminated string - // * pObjects - Array of related objects - // * objectCount - Number of related objects const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, - - // User Data - Any extra data the user may want to pass to the function void* /*pUserData*/ - ) -> VkBool32 /* If true, abort the call */ { + ) -> VkBool32 { std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; return VK_FALSE; } - - fn createInstance() -> void { - if (enableValidationLayers && !checkValidationLayerSupport()) - throw std::runtime_error("Validation layers requested, but not available!"); - - // General Metadata - VkApplicationInfo appInfo {}; - appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; // Used for pNext - appInfo.pApplicationName = "Hello Triangle"; - appInfo.pEngineName = "No Engine"; - appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); - appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); - appInfo.apiVersion = VK_API_VERSION_1_0; - - // Information used by vkCreateInstance - VkInstanceCreateInfo createInfo {}; - createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; - createInfo.pApplicationInfo = &appInfo; - - VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo {}; - - if (enableValidationLayers) { - createInfo.enabledLayerCount = static_cast(validationLayers.size()); - createInfo.ppEnabledLayerNames = validationLayers.data(); - - populateDebugMessengerCreateInfo(debugCreateInfo); - createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*)&debugCreateInfo; - } else { - createInfo.enabledLayerCount = 0; - - createInfo.pNext = nullptr; - } - - // Fixes macOS crashes - std::vector extensions = getRequiredExtensions(); - extensions.emplace_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); - - createInfo.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; - createInfo.enabledExtensionCount = static_cast(extensions.size()); - createInfo.ppEnabledExtensionNames = extensions.data(); - - // Finally, create the instance (and throw an error if it fails) - if (auto result = vkCreateInstance(&createInfo, nullptr, &mInstance); result != VK_SUCCESS) - throw std::runtime_error( - string("Failed to create instance! Error: ") + string(magic_enum::enum_name(result)) - ); - } - - fn initWindow() -> void { - // Initialize GLFW - glfwInit(); - - // Don't create an OpenGL context - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - - // Disable Resizing - glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); - - mWindow = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - } - - fn initVulkan() -> void { - createInstance(); - setupDebugMessenger(); - } - - static fn populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) -> void { - createInfo = {}; - createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; - createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | - VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | - VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; - createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | - VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | - VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; - createInfo.pfnUserCallback = debugCallback; - } - - fn setupDebugMessenger() -> void { - if (!enableValidationLayers) - return; - - VkDebugUtilsMessengerCreateInfoEXT createInfo; - populateDebugMessengerCreateInfo(createInfo); - - if (CreateDebugUtilsMessengerEXT(mInstance, &createInfo, nullptr, &mDebugMessenger) != VK_SUCCESS) { - throw std::runtime_error("Failed to set up debug messenger!"); - } - } - - fn mainLoop() -> void { - while (!glfwWindowShouldClose(mWindow)) glfwPollEvents(); - } - - fn cleanup() -> void { - if (enableValidationLayers) - DestroyDebugUtilsMessengerEXT(mInstance, mDebugMessenger, nullptr); - - vkDestroyInstance(mInstance, nullptr); - glfwDestroyWindow(mWindow); - glfwTerminate(); - } }; -fn main() -> int { +auto main() -> int { Application app; try {