#if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) #include "vulkan/vulkan.hpp" #include #include #else import vulkan_hpp; #endif #define GLFW_INCLUDE_VULKAN #include #include #include #include #include #include constexpr uint32_t WIDTH = 800; constexpr uint32_t HEIGHT = 600; const std::vector validationLayers = { "VK_LAYER_KHRONOS_validation" }; #ifdef NDEBUG constexpr bool enableValidationLayers = false; #else constexpr bool enableValidationLayers = true; #endif class HelloTriangleApplication { public: void run() { initWindow(); initVulkan(); mainLoop(); cleanup(); } private: void initWindow() { glfwInit(); // Don't create an OpenGL context glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); } void initVulkan() { createInstance(); createSurface(); pickPhysicalDevice(); createLogicalDevice(); } void mainLoop() { while (!glfwWindowShouldClose(window)) { glfwPollEvents(); } } void cleanup() { glfwDestroyWindow(window); glfwTerminate(); } void createInstance() { constexpr vk::ApplicationInfo appInfo { .pApplicationName = "Hello Triangle", .applicationVersion = VK_MAKE_VERSION(1, 0, 0), .pEngineName = "No Engine", .engineVersion = VK_MAKE_VERSION(1, 0, 0), .apiVersion = vk::ApiVersion14, }; // Get the required layers std::vector requiredLayers; if (enableValidationLayers) { requiredLayers.assign(validationLayers.begin(), validationLayers.end()); } // Check if the required layers are supported by the Vulkan implementation. auto layerProperties = context.enumerateInstanceLayerProperties(); if (std::ranges::any_of(requiredLayers, [&layerProperties](auto const& requiredLayer) { return std::ranges::none_of(layerProperties, [requiredLayer](auto const& layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; }); })) { throw std::runtime_error("One or more required layers are not supported!"); } // Get the required instance extensions from GLFW. uint32_t glfwExtensionCount = 0; auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); // Check if the required GLFW extensions are supported by the Vulkan implementation. auto extensionProperties = context.enumerateInstanceExtensionProperties(); for (uint32_t i = 0; i < glfwExtensionCount; ++i) { if (std::ranges::none_of(extensionProperties, [glfwExtension = glfwExtensions[i]](auto const& extensionProperty) { return strcmp(extensionProperty.extensionName, glfwExtension) == 0; })) { throw std::runtime_error("Required GLFW extension not supported: " + std::string(glfwExtensions[i])); } } vk::InstanceCreateInfo createInfo { .pApplicationInfo = &appInfo, .enabledLayerCount = static_cast(requiredLayers.size()), .ppEnabledLayerNames = requiredLayers.data(), .enabledExtensionCount = glfwExtensionCount, .ppEnabledExtensionNames = glfwExtensions, }; instance = vk::raii::Instance(context, createInfo); } void createSurface() { VkSurfaceKHR _surface; if (glfwCreateWindowSurface(*instance, window, nullptr, &_surface) != 0) { throw std::runtime_error("failed to create window surface!"); } surface = vk::raii::SurfaceKHR(instance, _surface); } void pickPhysicalDevice() { std::vector deviceExtensions = { vk::KHRSwapchainExtensionName, vk::KHRSpirv14ExtensionName, vk::KHRSynchronization2ExtensionName, vk::KHRCreateRenderpass2ExtensionName }; auto devices = instance.enumeratePhysicalDevices(); if (devices.empty()) { throw std::runtime_error("failed to find GPUs with Vulkan support!"); } for (const auto &device : devices) { auto deviceProperties = device.getProperties(); auto deviceFeatures = device.getFeatures(); auto queueFamilies = device.getQueueFamilyProperties(); auto extensions = device.enumerateDeviceExtensionProperties(); bool isSuitable = deviceProperties.apiVersion >= VK_API_VERSION_1_3; bool extensionFound = true; const vk::QueueFamilyProperties *qf = nullptr; for (const auto &qfp : queueFamilies) { if ((qfp.queueFlags & vk::QueueFlagBits::eGraphics) != static_cast(0)) { qf = &qfp; break; } } isSuitable = isSuitable && (qf != nullptr); for (const auto &extension : deviceExtensions) { auto extensionIter = std::ranges::find_if(extensions, [extension](auto const & ext) {return strcmp(ext.extensionName, extension) == 0;}); extensionFound = extensionFound && extensionIter != extensions.end(); } isSuitable = isSuitable && extensionFound; if (isSuitable) { physicalDevice = device; return; } throw std::runtime_error("failed to find a suitable GPU"); } } void createLogicalDevice() { std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); uint32_t graphicsIndex = findQueueFamilies(physicalDevice); float queuePriority = 0.5f; vk::DeviceQueueCreateInfo deviceQueueCreateInfo { .queueFamilyIndex = graphicsIndex, .queueCount = 1, .pQueuePriorities = &queuePriority, }; // Create a chain of feature structures vk::StructureChain featureChain = { {}, // vk::PhysicalDeviceFeatures2 (empty for now) {.dynamicRendering = true }, // Enable dynamic rendering from Vulkan 1.3 {.extendedDynamicState = true } // Enable extended dynamic state from the extension }; std::vector deviceExtensions = { vk::KHRSwapchainExtensionName, vk::KHRSpirv14ExtensionName, vk::KHRSynchronization2ExtensionName, vk::KHRCreateRenderpass2ExtensionName }; vk::DeviceCreateInfo deviceCreateInfo { .pNext = &featureChain.get(), .queueCreateInfoCount = 1, .pQueueCreateInfos = &deviceQueueCreateInfo, .enabledExtensionCount = static_cast(deviceExtensions.size()), .ppEnabledExtensionNames = deviceExtensions.data(), }; device = vk::raii::Device(physicalDevice, deviceCreateInfo); graphicsQueue = vk::raii::Queue(device, graphicsIndex, 0); } uint32_t findQueueFamilies(vk::raii::PhysicalDevice physicalDevice) { // find the index of the first queue family that supports graphics std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); // get the first index into queueFamilyProperties which both supports graphics and present uint32_t queueIndex = ~0; for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); ++qfpIndex) { if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) { queueIndex = qfpIndex; break; } } if (queueIndex == ~0) { throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); } return queueIndex; } GLFWwindow *window; vk::raii::Context context; vk::raii::Instance instance = nullptr; vk::raii::PhysicalDevice physicalDevice = nullptr; vk::raii::Device device = nullptr; vk::raii::Queue graphicsQueue = nullptr; vk::raii::SurfaceKHR surface = nullptr; }; int main() { HelloTriangleApplication app; try { app.run(); } catch (const std::exception &e) { std::cout << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; }