From d57dd446d1f0502a3e2a7de50a23dccb4e546560 Mon Sep 17 00:00:00 2001 From: Abdelrahman Date: Sat, 6 Dec 2025 00:20:08 +0000 Subject: [PATCH] Initial commit for vulkan tutorial Following along the first 9 chapters of the Vulkan HelloTriangle tutorial. --- .clangd | 2 + 00_base_code.cpp | 57 ++ 01_instance_creation.cpp | 94 ++++ 02_validation_layers.cpp | 125 +++++ 03_physical_device_selection.cpp | 185 ++++++ 04_logical_device.cpp | 226 ++++++++ 05_window_surface.cpp | 244 ++++++++ 06_swap_chain_creation.cpp | 313 +++++++++++ 07_image_views.cpp | 330 +++++++++++ 08_graphics_pipeline.cpp | 332 +++++++++++ 09_shader_modules.cpp | 374 ++++++++++++ CMake/FindKTX.cmake | 106 ++++ CMake/FindVulkan.cmake | 937 +++++++++++++++++++++++++++++++ CMake/FindVulkanHpp.cmake | 426 ++++++++++++++ CMake/Findglm.cmake | 133 +++++ CMake/Findnlohmann_json.cmake | 154 +++++ CMake/Findstb.cmake | 86 +++ CMake/Findtinygltf.cmake | 162 ++++++ CMake/Findtinyobjloader.cmake | 160 ++++++ CMakeLists.txt | 324 +++++++++++ build | 6 + incremental_patch.sh | 31 + run | 5 + shaders/09_shader_base.slang | 29 + 24 files changed, 4841 insertions(+) create mode 100644 .clangd create mode 100644 00_base_code.cpp create mode 100644 01_instance_creation.cpp create mode 100644 02_validation_layers.cpp create mode 100644 03_physical_device_selection.cpp create mode 100644 04_logical_device.cpp create mode 100644 05_window_surface.cpp create mode 100644 06_swap_chain_creation.cpp create mode 100644 07_image_views.cpp create mode 100644 08_graphics_pipeline.cpp create mode 100644 09_shader_modules.cpp create mode 100644 CMake/FindKTX.cmake create mode 100644 CMake/FindVulkan.cmake create mode 100644 CMake/FindVulkanHpp.cmake create mode 100644 CMake/Findglm.cmake create mode 100644 CMake/Findnlohmann_json.cmake create mode 100644 CMake/Findstb.cmake create mode 100644 CMake/Findtinygltf.cmake create mode 100644 CMake/Findtinyobjloader.cmake create mode 100644 CMakeLists.txt create mode 100755 build create mode 100755 incremental_patch.sh create mode 100755 run create mode 100644 shaders/09_shader_base.slang diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..950b928 --- /dev/null +++ b/.clangd @@ -0,0 +1,2 @@ +CompileFlags: + Remove: [-fmodules-ts, -fmodule-mapper=*, -fdeps-format=*] diff --git a/00_base_code.cpp b/00_base_code.cpp new file mode 100644 index 0000000..599fe23 --- /dev/null +++ b/00_base_code.cpp @@ -0,0 +1,57 @@ +#if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) +#include +#else +import vulkan_hpp; +#endif + +#define GLFW_INCLUDE_VULKAN +#include +#include +#include +#include + +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; + +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() {} + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + } + } + void cleanup() { + glfwDestroyWindow(window); + glfwTerminate(); + } + + GLFWwindow *window; +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } catch (const std::exception &e) { + std::cout << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/01_instance_creation.cpp b/01_instance_creation.cpp new file mode 100644 index 0000000..d7f7e06 --- /dev/null +++ b/01_instance_creation.cpp @@ -0,0 +1,94 @@ +#if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) +#include +#include +#else +import vulkan_hpp; +#endif + +#define GLFW_INCLUDE_VULKAN +#include +#include +#include +#include + +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; + +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(); + } + 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 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, + .enabledExtensionCount = glfwExtensionCount, + .ppEnabledExtensionNames = glfwExtensions, + }; + + instance = vk::raii::Instance(context, createInfo); + } + + GLFWwindow *window; + vk::raii::Context context; + vk::raii::Instance instance = 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; +} diff --git a/02_validation_layers.cpp b/02_validation_layers.cpp new file mode 100644 index 0000000..dd538b1 --- /dev/null +++ b/02_validation_layers.cpp @@ -0,0 +1,125 @@ +#if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) +#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(); + } + 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); + } + + GLFWwindow *window; + vk::raii::Context context; + vk::raii::Instance instance = 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; +} diff --git a/03_physical_device_selection.cpp b/03_physical_device_selection.cpp new file mode 100644 index 0000000..0278eee --- /dev/null +++ b/03_physical_device_selection.cpp @@ -0,0 +1,185 @@ +#if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) +#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(); + pickPhysicalDevice(); + } + 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 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"); + } + } + 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 supports graphics + auto graphicsQueueFamilyProperty = + std::find_if( queueFamilyProperties.begin(), + queueFamilyProperties.end(), + []( vk::QueueFamilyProperties const & qfp ) { return qfp.queueFlags & vk::QueueFlagBits::eGraphics; } ); + + return static_cast( std::distance( queueFamilyProperties.begin(), graphicsQueueFamilyProperty ) ); + } + + GLFWwindow *window; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::PhysicalDevice physicalDevice = 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; +} diff --git a/04_logical_device.cpp b/04_logical_device.cpp new file mode 100644 index 0000000..81b0be7 --- /dev/null +++ b/04_logical_device.cpp @@ -0,0 +1,226 @@ +#include "vulkan/vulkan.hpp" +#if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) +#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(); + 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 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 supports graphics + auto graphicsQueueFamilyProperty = + std::find_if( queueFamilyProperties.begin(), + queueFamilyProperties.end(), + []( vk::QueueFamilyProperties const & qfp ) { return qfp.queueFlags & vk::QueueFlagBits::eGraphics; } ); + + return static_cast( std::distance( queueFamilyProperties.begin(), graphicsQueueFamilyProperty ) ); + } + + 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; +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } catch (const std::exception &e) { + std::cout << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/05_window_surface.cpp b/05_window_surface.cpp new file mode 100644 index 0000000..86729a1 --- /dev/null +++ b/05_window_surface.cpp @@ -0,0 +1,244 @@ +#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; +} diff --git a/06_swap_chain_creation.cpp b/06_swap_chain_creation.cpp new file mode 100644 index 0000000..21733fb --- /dev/null +++ b/06_swap_chain_creation.cpp @@ -0,0 +1,313 @@ +#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 +#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(); + createSwapChain(); + } + 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); + } + void createSwapChain() { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface); + swapChainSurfaceFormat = chooseSwapSurfaceFormat(physicalDevice.getSurfaceFormatsKHR(surface)); + swapChainExtent = chooseSwapExtent(surfaceCapabilities); + auto minImageCount = std::max(3u, surfaceCapabilities.minImageCount); + minImageCount = (surfaceCapabilities.maxImageCount > 0 && + minImageCount > surfaceCapabilities.maxImageCount) ? + surfaceCapabilities.maxImageCount : + minImageCount; + + vk::SwapchainCreateInfoKHR swapChainCreateInfo { + .flags = vk::SwapchainCreateFlagsKHR(), + .surface = surface, + .minImageCount = minImageCount, + .imageFormat = swapChainSurfaceFormat.format, + .imageColorSpace = swapChainSurfaceFormat.colorSpace, + .imageExtent = swapChainExtent, + .imageArrayLayers = 1, + .imageUsage = vk::ImageUsageFlagBits::eColorAttachment, + .imageSharingMode = vk::SharingMode::eExclusive, + .preTransform = surfaceCapabilities.currentTransform, + .compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque, + .presentMode = chooseSwapPresentMode(physicalDevice.getSurfacePresentModesKHR(surface)), + .clipped = true, + .oldSwapchain = nullptr, + }; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == vk::Format::eB8G8R8A8Srgb && availableFormat.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear) { + return availableFormat; + } + } + + return availableFormats[0]; + } + vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == vk::PresentModeKHR::eMailbox) { + return availablePresentMode; + } + } + + return vk::PresentModeKHR::eFifo; + } + vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } + + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + return { + std::clamp(width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width), + std::clamp(height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height), + }; + } + 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; + vk::raii::SwapchainKHR swapChain = nullptr; + vk::SurfaceFormatKHR swapChainSurfaceFormat; + vk::Extent2D swapChainExtent; + std::vector swapChainImages; + std::vector swapChainImageViews; +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } catch (const std::exception &e) { + std::cout << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/07_image_views.cpp b/07_image_views.cpp new file mode 100644 index 0000000..b929bfe --- /dev/null +++ b/07_image_views.cpp @@ -0,0 +1,330 @@ +#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 +#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(); + createSwapChain(); + createImageViews(); + } + 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); + } + void createSwapChain() { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface); + swapChainSurfaceFormat = chooseSwapSurfaceFormat(physicalDevice.getSurfaceFormatsKHR(surface)); + swapChainExtent = chooseSwapExtent(surfaceCapabilities); + auto minImageCount = std::max(3u, surfaceCapabilities.minImageCount); + minImageCount = (surfaceCapabilities.maxImageCount > 0 && + minImageCount > surfaceCapabilities.maxImageCount) ? + surfaceCapabilities.maxImageCount : + minImageCount; + + vk::SwapchainCreateInfoKHR swapChainCreateInfo { + .flags = vk::SwapchainCreateFlagsKHR(), + .surface = surface, + .minImageCount = minImageCount, + .imageFormat = swapChainSurfaceFormat.format, + .imageColorSpace = swapChainSurfaceFormat.colorSpace, + .imageExtent = swapChainExtent, + .imageArrayLayers = 1, + .imageUsage = vk::ImageUsageFlagBits::eColorAttachment, + .imageSharingMode = vk::SharingMode::eExclusive, + .preTransform = surfaceCapabilities.currentTransform, + .compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque, + .presentMode = chooseSwapPresentMode(physicalDevice.getSurfacePresentModesKHR(surface)), + .clipped = true, + .oldSwapchain = nullptr, + }; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + swapChainImageFormat = swapChainSurfaceFormat.format; + } + void createImageViews() { + swapChainImageViews.clear(); + + vk::ImageViewCreateInfo imageViewCreateInfo{ + .viewType = vk::ImageViewType::e2D, + .format = swapChainImageFormat, + .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } + }; + + for (auto image : swapChainImages) { + imageViewCreateInfo.image = image; + swapChainImageViews.emplace_back(vk::raii::ImageView(device, imageViewCreateInfo)); + } + } + vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == vk::Format::eB8G8R8A8Srgb && availableFormat.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear) { + return availableFormat; + } + } + + return availableFormats[0]; + } + vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == vk::PresentModeKHR::eMailbox) { + return availablePresentMode; + } + } + + return vk::PresentModeKHR::eFifo; + } + vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } + + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + return { + std::clamp(width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width), + std::clamp(height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height), + }; + } + 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; + vk::raii::SwapchainKHR swapChain = nullptr; + vk::SurfaceFormatKHR swapChainSurfaceFormat; + vk::Extent2D swapChainExtent; + vk::Format swapChainImageFormat = vk::Format::eUndefined; + std::vector swapChainImages; + std::vector swapChainImageViews; +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } catch (const std::exception &e) { + std::cout << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/08_graphics_pipeline.cpp b/08_graphics_pipeline.cpp new file mode 100644 index 0000000..20adcb3 --- /dev/null +++ b/08_graphics_pipeline.cpp @@ -0,0 +1,332 @@ +#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 +#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(); + createSwapChain(); + createImageViews(); + createGraphicsPipeline(); + } + 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); + } + void createSwapChain() { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface); + swapChainSurfaceFormat = chooseSwapSurfaceFormat(physicalDevice.getSurfaceFormatsKHR(surface)); + swapChainExtent = chooseSwapExtent(surfaceCapabilities); + auto minImageCount = std::max(3u, surfaceCapabilities.minImageCount); + minImageCount = (surfaceCapabilities.maxImageCount > 0 && + minImageCount > surfaceCapabilities.maxImageCount) ? + surfaceCapabilities.maxImageCount : + minImageCount; + + vk::SwapchainCreateInfoKHR swapChainCreateInfo { + .flags = vk::SwapchainCreateFlagsKHR(), + .surface = surface, + .minImageCount = minImageCount, + .imageFormat = swapChainSurfaceFormat.format, + .imageColorSpace = swapChainSurfaceFormat.colorSpace, + .imageExtent = swapChainExtent, + .imageArrayLayers = 1, + .imageUsage = vk::ImageUsageFlagBits::eColorAttachment, + .imageSharingMode = vk::SharingMode::eExclusive, + .preTransform = surfaceCapabilities.currentTransform, + .compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque, + .presentMode = chooseSwapPresentMode(physicalDevice.getSurfacePresentModesKHR(surface)), + .clipped = true, + .oldSwapchain = nullptr, + }; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + swapChainImageFormat = swapChainSurfaceFormat.format; + } + void createImageViews() { + swapChainImageViews.clear(); + + vk::ImageViewCreateInfo imageViewCreateInfo{ + .viewType = vk::ImageViewType::e2D, + .format = swapChainImageFormat, + .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } + }; + + for (auto image : swapChainImages) { + imageViewCreateInfo.image = image; + swapChainImageViews.emplace_back(vk::raii::ImageView(device, imageViewCreateInfo)); + } + } + void createGraphicsPipeline() {} + vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == vk::Format::eB8G8R8A8Srgb && availableFormat.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear) { + return availableFormat; + } + } + + return availableFormats[0]; + } + vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == vk::PresentModeKHR::eMailbox) { + return availablePresentMode; + } + } + + return vk::PresentModeKHR::eFifo; + } + vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } + + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + return { + std::clamp(width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width), + std::clamp(height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height), + }; + } + 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; + vk::raii::SwapchainKHR swapChain = nullptr; + vk::SurfaceFormatKHR swapChainSurfaceFormat; + vk::Extent2D swapChainExtent; + vk::Format swapChainImageFormat = vk::Format::eUndefined; + std::vector swapChainImages; + std::vector swapChainImageViews; +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } catch (const std::exception &e) { + std::cout << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/09_shader_modules.cpp b/09_shader_modules.cpp new file mode 100644 index 0000000..4a4da43 --- /dev/null +++ b/09_shader_modules.cpp @@ -0,0 +1,374 @@ +#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 +#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 + +static std::vector readFile(const std::string &filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + if (!file.is_open()) { + throw std::runtime_error("failed to open file!"); + } + + std::vector buffer(file.tellg()); + file.seekg(0, std::ios::beg); + file.read(buffer.data(), static_cast(buffer.size())); + file.close(); + + return buffer; +} + +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(); + createSwapChain(); + createImageViews(); + createGraphicsPipeline(); + } + 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 + {.shaderDrawParameters = true }, // Enable shader draw parameters from Vulkan 1.2 + {.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); + } + void createSwapChain() { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface); + swapChainSurfaceFormat = chooseSwapSurfaceFormat(physicalDevice.getSurfaceFormatsKHR(surface)); + swapChainExtent = chooseSwapExtent(surfaceCapabilities); + auto minImageCount = std::max(3u, surfaceCapabilities.minImageCount); + minImageCount = (surfaceCapabilities.maxImageCount > 0 && + minImageCount > surfaceCapabilities.maxImageCount) ? + surfaceCapabilities.maxImageCount : + minImageCount; + + vk::SwapchainCreateInfoKHR swapChainCreateInfo { + .flags = vk::SwapchainCreateFlagsKHR(), + .surface = surface, + .minImageCount = minImageCount, + .imageFormat = swapChainSurfaceFormat.format, + .imageColorSpace = swapChainSurfaceFormat.colorSpace, + .imageExtent = swapChainExtent, + .imageArrayLayers = 1, + .imageUsage = vk::ImageUsageFlagBits::eColorAttachment, + .imageSharingMode = vk::SharingMode::eExclusive, + .preTransform = surfaceCapabilities.currentTransform, + .compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque, + .presentMode = chooseSwapPresentMode(physicalDevice.getSurfacePresentModesKHR(surface)), + .clipped = true, + .oldSwapchain = nullptr, + }; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + swapChainImageFormat = swapChainSurfaceFormat.format; + } + void createImageViews() { + swapChainImageViews.clear(); + + vk::ImageViewCreateInfo imageViewCreateInfo{ + .viewType = vk::ImageViewType::e2D, + .format = swapChainImageFormat, + .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } + }; + + for (auto image : swapChainImages) { + imageViewCreateInfo.image = image; + swapChainImageViews.emplace_back(vk::raii::ImageView(device, imageViewCreateInfo)); + } + } + void createGraphicsPipeline() { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/09_shader_base.spv")); + + vk::PipelineShaderStageCreateInfo vertShaderStageInfo = { + .stage = vk::ShaderStageFlagBits::eVertex, + .module = shaderModule, + .pName = "vertMain", + }; + vk::PipelineShaderStageCreateInfo fragShaderStageInfo = { + .stage = vk::ShaderStageFlagBits::eFragment, + .module = shaderModule, + .pName = "fragMain", + }; + + vk::PipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; + } + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const { + vk::ShaderModuleCreateInfo createInfo { + .codeSize = code.size() * sizeof(char), + .pCode = reinterpret_cast(code.data()), + }; + + return vk::raii::ShaderModule{device, createInfo}; + } + vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == vk::Format::eB8G8R8A8Srgb && availableFormat.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear) { + return availableFormat; + } + } + + return availableFormats[0]; + } + vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == vk::PresentModeKHR::eMailbox) { + return availablePresentMode; + } + } + + return vk::PresentModeKHR::eFifo; + } + vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } + + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + return { + std::clamp(width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width), + std::clamp(height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height), + }; + } + 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; + vk::raii::SwapchainKHR swapChain = nullptr; + vk::SurfaceFormatKHR swapChainSurfaceFormat; + vk::Extent2D swapChainExtent; + vk::Format swapChainImageFormat = vk::Format::eUndefined; + std::vector swapChainImages; + std::vector swapChainImageViews; +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } catch (const std::exception &e) { + std::cout << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/CMake/FindKTX.cmake b/CMake/FindKTX.cmake new file mode 100644 index 0000000..ac6971a --- /dev/null +++ b/CMake/FindKTX.cmake @@ -0,0 +1,106 @@ +# FindKTX.cmake +# +# Finds the KTX library +# +# This will define the following variables +# +# KTX_FOUND +# KTX_INCLUDE_DIRS +# KTX_LIBRARIES +# +# and the following imported targets +# +# KTX::ktx +# + +# Check if we're on Linux - if so, we'll skip the search and directly use FetchContent +if(UNIX AND NOT APPLE) + # On Linux, we assume KTX is not installed and proceed directly to fetching it + set(KTX_FOUND FALSE) +else() + # On non-Linux platforms, try to find KTX using pkg-config first + find_package(PkgConfig QUIET) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_KTX QUIET ktx libktx ktx2 libktx2) + endif() + + # Try to find KTX using standard find_package + find_path(KTX_INCLUDE_DIR + NAMES ktx.h + PATH_SUFFIXES include ktx KTX ktx2 KTX2 + HINTS + ${PC_KTX_INCLUDEDIR} + /usr/include + /usr/local/include + $ENV{KTX_DIR}/include + $ENV{VULKAN_SDK}/include + ${CMAKE_SOURCE_DIR}/external/ktx/include + ) + + find_library(KTX_LIBRARY + NAMES ktx ktx2 libktx libktx2 + PATH_SUFFIXES lib lib64 + HINTS + ${PC_KTX_LIBDIR} + /usr/lib + /usr/lib64 + /usr/local/lib + /usr/local/lib64 + $ENV{KTX_DIR}/lib + $ENV{VULKAN_SDK}/lib + ${CMAKE_SOURCE_DIR}/external/ktx/lib + ) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(KTX + REQUIRED_VARS KTX_INCLUDE_DIR KTX_LIBRARY + FAIL_MESSAGE "" # Suppress the error message to allow our fallback + ) + + # Debug output if KTX is not found (only on non-Linux platforms) + if(NOT KTX_FOUND) + message(STATUS "KTX include directory search paths: ${PC_KTX_INCLUDEDIR}, /usr/include, /usr/local/include, $ENV{KTX_DIR}/include, $ENV{VULKAN_SDK}/include, ${CMAKE_SOURCE_DIR}/external/ktx/include") + message(STATUS "KTX library search paths: ${PC_KTX_LIBDIR}, /usr/lib, /usr/lib64, /usr/local/lib, /usr/local/lib64, $ENV{KTX_DIR}/lib, $ENV{VULKAN_SDK}/lib, ${CMAKE_SOURCE_DIR}/external/ktx/lib") + endif() +endif() + +if(KTX_FOUND) + set(KTX_INCLUDE_DIRS ${KTX_INCLUDE_DIR}) + set(KTX_LIBRARIES ${KTX_LIBRARY}) + + if(NOT TARGET KTX::ktx) + add_library(KTX::ktx UNKNOWN IMPORTED) + set_target_properties(KTX::ktx PROPERTIES + IMPORTED_LOCATION "${KTX_LIBRARIES}" + INTERFACE_INCLUDE_DIRECTORIES "${KTX_INCLUDE_DIRS}" + ) + endif() +else() + # If not found, use FetchContent to download and build + include(FetchContent) + + # Only show the message on non-Linux platforms + if(NOT (UNIX AND NOT APPLE)) + message(STATUS "KTX not found, fetching from GitHub...") + endif() + + FetchContent_Declare( + ktx + GIT_REPOSITORY https://github.com/KhronosGroup/KTX-Software.git + GIT_TAG v4.3.1 # Use a specific tag for stability + ) + + # Set options to minimize build time and dependencies + set(KTX_FEATURE_TOOLS OFF CACHE BOOL "Build KTX tools" FORCE) + set(KTX_FEATURE_DOC OFF CACHE BOOL "Build KTX documentation" FORCE) + set(KTX_FEATURE_TESTS OFF CACHE BOOL "Build KTX tests" FORCE) + + FetchContent_MakeAvailable(ktx) + + # Create an alias to match the expected target name + if(NOT TARGET KTX::ktx) + add_library(KTX::ktx ALIAS ktx) + endif() + + set(KTX_FOUND TRUE) +endif() diff --git a/CMake/FindVulkan.cmake b/CMake/FindVulkan.cmake new file mode 100644 index 0000000..55ac153 --- /dev/null +++ b/CMake/FindVulkan.cmake @@ -0,0 +1,937 @@ +# Updates for iOS Copyright (c) 2024, Holochip Inc. +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +#[=======================================================================[.rst: +FindVulkan +---------- + +.. versionadded:: 3.7 + +Find Vulkan, which is a low-overhead, cross-platform 3D graphics +and computing API. + +Optional COMPONENTS +^^^^^^^^^^^^^^^^^^^ + +.. versionadded:: 3.24 + +This module respects several optional COMPONENTS. +There are corresponding imported targets for each of these. + +``glslc`` + The SPIR-V compiler. + +``glslangValidator`` + The ``glslangValidator`` tool. + +``glslang`` + The SPIR-V generator library. + +``shaderc_combined`` + The static library for Vulkan shader compilation. + +``SPIRV-Tools`` + Tools to process SPIR-V modules. + +``MoltenVK`` + On macOS, an additional component ``MoltenVK`` is available. + +``dxc`` + .. versionadded:: 3.25 + + The DirectX Shader Compiler. + +The ``glslc`` and ``glslangValidator`` components are provided even +if not explicitly requested (for backward compatibility). + +IMPORTED Targets +^^^^^^^^^^^^^^^^ + +This module defines :prop_tgt:`IMPORTED` targets if Vulkan has been found: + +``Vulkan::Vulkan`` + The main Vulkan library. + +``Vulkan::glslc`` + .. versionadded:: 3.19 + + The GLSLC SPIR-V compiler, if it has been found. + +``Vulkan::Headers`` + .. versionadded:: 3.21 + + Provides just Vulkan headers include paths, if found. No library is + included in this target. This can be useful for applications that + load Vulkan library dynamically. + +``Vulkan::glslangValidator`` + .. versionadded:: 3.21 + + The glslangValidator tool, if found. It is used to compile GLSL and + HLSL shaders into SPIR-V. + +``Vulkan::glslang`` + .. versionadded:: 3.24 + + Defined if SDK has the Khronos-reference front-end shader parser and SPIR-V + generator library (glslang). + +``Vulkan::shaderc_combined`` + .. versionadded:: 3.24 + + Defined if SDK has the Google static library for Vulkan shader compilation + (shaderc_combined). + +``Vulkan::SPIRV-Tools`` + .. versionadded:: 3.24 + + Defined if SDK has the Khronos library to process SPIR-V modules + (SPIRV-Tools). + +``Vulkan::MoltenVK`` + .. versionadded:: 3.24 + + Defined if SDK has the Khronos library which implement a subset of Vulkan API + over Apple Metal graphics framework. (MoltenVK). + +``Vulkan::volk`` + .. versionadded:: 3.25 + + Defined if SDK has the Vulkan meta-loader (volk). + +``Vulkan::dxc_lib`` + .. versionadded:: 3.25 + + Defined if SDK has the DirectX shader compiler library. + +``Vulkan::dxc_exe`` + .. versionadded:: 3.25 + + Defined if SDK has the DirectX shader compiler CLI tool. + +Result Variables +^^^^^^^^^^^^^^^^ + +This module defines the following variables: + +``Vulkan_FOUND`` + set to true if Vulkan was found +``Vulkan_INCLUDE_DIRS`` + include directories for Vulkan +``Vulkan_LIBRARIES`` + link against this library to use Vulkan +``Vulkan_VERSION`` + .. versionadded:: 3.23 + + value from ``vulkan/vulkan_core.h`` +``Vulkan_glslc_FOUND`` + .. versionadded:: 3.24 + + True, if the SDK has the glslc executable. +``Vulkan_glslangValidator_FOUND`` + .. versionadded:: 3.24 + + True, if the SDK has the glslangValidator executable. +``Vulkan_glslang_FOUND`` + .. versionadded:: 3.24 + + True, if the SDK has the glslang library. +``Vulkan_shaderc_combined_FOUND`` + .. versionadded:: 3.24 + + True, if the SDK has the shaderc_combined library. +``Vulkan_SPIRV-Tools_FOUND`` + .. versionadded:: 3.24 + + True, if the SDK has the SPIRV-Tools library. +``Vulkan_MoltenVK_FOUND`` + .. versionadded:: 3.24 + + True, if the SDK has the MoltenVK library. +``Vulkan_volk_FOUND`` + .. versionadded:: 3.25 + + True, if the SDK has the volk library. + +``Vulkan_dxc_lib_FOUND`` + .. versionadded:: 3.25 + + True, if the SDK has the DirectX shader compiler library. + +``Vulkan_dxc_exe_FOUND`` + .. versionadded:: 3.25 + + True, if the SDK has the DirectX shader compiler CLI tool. + + +The module will also defines these cache variables: + +``Vulkan_INCLUDE_DIR`` + the Vulkan include directory +``Vulkan_LIBRARY`` + the path to the Vulkan library +``Vulkan_GLSLC_EXECUTABLE`` + the path to the GLSL SPIR-V compiler +``Vulkan_GLSLANG_VALIDATOR_EXECUTABLE`` + the path to the glslangValidator tool +``Vulkan_glslang_LIBRARY`` + .. versionadded:: 3.24 + + Path to the glslang library. +``Vulkan_shaderc_combined_LIBRARY`` + .. versionadded:: 3.24 + + Path to the shaderc_combined library. +``Vulkan_SPIRV-Tools_LIBRARY`` + .. versionadded:: 3.24 + + Path to the SPIRV-Tools library. +``Vulkan_MoltenVK_LIBRARY`` + .. versionadded:: 3.24 + + Path to the MoltenVK library. + +``Vulkan_volk_LIBRARY`` + .. versionadded:: 3.25 + + Path to the volk library. + +``Vulkan_dxc_LIBRARY`` + .. versionadded:: 3.25 + + Path to the DirectX shader compiler library. + +``Vulkan_dxc_EXECUTABLE`` + .. versionadded:: 3.25 + + Path to the DirectX shader compiler CLI tool. + +Hints +^^^^^ + +.. versionadded:: 3.18 + +The ``VULKAN_SDK`` environment variable optionally specifies the +location of the Vulkan SDK root directory for the given +architecture. It is typically set by sourcing the toplevel +``setup-env.sh`` script of the Vulkan SDK directory into the shell +environment. + +#]=======================================================================] + +cmake_policy(PUSH) +cmake_policy(SET CMP0057 NEW) + +# Provide compatibility with a common invalid component request that +# was silently ignored prior to CMake 3.24. +if("FATAL_ERROR" IN_LIST Vulkan_FIND_COMPONENTS) + message(AUTHOR_WARNING + "Ignoring unknown component 'FATAL_ERROR'.\n" + "The find_package() command documents no such argument." + ) + list(REMOVE_ITEM Vulkan_FIND_COMPONENTS "FATAL_ERROR") +endif() + +if(IOS) + get_filename_component(Vulkan_Target_SDK "$ENV{VULKAN_SDK}/.." REALPATH) + list(APPEND CMAKE_FRAMEWORK_PATH "${Vulkan_Target_SDK}/iOS/lib") + set (CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH) + set (CMAKE_FIND_ROOT_PATH_MODE_LIBRARY BOTH) + set (CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH) +endif() + +# For backward compatibility as `FindVulkan` in previous CMake versions allow to retrieve `glslc` +# and `glslangValidator` without requesting the corresponding component. +if(NOT glslc IN_LIST Vulkan_FIND_COMPONENTS) + list(APPEND Vulkan_FIND_COMPONENTS glslc) +endif() +if(NOT glslangValidator IN_LIST Vulkan_FIND_COMPONENTS) + list(APPEND Vulkan_FIND_COMPONENTS glslangValidator) +endif() + +if(WIN32) + set(_Vulkan_library_name vulkan-1) + set(_Vulkan_hint_include_search_paths + "$ENV{VULKAN_SDK}/include" + ) + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(_Vulkan_hint_executable_search_paths + "$ENV{VULKAN_SDK}/bin" + ) + set(_Vulkan_hint_library_search_paths + "$ENV{VULKAN_SDK}/lib" + "$ENV{VULKAN_SDK}/bin" + ) + else() + set(_Vulkan_hint_executable_search_paths + "$ENV{VULKAN_SDK}/bin32" + "$ENV{VULKAN_SDK}/bin" + ) + set(_Vulkan_hint_library_search_paths + "$ENV{VULKAN_SDK}/lib32" + "$ENV{VULKAN_SDK}/bin32" + "$ENV{VULKAN_SDK}/lib" + "$ENV{VULKAN_SDK}/bin" + ) + endif() +else() + set(_Vulkan_library_name vulkan) + set(_Vulkan_hint_include_search_paths + "$ENV{VULKAN_SDK}/include" + ) + set(_Vulkan_hint_executable_search_paths + "$ENV{VULKAN_SDK}/bin" + ) + set(_Vulkan_hint_library_search_paths + "$ENV{VULKAN_SDK}/lib" + ) +endif() +if(APPLE AND DEFINED ENV{VULKAN_SDK}) + list(APPEND _Vulkan_hint_include_search_paths + "${Vulkan_Target_SDK}/macOS/include" + ) + if(CMAKE_SYSTEM_NAME STREQUAL "iOS") + list(APPEND _Vulkan_hint_library_search_paths + "${Vulkan_Target_SDK}/iOS/lib" + ) + elseif(CMAKE_SYSTEM_NAME STREQUAL "tvOS") + list(APPEND _Vulkan_hint_library_search_paths + "${Vulkan_Target_SDK}/tvOS/lib" + ) + else() + list(APPEND _Vulkan_hint_library_search_paths + "${Vulkan_Target_SDK}}/lib" + ) + endif() +endif() + +find_path(Vulkan_INCLUDE_DIR + NAMES vulkan/vulkan.h + HINTS + ${_Vulkan_hint_include_search_paths} +) +message(STATUS "vulkan_include_dir ${Vulkan_INCLUDE_DIR} search paths ${_Vulkan_hint_include_search_paths}") +mark_as_advanced(Vulkan_INCLUDE_DIR) + +find_library(Vulkan_LIBRARY + NAMES ${_Vulkan_library_name} + HINTS + ${_Vulkan_hint_library_search_paths} +) +message(STATUS "vulkan_library ${Vulkan_LIBRARY} search paths ${_Vulkan_hint_library_search_paths}") +mark_as_advanced(Vulkan_LIBRARY) + +find_library(Vulkan_Layer_API_DUMP + NAMES VkLayer_api_dump + HINTS + ${_Vulkan_hint_library_search_paths} +) +mark_as_advanced(Vulkan_Layer_API_DUMP) + +find_library(Vulkan_Layer_SHADER_OBJECT + NAMES VkLayer_khronos_shader_object + HINTS + ${_Vulkan_hint_library_search_paths} +) +mark_as_advanced(VkLayer_khronos_shader_object) + +find_library(Vulkan_Layer_SYNC2 + NAMES VkLayer_khronos_synchronization2 + HINTS + ${_Vulkan_hint_library_search_paths} +) +mark_as_advanced(Vulkan_Layer_SYNC2) + +find_library(Vulkan_Layer_VALIDATION + NAMES VkLayer_khronos_validation + HINTS + ${_Vulkan_hint_library_search_paths} +) +mark_as_advanced(Vulkan_Layer_VALIDATION) + +if(glslc IN_LIST Vulkan_FIND_COMPONENTS) + find_program(Vulkan_GLSLC_EXECUTABLE + NAMES glslc + HINTS + ${_Vulkan_hint_executable_search_paths} + ) + mark_as_advanced(Vulkan_GLSLC_EXECUTABLE) +endif() +if(glslangValidator IN_LIST Vulkan_FIND_COMPONENTS) + find_program(Vulkan_GLSLANG_VALIDATOR_EXECUTABLE + NAMES glslangValidator + HINTS + ${_Vulkan_hint_executable_search_paths} + ) + mark_as_advanced(Vulkan_GLSLANG_VALIDATOR_EXECUTABLE) +endif() +if(glslang IN_LIST Vulkan_FIND_COMPONENTS) + find_library(Vulkan_glslang-spirv_LIBRARY + NAMES SPIRV + HINTS + ${_Vulkan_hint_library_search_paths} + ) + mark_as_advanced(Vulkan_glslang-spirv_LIBRARY) + + find_library(Vulkan_glslang-spirv_DEBUG_LIBRARY + NAMES SPIRVd + HINTS + ${_Vulkan_hint_library_search_paths} + ) + mark_as_advanced(Vulkan_glslang-spirv_DEBUG_LIBRARY) + + find_library(Vulkan_glslang-oglcompiler_LIBRARY + NAMES OGLCompiler + HINTS + ${_Vulkan_hint_library_search_paths} + ) + mark_as_advanced(Vulkan_glslang-oglcompiler_LIBRARY) + + find_library(Vulkan_glslang-oglcompiler_DEBUG_LIBRARY + NAMES OGLCompilerd + HINTS + ${_Vulkan_hint_library_search_paths} + ) + mark_as_advanced(Vulkan_glslang-oglcompiler_DEBUG_LIBRARY) + + find_library(Vulkan_glslang-osdependent_LIBRARY + NAMES OSDependent + HINTS + ${_Vulkan_hint_library_search_paths} + ) + mark_as_advanced(Vulkan_glslang-osdependent_LIBRARY) + + find_library(Vulkan_glslang-osdependent_DEBUG_LIBRARY + NAMES OSDependentd + HINTS + ${_Vulkan_hint_library_search_paths} + ) + mark_as_advanced(Vulkan_glslang-osdependent_DEBUG_LIBRARY) + + find_library(Vulkan_glslang-machineindependent_LIBRARY + NAMES MachineIndependent + HINTS + ${_Vulkan_hint_library_search_paths} + ) + mark_as_advanced(Vulkan_glslang-machineindependent_LIBRARY) + + find_library(Vulkan_glslang-machineindependent_DEBUG_LIBRARY + NAMES MachineIndependentd + HINTS + ${_Vulkan_hint_library_search_paths} + ) + mark_as_advanced(Vulkan_glslang-machineindependent_DEBUG_LIBRARY) + + find_library(Vulkan_glslang-genericcodegen_LIBRARY + NAMES GenericCodeGen + HINTS + ${_Vulkan_hint_library_search_paths} + ) + mark_as_advanced(Vulkan_glslang-genericcodegen_LIBRARY) + + find_library(Vulkan_glslang-genericcodegen_DEBUG_LIBRARY + NAMES GenericCodeGend + HINTS + ${_Vulkan_hint_library_search_paths} + ) + mark_as_advanced(Vulkan_glslang-genericcodegen_DEBUG_LIBRARY) + + find_library(Vulkan_glslang_LIBRARY + NAMES glslang + HINTS + ${_Vulkan_hint_library_search_paths} + ) + mark_as_advanced(Vulkan_glslang_LIBRARY) + + find_library(Vulkan_glslang_DEBUG_LIBRARY + NAMES glslangd + HINTS + ${_Vulkan_hint_library_search_paths} + ) + mark_as_advanced(Vulkan_glslang_DEBUG_LIBRARY) +endif() +if(shaderc_combined IN_LIST Vulkan_FIND_COMPONENTS) + find_library(Vulkan_shaderc_combined_LIBRARY + NAMES shaderc_combined + HINTS + ${_Vulkan_hint_library_search_paths}) + mark_as_advanced(Vulkan_shaderc_combined_LIBRARY) + + find_library(Vulkan_shaderc_combined_DEBUG_LIBRARY + NAMES shaderc_combinedd + HINTS + ${_Vulkan_hint_library_search_paths}) + mark_as_advanced(Vulkan_shaderc_combined_DEBUG_LIBRARY) +endif() +if(SPIRV-Tools IN_LIST Vulkan_FIND_COMPONENTS) + find_library(Vulkan_SPIRV-Tools_LIBRARY + NAMES SPIRV-Tools + HINTS + ${_Vulkan_hint_library_search_paths}) + mark_as_advanced(Vulkan_SPIRV-Tools_LIBRARY) + + find_library(Vulkan_SPIRV-Tools_DEBUG_LIBRARY + NAMES SPIRV-Toolsd + HINTS + ${_Vulkan_hint_library_search_paths}) + mark_as_advanced(Vulkan_SPIRV-Tools_DEBUG_LIBRARY) +endif() +if(MoltenVK IN_LIST Vulkan_FIND_COMPONENTS) + # CMake has a bug in 3.28 that doesn't handle xcframeworks. Do it by hand for now. + if(CMAKE_SYSTEM_NAME STREQUAL "iOS") + if(CMAKE_VERSION VERSION_LESS 3.29) + set( _Vulkan_hint_library_search_paths ${Vulkan_Target_SDK}/ios/lib/MoltenVK.xcframework/ios-arm64) + else () + set( _Vulkan_hint_library_search_paths ${Vulkan_Target_SDK}/ios/lib/) + endif () + endif () + find_library(Vulkan_MoltenVK_LIBRARY + NAMES MoltenVK + HINTS + ${_Vulkan_hint_library_search_paths} + ) + mark_as_advanced(Vulkan_MoltenVK_LIBRARY) + + find_path(Vulkan_MoltenVK_INCLUDE_DIR + NAMES MoltenVK/mvk_vulkan.h + HINTS + ${_Vulkan_hint_include_search_paths} + ) + mark_as_advanced(Vulkan_MoltenVK_INCLUDE_DIR) +endif() +if(volk IN_LIST Vulkan_FIND_COMPONENTS) + find_library(Vulkan_volk_LIBRARY + NAMES volk + HINTS + ${_Vulkan_hint_library_search_paths}) + mark_as_advanced(Vulkan_Volk_LIBRARY) +endif() + +if (dxc IN_LIST Vulkan_FIND_COMPONENTS) + find_library(Vulkan_dxc_LIBRARY + NAMES dxcompiler + HINTS + ${_Vulkan_hint_library_search_paths}) + mark_as_advanced(Vulkan_dxc_LIBRARY) + + find_program(Vulkan_dxc_EXECUTABLE + NAMES dxc + HINTS + ${_Vulkan_hint_executable_search_paths}) + mark_as_advanced(Vulkan_dxc_EXECUTABLE) +endif() + +if(Vulkan_GLSLC_EXECUTABLE) + set(Vulkan_glslc_FOUND TRUE) +else() + set(Vulkan_glslc_FOUND FALSE) +endif() + +if(Vulkan_GLSLANG_VALIDATOR_EXECUTABLE) + set(Vulkan_glslangValidator_FOUND TRUE) +else() + set(Vulkan_glslangValidator_FOUND FALSE) +endif() + +if (Vulkan_dxc_EXECUTABLE) + set(Vulkan_dxc_exe_FOUND TRUE) +else() + set(Vulkan_dxc_exe_FOUND FALSE) +endif() + +function(_Vulkan_set_library_component_found component) + cmake_parse_arguments(PARSE_ARGV 1 _ARG + "NO_WARNING" + "" + "DEPENDENT_COMPONENTS") + + set(all_dependent_component_found TRUE) + foreach(dependent_component IN LISTS _ARG_DEPENDENT_COMPONENTS) + if(NOT Vulkan_${dependent_component}_FOUND) + set(all_dependent_component_found FALSE) + break() + endif() + endforeach() + + if(all_dependent_component_found AND (Vulkan_${component}_LIBRARY OR Vulkan_${component}_DEBUG_LIBRARY)) + set(Vulkan_${component}_FOUND TRUE PARENT_SCOPE) + + # For Windows Vulkan SDK, third party tools binaries are provided with different MSVC ABI: + # - Release binaries uses a runtime library + # - Debug binaries uses a debug runtime library + # This lead to incompatibilities in linking for some configuration types due to CMake-default or project-configured selected MSVC ABI. + if(WIN32 AND NOT _ARG_NO_WARNING) + if(NOT Vulkan_${component}_LIBRARY) + message(WARNING + "Library ${component} for Release configuration is missing, imported target Vulkan::${component} may not be able to link when targeting this build configuration due to incompatible MSVC ABI.") + endif() + if(NOT Vulkan_${component}_DEBUG_LIBRARY) + message(WARNING + "Library ${component} for Debug configuration is missing, imported target Vulkan::${component} may not be able to link when targeting this build configuration due to incompatible MSVC ABI. Consider re-installing the Vulkan SDK and request debug libraries to fix this warning.") + endif() + endif() + else() + set(Vulkan_${component}_FOUND FALSE PARENT_SCOPE) + endif() +endfunction() + +_Vulkan_set_library_component_found(glslang-spirv NO_WARNING) +_Vulkan_set_library_component_found(glslang-oglcompiler NO_WARNING) +_Vulkan_set_library_component_found(glslang-osdependent NO_WARNING) +_Vulkan_set_library_component_found(glslang-machineindependent NO_WARNING) +_Vulkan_set_library_component_found(glslang-genericcodegen NO_WARNING) +_Vulkan_set_library_component_found(glslang + DEPENDENT_COMPONENTS + glslang-spirv + glslang-oglcompiler + glslang-osdependent + glslang-machineindependent + glslang-genericcodegen) +_Vulkan_set_library_component_found(shaderc_combined) +_Vulkan_set_library_component_found(SPIRV-Tools) +_Vulkan_set_library_component_found(volk) +_Vulkan_set_library_component_found(dxc) + +if(Vulkan_MoltenVK_INCLUDE_DIR AND Vulkan_MoltenVK_LIBRARY) + set(Vulkan_MoltenVK_FOUND TRUE) +else() + set(Vulkan_MoltenVK_FOUND FALSE) +endif() + +set(Vulkan_LIBRARIES ${Vulkan_LIBRARY}) +set(Vulkan_INCLUDE_DIRS ${Vulkan_INCLUDE_DIR}) + +# detect version e.g 1.2.189 +set(Vulkan_VERSION "") +if(Vulkan_INCLUDE_DIR) + set(VULKAN_CORE_H ${Vulkan_INCLUDE_DIR}/vulkan/vulkan_core.h) + if(EXISTS ${VULKAN_CORE_H}) + file(STRINGS ${VULKAN_CORE_H} VulkanHeaderVersionLine REGEX "^#define VK_HEADER_VERSION ") + string(REGEX MATCHALL "[0-9]+" VulkanHeaderVersion "${VulkanHeaderVersionLine}") + file(STRINGS ${VULKAN_CORE_H} VulkanHeaderVersionLine2 REGEX "^#define VK_HEADER_VERSION_COMPLETE ") + string(REGEX MATCHALL "[0-9]+" VulkanHeaderVersion2 "${VulkanHeaderVersionLine2}") + list(LENGTH VulkanHeaderVersion2 _len) + # versions >= 1.2.175 have an additional numbers in front of e.g. '0, 1, 2' instead of '1, 2' + if(_len EQUAL 3) + list(REMOVE_AT VulkanHeaderVersion2 0) + endif() + list(APPEND VulkanHeaderVersion2 ${VulkanHeaderVersion}) + list(JOIN VulkanHeaderVersion2 "." Vulkan_VERSION) + endif() +endif() + +if(Vulkan_MoltenVK_FOUND) + set(Vulkan_MoltenVK_VERSION "") + if(Vulkan_MoltenVK_INCLUDE_DIR) + set(VK_MVK_MOLTENVK_H ${Vulkan_MoltenVK_INCLUDE_DIR}/MoltenVK/vk_mvk_moltenvk.h) + if(EXISTS ${VK_MVK_MOLTENVK_H}) + file(STRINGS ${VK_MVK_MOLTENVK_H} _Vulkan_MoltenVK_VERSION_MAJOR REGEX "^#define MVK_VERSION_MAJOR ") + string(REGEX MATCHALL "[0-9]+" _Vulkan_MoltenVK_VERSION_MAJOR "${_Vulkan_MoltenVK_VERSION_MAJOR}") + file(STRINGS ${VK_MVK_MOLTENVK_H} _Vulkan_MoltenVK_VERSION_MINOR REGEX "^#define MVK_VERSION_MINOR ") + string(REGEX MATCHALL "[0-9]+" _Vulkan_MoltenVK_VERSION_MINOR "${_Vulkan_MoltenVK_VERSION_MINOR}") + file(STRINGS ${VK_MVK_MOLTENVK_H} _Vulkan_MoltenVK_VERSION_PATCH REGEX "^#define MVK_VERSION_PATCH ") + string(REGEX MATCHALL "[0-9]+" _Vulkan_MoltenVK_VERSION_PATCH "${_Vulkan_MoltenVK_VERSION_PATCH}") + set(Vulkan_MoltenVK_VERSION "${_Vulkan_MoltenVK_VERSION_MAJOR}.${_Vulkan_MoltenVK_VERSION_MINOR}.${_Vulkan_MoltenVK_VERSION_PATCH}") + unset(_Vulkan_MoltenVK_VERSION_MAJOR) + unset(_Vulkan_MoltenVK_VERSION_MINOR) + unset(_Vulkan_MoltenVK_VERSION_PATCH) + endif() + endif() +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Vulkan + REQUIRED_VARS + Vulkan_LIBRARY + Vulkan_INCLUDE_DIR + VERSION_VAR + Vulkan_VERSION + HANDLE_COMPONENTS +) + +if(Vulkan_FOUND AND NOT TARGET Vulkan::Vulkan) + add_library(Vulkan::Vulkan UNKNOWN IMPORTED) + set_target_properties(Vulkan::Vulkan PROPERTIES + IMPORTED_LOCATION "${Vulkan_LIBRARIES}" + INTERFACE_INCLUDE_DIRECTORIES "${Vulkan_INCLUDE_DIRS}") +endif() + +if(Vulkan_FOUND AND NOT TARGET Vulkan::Headers) + add_library(Vulkan::Headers INTERFACE IMPORTED) + set_target_properties(Vulkan::Headers PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${Vulkan_INCLUDE_DIRS}") +endif() + +if(Vulkan_FOUND AND Vulkan_GLSLC_EXECUTABLE AND NOT TARGET Vulkan::glslc) + add_executable(Vulkan::glslc IMPORTED) + set_property(TARGET Vulkan::glslc PROPERTY IMPORTED_LOCATION "${Vulkan_GLSLC_EXECUTABLE}") +endif() + +if(Vulkan_FOUND AND Vulkan_GLSLANG_VALIDATOR_EXECUTABLE AND NOT TARGET Vulkan::glslangValidator) + add_executable(Vulkan::glslangValidator IMPORTED) + set_property(TARGET Vulkan::glslangValidator PROPERTY IMPORTED_LOCATION "${Vulkan_GLSLANG_VALIDATOR_EXECUTABLE}") +endif() + +if(Vulkan_FOUND) + if((Vulkan_glslang-spirv_LIBRARY OR Vulkan_glslang-spirv_DEBUG_LIBRARY) AND NOT TARGET Vulkan::glslang-spirv) + add_library(Vulkan::glslang-spirv STATIC IMPORTED) + set_property(TARGET Vulkan::glslang-spirv + PROPERTY + INTERFACE_INCLUDE_DIRECTORIES "${Vulkan_INCLUDE_DIRS}") + if(Vulkan_glslang-spirv_LIBRARY) + set_property(TARGET Vulkan::glslang-spirv APPEND + PROPERTY + IMPORTED_CONFIGURATIONS Release) + set_property(TARGET Vulkan::glslang-spirv + PROPERTY + IMPORTED_LOCATION_RELEASE "${Vulkan_glslang-spirv_LIBRARY}") + endif() + if(Vulkan_glslang-spirv_DEBUG_LIBRARY) + set_property(TARGET Vulkan::glslang-spirv APPEND + PROPERTY + IMPORTED_CONFIGURATIONS Debug) + set_property(TARGET Vulkan::glslang-spirv + PROPERTY + IMPORTED_LOCATION_DEBUG "${Vulkan_glslang-spirv_DEBUG_LIBRARY}") + endif() + endif() + + if((Vulkan_glslang-oglcompiler_LIBRARY OR Vulkan_glslang-oglcompiler_DEBUG_LIBRARY) AND NOT TARGET Vulkan::glslang-oglcompiler) + add_library(Vulkan::glslang-oglcompiler STATIC IMPORTED) + set_property(TARGET Vulkan::glslang-oglcompiler + PROPERTY + INTERFACE_INCLUDE_DIRECTORIES "${Vulkan_INCLUDE_DIRS}") + if(Vulkan_glslang-oglcompiler_LIBRARY) + set_property(TARGET Vulkan::glslang-oglcompiler APPEND + PROPERTY + IMPORTED_CONFIGURATIONS Release) + set_property(TARGET Vulkan::glslang-oglcompiler + PROPERTY + IMPORTED_LOCATION_RELEASE "${Vulkan_glslang-oglcompiler_LIBRARY}") + endif() + if(Vulkan_glslang-oglcompiler_DEBUG_LIBRARY) + set_property(TARGET Vulkan::glslang-oglcompiler APPEND + PROPERTY + IMPORTED_CONFIGURATIONS Debug) + set_property(TARGET Vulkan::glslang-oglcompiler + PROPERTY + IMPORTED_LOCATION_DEBUG "${Vulkan_glslang-oglcompiler_DEBUG_LIBRARY}") + endif() + endif() + + if((Vulkan_glslang-osdependent_LIBRARY OR Vulkan_glslang-osdependent_DEBUG_LIBRARY) AND NOT TARGET Vulkan::glslang-osdependent) + add_library(Vulkan::glslang-osdependent STATIC IMPORTED) + set_property(TARGET Vulkan::glslang-osdependent + PROPERTY + INTERFACE_INCLUDE_DIRECTORIES "${Vulkan_INCLUDE_DIRS}") + if(Vulkan_glslang-osdependent_LIBRARY) + set_property(TARGET Vulkan::glslang-osdependent APPEND + PROPERTY + IMPORTED_CONFIGURATIONS Release) + set_property(TARGET Vulkan::glslang-osdependent + PROPERTY + IMPORTED_LOCATION_RELEASE "${Vulkan_glslang-osdependent_LIBRARY}") + endif() + if(Vulkan_glslang-osdependent_DEBUG_LIBRARY) + set_property(TARGET Vulkan::glslang-osdependent APPEND + PROPERTY + IMPORTED_CONFIGURATIONS Debug) + set_property(TARGET Vulkan::glslang-osdependent + PROPERTY + IMPORTED_LOCATION_DEBUG "${Vulkan_glslang-osdependent_DEBUG_LIBRARY}") + endif() + endif() + + if((Vulkan_glslang-machineindependent_LIBRARY OR Vulkan_glslang-machineindependent_DEBUG_LIBRARY) AND NOT TARGET Vulkan::glslang-machineindependent) + add_library(Vulkan::glslang-machineindependent STATIC IMPORTED) + set_property(TARGET Vulkan::glslang-machineindependent + PROPERTY + INTERFACE_INCLUDE_DIRECTORIES "${Vulkan_INCLUDE_DIRS}") + if(Vulkan_glslang-machineindependent_LIBRARY) + set_property(TARGET Vulkan::glslang-machineindependent APPEND + PROPERTY + IMPORTED_CONFIGURATIONS Release) + set_property(TARGET Vulkan::glslang-machineindependent + PROPERTY + IMPORTED_LOCATION_RELEASE "${Vulkan_glslang-machineindependent_LIBRARY}") + endif() + if(Vulkan_glslang-machineindependent_DEBUG_LIBRARY) + set_property(TARGET Vulkan::glslang-machineindependent APPEND + PROPERTY + IMPORTED_CONFIGURATIONS Debug) + set_property(TARGET Vulkan::glslang-machineindependent + PROPERTY + IMPORTED_LOCATION_DEBUG "${Vulkan_glslang-machineindependent_DEBUG_LIBRARY}") + endif() + endif() + + if((Vulkan_glslang-genericcodegen_LIBRARY OR Vulkan_glslang-genericcodegen_DEBUG_LIBRARY) AND NOT TARGET Vulkan::glslang-genericcodegen) + add_library(Vulkan::glslang-genericcodegen STATIC IMPORTED) + set_property(TARGET Vulkan::glslang-genericcodegen + PROPERTY + INTERFACE_INCLUDE_DIRECTORIES "${Vulkan_INCLUDE_DIRS}") + if(Vulkan_glslang-genericcodegen_LIBRARY) + set_property(TARGET Vulkan::glslang-genericcodegen APPEND + PROPERTY + IMPORTED_CONFIGURATIONS Release) + set_property(TARGET Vulkan::glslang-genericcodegen + PROPERTY + IMPORTED_LOCATION_RELEASE "${Vulkan_glslang-genericcodegen_LIBRARY}") + endif() + if(Vulkan_glslang-genericcodegen_DEBUG_LIBRARY) + set_property(TARGET Vulkan::glslang-genericcodegen APPEND + PROPERTY + IMPORTED_CONFIGURATIONS Debug) + set_property(TARGET Vulkan::glslang-genericcodegen + PROPERTY + IMPORTED_LOCATION_DEBUG "${Vulkan_glslang-genericcodegen_DEBUG_LIBRARY}") + endif() + endif() + + if((Vulkan_glslang_LIBRARY OR Vulkan_glslang_DEBUG_LIBRARY) + AND TARGET Vulkan::glslang-spirv + AND TARGET Vulkan::glslang-oglcompiler + AND TARGET Vulkan::glslang-osdependent + AND TARGET Vulkan::glslang-machineindependent + AND TARGET Vulkan::glslang-genericcodegen + AND NOT TARGET Vulkan::glslang) + add_library(Vulkan::glslang STATIC IMPORTED) + set_property(TARGET Vulkan::glslang + PROPERTY + INTERFACE_INCLUDE_DIRECTORIES "${Vulkan_INCLUDE_DIRS}") + if(Vulkan_glslang_LIBRARY) + set_property(TARGET Vulkan::glslang APPEND + PROPERTY + IMPORTED_CONFIGURATIONS Release) + set_property(TARGET Vulkan::glslang + PROPERTY + IMPORTED_LOCATION_RELEASE "${Vulkan_glslang_LIBRARY}") + endif() + if(Vulkan_glslang_DEBUG_LIBRARY) + set_property(TARGET Vulkan::glslang APPEND + PROPERTY + IMPORTED_CONFIGURATIONS Debug) + set_property(TARGET Vulkan::glslang + PROPERTY + IMPORTED_LOCATION_DEBUG "${Vulkan_glslang_DEBUG_LIBRARY}") + endif() + target_link_libraries(Vulkan::glslang + INTERFACE + Vulkan::glslang-spirv + Vulkan::glslang-oglcompiler + Vulkan::glslang-osdependent + Vulkan::glslang-machineindependent + Vulkan::glslang-genericcodegen + ) + endif() + + if((Vulkan_shaderc_combined_LIBRARY OR Vulkan_shaderc_combined_DEBUG_LIBRARY) AND NOT TARGET Vulkan::shaderc_combined) + add_library(Vulkan::shaderc_combined STATIC IMPORTED) + set_property(TARGET Vulkan::shaderc_combined + PROPERTY + INTERFACE_INCLUDE_DIRECTORIES "${Vulkan_INCLUDE_DIRS}") + if(Vulkan_shaderc_combined_LIBRARY) + set_property(TARGET Vulkan::shaderc_combined APPEND + PROPERTY + IMPORTED_CONFIGURATIONS Release) + set_property(TARGET Vulkan::shaderc_combined + PROPERTY + IMPORTED_LOCATION_RELEASE "${Vulkan_shaderc_combined_LIBRARY}") + endif() + if(Vulkan_shaderc_combined_DEBUG_LIBRARY) + set_property(TARGET Vulkan::shaderc_combined APPEND + PROPERTY + IMPORTED_CONFIGURATIONS Debug) + set_property(TARGET Vulkan::shaderc_combined + PROPERTY + IMPORTED_LOCATION_DEBUG "${Vulkan_shaderc_combined_DEBUG_LIBRARY}") + endif() + + if(UNIX) + find_package(Threads REQUIRED) + target_link_libraries(Vulkan::shaderc_combined + INTERFACE + Threads::Threads) + endif() + endif() + + if((Vulkan_SPIRV-Tools_LIBRARY OR Vulkan_SPIRV-Tools_DEBUG_LIBRARY) AND NOT TARGET Vulkan::SPIRV-Tools) + add_library(Vulkan::SPIRV-Tools STATIC IMPORTED) + set_property(TARGET Vulkan::SPIRV-Tools + PROPERTY + INTERFACE_INCLUDE_DIRECTORIES "${Vulkan_INCLUDE_DIRS}") + if(Vulkan_SPIRV-Tools_LIBRARY) + set_property(TARGET Vulkan::SPIRV-Tools APPEND + PROPERTY + IMPORTED_CONFIGURATIONS Release) + set_property(TARGET Vulkan::SPIRV-Tools + PROPERTY + IMPORTED_LOCATION_RELEASE "${Vulkan_SPIRV-Tools_LIBRARY}") + endif() + if(Vulkan_SPIRV-Tools_DEBUG_LIBRARY) + set_property(TARGET Vulkan::SPIRV-Tools APPEND + PROPERTY + IMPORTED_CONFIGURATIONS Debug) + set_property(TARGET Vulkan::SPIRV-Tools + PROPERTY + IMPORTED_LOCATION_DEBUG "${Vulkan_SPIRV-Tools_DEBUG_LIBRARY}") + endif() + endif() + + if(Vulkan_volk_LIBRARY AND NOT TARGET Vulkan::volk) + add_library(Vulkan::volk STATIC IMPORTED) + set_property(TARGET Vulkan::volk + PROPERTY + INTERFACE_INCLUDE_DIRECTORIES "${Vulkan_INCLUDE_DIRS}") + set_property(TARGET Vulkan::volk APPEND + PROPERTY + IMPORTED_CONFIGURATIONS Release) + set_property(TARGET Vulkan::volk APPEND + PROPERTY + IMPORTED_LOCATION_RELEASE "${Vulkan_volk_LIBRARY}") + + if (NOT WIN32) + set_property(TARGET Vulkan::volk APPEND + PROPERTY + IMPORTED_LINK_INTERFACE_LIBRARIES dl) + endif() + endif() + + if (Vulkan_dxc_LIBRARY AND NOT TARGET Vulkan::dxc_lib) + add_library(Vulkan::dxc_lib STATIC IMPORTED) + set_property(TARGET Vulkan::dxc_lib + PROPERTY + INTERFACE_INCLUDE_DIRECTORIES "${Vulkan_INCLUDE_DIRS}") + set_property(TARGET Vulkan::dxc_lib APPEND + PROPERTY + IMPORTED_CONFIGURATIONS Release) + set_property(TARGET Vulkan::dxc_lib APPEND + PROPERTY + IMPORTED_LOCATION_RELEASE "${Vulkan_dxc_LIBRARY}") + endif() + + if(Vulkan_dxc_EXECUTABLE AND NOT TARGET Vulkan::dxc_exe) + add_executable(Vulkan::dxc_exe IMPORTED) + set_property(TARGET Vulkan::dxc_exe PROPERTY IMPORTED_LOCATION "${Vulkan_dxc_EXECUTABLE}") + endif() + +endif() + +if(Vulkan_MoltenVK_FOUND) + if(Vulkan_MoltenVK_LIBRARY AND NOT TARGET Vulkan::MoltenVK) + add_library(Vulkan::MoltenVK SHARED IMPORTED) + set_target_properties(Vulkan::MoltenVK + PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${Vulkan_MoltenVK_INCLUDE_DIR}" + IMPORTED_LOCATION "${Vulkan_MoltenVK_LIBRARY}" + ) + endif() +endif() + +unset(_Vulkan_library_name) +unset(_Vulkan_hint_include_search_paths) +unset(_Vulkan_hint_executable_search_paths) +unset(_Vulkan_hint_library_search_paths) + +cmake_policy(POP) \ No newline at end of file diff --git a/CMake/FindVulkanHpp.cmake b/CMake/FindVulkanHpp.cmake new file mode 100644 index 0000000..2c0e23a --- /dev/null +++ b/CMake/FindVulkanHpp.cmake @@ -0,0 +1,426 @@ +# FindVulkanHpp.cmake +# +# Finds or downloads the Vulkan-Hpp headers and Vulkan Profiles headers +# +# This will define the following variables +# +# VulkanHpp_FOUND +# VulkanHpp_INCLUDE_DIRS +# +# and the following imported targets +# +# VulkanHpp::VulkanHpp +# + +# Try to find the package using standard find_path +find_path(VulkanHpp_INCLUDE_DIR + NAMES vulkan/vulkan.hpp + PATHS + ${Vulkan_INCLUDE_DIR} + /usr/include + /usr/local/include + $ENV{VULKAN_SDK}/include + ${ANDROID_NDK}/sources/third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/include + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../include +) + +# Also try to find vulkan.cppm +find_path(VulkanHpp_CPPM_DIR + NAMES vulkan/vulkan.cppm + PATHS + ${Vulkan_INCLUDE_DIR} + /usr/include + /usr/local/include + $ENV{VULKAN_SDK}/include + ${ANDROID_NDK}/sources/third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/include + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../include +) + +# Try to find vulkan_profiles.hpp +find_path(VulkanProfiles_INCLUDE_DIR + NAMES vulkan/vulkan_profiles.hpp + PATHS + ${Vulkan_INCLUDE_DIR} + /usr/include + /usr/local/include + $ENV{VULKAN_SDK}/include + ${ANDROID_NDK}/sources/third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/include + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../include +) + +# Function to extract Vulkan version from vulkan_core.h +function(extract_vulkan_version VULKAN_CORE_H_PATH OUTPUT_VERSION_TAG) + # Extract the version information from vulkan_core.h + file(STRINGS ${VULKAN_CORE_H_PATH} VULKAN_VERSION_MAJOR_LINE REGEX "^#define VK_VERSION_MAJOR") + file(STRINGS ${VULKAN_CORE_H_PATH} VULKAN_VERSION_MINOR_LINE REGEX "^#define VK_VERSION_MINOR") + file(STRINGS ${VULKAN_CORE_H_PATH} VULKAN_HEADER_VERSION_LINE REGEX "^#define VK_HEADER_VERSION") + + set(VERSION_TAG "v1.3.275") # Default fallback + + if(VULKAN_VERSION_MAJOR_LINE AND VULKAN_VERSION_MINOR_LINE AND VULKAN_HEADER_VERSION_LINE) + string(REGEX REPLACE "^#define VK_VERSION_MAJOR[ \t]+([0-9]+).*$" "\\1" VULKAN_VERSION_MAJOR "${VULKAN_VERSION_MAJOR_LINE}") + string(REGEX REPLACE "^#define VK_VERSION_MINOR[ \t]+([0-9]+).*$" "\\1" VULKAN_VERSION_MINOR "${VULKAN_VERSION_MINOR_LINE}") + string(REGEX REPLACE "^#define VK_HEADER_VERSION[ \t]+([0-9]+).*$" "\\1" VULKAN_HEADER_VERSION "${VULKAN_HEADER_VERSION_LINE}") + + # Construct the version tag + set(VERSION_TAG "v${VULKAN_VERSION_MAJOR}.${VULKAN_VERSION_MINOR}.${VULKAN_HEADER_VERSION}") + else() + # Alternative approach: look for VK_HEADER_VERSION_COMPLETE + file(STRINGS ${VULKAN_CORE_H_PATH} VULKAN_HEADER_VERSION_COMPLETE_LINE REGEX "^#define VK_HEADER_VERSION_COMPLETE") + file(STRINGS ${VULKAN_CORE_H_PATH} VULKAN_HEADER_VERSION_LINE REGEX "^#define VK_HEADER_VERSION") + + if(VULKAN_HEADER_VERSION_COMPLETE_LINE AND VULKAN_HEADER_VERSION_LINE) + # Extract the header version + string(REGEX REPLACE "^#define VK_HEADER_VERSION[ \t]+([0-9]+).*$" "\\1" VULKAN_HEADER_VERSION "${VULKAN_HEADER_VERSION_LINE}") + + # Check if the complete version line contains the major and minor versions + if(VULKAN_HEADER_VERSION_COMPLETE_LINE MATCHES "VK_MAKE_API_VERSION\\(.*,[ \t]*([0-9]+),[ \t]*([0-9]+),[ \t]*VK_HEADER_VERSION\\)") + set(VULKAN_VERSION_MAJOR "${CMAKE_MATCH_1}") + set(VULKAN_VERSION_MINOR "${CMAKE_MATCH_2}") + set(VERSION_TAG "v${VULKAN_VERSION_MAJOR}.${VULKAN_VERSION_MINOR}.${VULKAN_HEADER_VERSION}") + endif() + endif() + endif() + + # Return the version tag + set(${OUTPUT_VERSION_TAG} ${VERSION_TAG} PARENT_SCOPE) +endfunction() + +# Determine the Vulkan version to use for Vulkan-Hpp and Vulkan-Profiles +set(VULKAN_VERSION_TAG "v1.3.275") # Default version + +# Try to detect the Vulkan version +set(VULKAN_CORE_H "") + +# If we're building for Android, try to detect the NDK's Vulkan version +if(DEFINED ANDROID_NDK) + # Find the vulkan_core.h file in the NDK + find_file(VULKAN_CORE_H vulkan_core.h + PATHS + ${ANDROID_NDK}/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include/vulkan + ${ANDROID_NDK}/toolchains/llvm/prebuilt/darwin-x86_64/sysroot/usr/include/vulkan + ${ANDROID_NDK}/toolchains/llvm/prebuilt/windows-x86_64/sysroot/usr/include/vulkan + ${ANDROID_NDK}/toolchains/llvm/prebuilt/windows/sysroot/usr/include/vulkan + NO_DEFAULT_PATH + ) + + if(VULKAN_CORE_H) + extract_vulkan_version(${VULKAN_CORE_H} VULKAN_VERSION_TAG) + message(STATUS "Detected NDK Vulkan version: ${VULKAN_VERSION_TAG}") + else() + message(STATUS "Could not find vulkan_core.h in NDK, using default version: ${VULKAN_VERSION_TAG}") + endif() +# For desktop builds, try to detect the Vulkan SDK version +elseif(DEFINED ENV{VULKAN_SDK}) + # Find the vulkan_core.h file in the Vulkan SDK + find_file(VULKAN_CORE_H vulkan_core.h + PATHS + $ENV{VULKAN_SDK}/include/vulkan + NO_DEFAULT_PATH + ) + + if(VULKAN_CORE_H) + extract_vulkan_version(${VULKAN_CORE_H} VULKAN_VERSION_TAG) + message(STATUS "Detected Vulkan SDK version: ${VULKAN_VERSION_TAG}") + else() + message(STATUS "Could not find vulkan_core.h in Vulkan SDK, using default version: ${VULKAN_VERSION_TAG}") + endif() +# If Vulkan package was already found, try to use its include directory +elseif(DEFINED Vulkan_INCLUDE_DIR) + # Find the vulkan_core.h file in the Vulkan include directory + find_file(VULKAN_CORE_H vulkan_core.h + PATHS + ${Vulkan_INCLUDE_DIR}/vulkan + NO_DEFAULT_PATH + ) + + if(VULKAN_CORE_H) + extract_vulkan_version(${VULKAN_CORE_H} VULKAN_VERSION_TAG) + message(STATUS "Detected Vulkan version from include directory: ${VULKAN_VERSION_TAG}") + else() + message(STATUS "Could not find vulkan_core.h in Vulkan include directory, using default version: ${VULKAN_VERSION_TAG}") + endif() +else() + # Try to find vulkan_core.h in system paths + find_file(VULKAN_CORE_H vulkan_core.h + PATHS + /usr/include/vulkan + /usr/local/include/vulkan + ) + + if(VULKAN_CORE_H) + extract_vulkan_version(${VULKAN_CORE_H} VULKAN_VERSION_TAG) + message(STATUS "Detected system Vulkan version: ${VULKAN_VERSION_TAG}") + else() + message(STATUS "Could not find vulkan_core.h in system paths, using default version: ${VULKAN_VERSION_TAG}") + endif() +endif() + +# If the include directory wasn't found, use FetchContent to download and build +if(NOT VulkanHpp_INCLUDE_DIR OR NOT VulkanHpp_CPPM_DIR) + # If not found, use FetchContent to download + include(FetchContent) + + message(STATUS "Vulkan-Hpp not found, fetching from GitHub with version ${VULKAN_VERSION_TAG}...") + FetchContent_Declare( + VulkanHpp + GIT_REPOSITORY https://github.com/KhronosGroup/Vulkan-Hpp.git + GIT_TAG ${VULKAN_VERSION_TAG} # Use the detected or default version + ) + + # Set policy to suppress the deprecation warning + if(POLICY CMP0169) + cmake_policy(SET CMP0169 OLD) + endif() + + # Make sure FetchContent is available + include(FetchContent) + + # Populate the content + FetchContent_GetProperties(VulkanHpp SOURCE_DIR VulkanHpp_SOURCE_DIR) + if(NOT VulkanHpp_POPULATED) + FetchContent_Populate(VulkanHpp) + # Get the source directory after populating + FetchContent_GetProperties(VulkanHpp SOURCE_DIR VulkanHpp_SOURCE_DIR) + endif() + + # Set the include directory to the source directory + set(VulkanHpp_INCLUDE_DIR ${VulkanHpp_SOURCE_DIR}) + message(STATUS "VulkanHpp_SOURCE_DIR: ${VulkanHpp_SOURCE_DIR}") + message(STATUS "VulkanHpp_INCLUDE_DIR: ${VulkanHpp_INCLUDE_DIR}") + + # Check if vulkan.cppm exists in the downloaded repository + if(EXISTS "${VulkanHpp_SOURCE_DIR}/vulkan/vulkan.cppm") + set(VulkanHpp_CPPM_DIR ${VulkanHpp_SOURCE_DIR}) + else() + # If vulkan.cppm doesn't exist, we need to create it + set(VulkanHpp_CPPM_DIR ${CMAKE_CURRENT_BINARY_DIR}/VulkanHpp) + file(MAKE_DIRECTORY ${VulkanHpp_CPPM_DIR}/vulkan) + + # Create vulkan.cppm file + file(WRITE "${VulkanHpp_CPPM_DIR}/vulkan/vulkan.cppm" +"// Auto-generated vulkan.cppm file +module; +#include +export module vulkan; +export namespace vk { + using namespace VULKAN_HPP_NAMESPACE; +} +") + endif() +endif() + +# If the Vulkan Profiles include directory wasn't found, use FetchContent to download +if(NOT VulkanProfiles_INCLUDE_DIR) + # If not found, use FetchContent to download + include(FetchContent) + + message(STATUS "Vulkan-Profiles not found, fetching from GitHub main branch...") + FetchContent_Declare( + VulkanProfiles + GIT_REPOSITORY https://github.com/KhronosGroup/Vulkan-Profiles.git + GIT_TAG main # Use main branch instead of a specific tag + ) + + # Set policy to suppress the deprecation warning + if(POLICY CMP0169) + cmake_policy(SET CMP0169 OLD) + endif() + + # Populate the content + FetchContent_GetProperties(VulkanProfiles SOURCE_DIR VulkanProfiles_SOURCE_DIR) + if(NOT VulkanProfiles_POPULATED) + FetchContent_Populate(VulkanProfiles) + # Get the source directory after populating + FetchContent_GetProperties(VulkanProfiles SOURCE_DIR VulkanProfiles_SOURCE_DIR) + endif() + + # Create the include directory structure if it doesn't exist + set(VulkanProfiles_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/VulkanProfiles/include) + file(MAKE_DIRECTORY ${VulkanProfiles_INCLUDE_DIR}/vulkan) + + # Create a stub vulkan_profiles.hpp file if it doesn't exist + if(NOT EXISTS "${VulkanProfiles_INCLUDE_DIR}/vulkan/vulkan_profiles.hpp") + file(WRITE "${VulkanProfiles_INCLUDE_DIR}/vulkan/vulkan_profiles.hpp" +"// Auto-generated vulkan_profiles.hpp stub file +#pragma once +#include + +namespace vp { + // Stub implementation for Vulkan Profiles + struct ProfileDesc { + const char* name; + uint32_t specVersion; + }; + + inline bool GetProfileSupport(VkPhysicalDevice physicalDevice, const ProfileDesc* pProfile, VkBool32* pSupported) { + *pSupported = VK_TRUE; + return true; + } +} +") + endif() + + message(STATUS "VulkanProfiles_SOURCE_DIR: ${VulkanProfiles_SOURCE_DIR}") + message(STATUS "VulkanProfiles_INCLUDE_DIR: ${VulkanProfiles_INCLUDE_DIR}") +endif() + +# Set the variables +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(VulkanHpp + REQUIRED_VARS VulkanHpp_INCLUDE_DIR + FAIL_MESSAGE "Could NOT find VulkanHpp. Install it or set VulkanHpp_INCLUDE_DIR to the directory containing vulkan/vulkan.hpp" +) + +# Debug output +message(STATUS "VulkanHpp_FOUND: ${VulkanHpp_FOUND}") +message(STATUS "VULKANHPP_FOUND: ${VULKANHPP_FOUND}") + +if(VulkanHpp_FOUND) + set(VulkanHpp_INCLUDE_DIRS ${VulkanHpp_INCLUDE_DIR}) + + # Make sure VulkanHpp_CPPM_DIR is set + if(NOT DEFINED VulkanHpp_CPPM_DIR) + # Check if vulkan.cppm exists in the include directory + if(EXISTS "${VulkanHpp_INCLUDE_DIR}/vulkan/vulkan.cppm") + set(VulkanHpp_CPPM_DIR ${VulkanHpp_INCLUDE_DIR}) + message(STATUS "Found vulkan.cppm in VulkanHpp_INCLUDE_DIR: ${VulkanHpp_CPPM_DIR}") + elseif(DEFINED VulkanHpp_SOURCE_DIR AND EXISTS "${VulkanHpp_SOURCE_DIR}/vulkan/vulkan.cppm") + set(VulkanHpp_CPPM_DIR ${VulkanHpp_SOURCE_DIR}) + message(STATUS "Found vulkan.cppm in VulkanHpp_SOURCE_DIR: ${VulkanHpp_CPPM_DIR}") + elseif(DEFINED vulkanhpp_SOURCE_DIR AND EXISTS "${vulkanhpp_SOURCE_DIR}/vulkan/vulkan.cppm") + set(VulkanHpp_CPPM_DIR ${vulkanhpp_SOURCE_DIR}) + message(STATUS "Found vulkan.cppm in vulkanhpp_SOURCE_DIR: ${VulkanHpp_CPPM_DIR}") + else() + # If vulkan.cppm doesn't exist, we need to create it + set(VulkanHpp_CPPM_DIR ${CMAKE_CURRENT_BINARY_DIR}/VulkanHpp) + file(MAKE_DIRECTORY ${VulkanHpp_CPPM_DIR}/vulkan) + message(STATUS "Creating vulkan.cppm in ${VulkanHpp_CPPM_DIR}") + + # Create vulkan.cppm file + file(WRITE "${VulkanHpp_CPPM_DIR}/vulkan/vulkan.cppm" +"// Auto-generated vulkan.cppm file +module; +#include +export module vulkan; +export namespace vk { + using namespace VULKAN_HPP_NAMESPACE; +} +") + endif() + endif() + + message(STATUS "Final VulkanHpp_CPPM_DIR: ${VulkanHpp_CPPM_DIR}") + + # Add Vulkan Profiles include directory if found + if(VulkanProfiles_INCLUDE_DIR AND EXISTS "${VulkanProfiles_INCLUDE_DIR}/vulkan/vulkan_profiles.hpp") + list(APPEND VulkanHpp_INCLUDE_DIRS ${VulkanProfiles_INCLUDE_DIR}) + message(STATUS "Added Vulkan Profiles include directory: ${VulkanProfiles_INCLUDE_DIR}") + endif() + + # Create an imported target + if(NOT TARGET VulkanHpp::VulkanHpp) + add_library(VulkanHpp::VulkanHpp INTERFACE IMPORTED) + set_target_properties(VulkanHpp::VulkanHpp PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${VulkanHpp_INCLUDE_DIRS}" + ) + endif() +elseif(DEFINED VulkanHpp_SOURCE_DIR OR DEFINED vulkanhpp_SOURCE_DIR) + # If find_package_handle_standard_args failed but we have a VulkanHpp source directory from FetchContent + # Create an imported target + if(NOT TARGET VulkanHpp::VulkanHpp) + add_library(VulkanHpp::VulkanHpp INTERFACE IMPORTED) + + # Determine the source directory + if(DEFINED VulkanHpp_SOURCE_DIR) + set(_vulkanhpp_source_dir ${VulkanHpp_SOURCE_DIR}) + elseif(DEFINED vulkanhpp_SOURCE_DIR) + set(_vulkanhpp_source_dir ${vulkanhpp_SOURCE_DIR}) + endif() + + message(STATUS "Using fallback VulkanHpp source directory: ${_vulkanhpp_source_dir}") + + set_target_properties(VulkanHpp::VulkanHpp PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${_vulkanhpp_source_dir}" + ) + endif() + + # Set variables to indicate that VulkanHpp was found + set(VulkanHpp_FOUND TRUE) + set(VULKANHPP_FOUND TRUE) + + # Set include directories + if(DEFINED _vulkanhpp_source_dir) + set(VulkanHpp_INCLUDE_DIR ${_vulkanhpp_source_dir}) + elseif(DEFINED VulkanHpp_SOURCE_DIR) + set(VulkanHpp_INCLUDE_DIR ${VulkanHpp_SOURCE_DIR}) + elseif(DEFINED vulkanhpp_SOURCE_DIR) + set(VulkanHpp_INCLUDE_DIR ${vulkanhpp_SOURCE_DIR}) + endif() + set(VulkanHpp_INCLUDE_DIRS ${VulkanHpp_INCLUDE_DIR}) + + # Add Vulkan Profiles include directory if found + if(VulkanProfiles_INCLUDE_DIR AND EXISTS "${VulkanProfiles_INCLUDE_DIR}/vulkan/vulkan_profiles.hpp") + list(APPEND VulkanHpp_INCLUDE_DIRS ${VulkanProfiles_INCLUDE_DIR}) + message(STATUS "Added Vulkan Profiles include directory to fallback: ${VulkanProfiles_INCLUDE_DIR}") + endif() + + # Make sure VulkanHpp_CPPM_DIR is set + if(NOT DEFINED VulkanHpp_CPPM_DIR) + # Check if vulkan.cppm exists in the downloaded repository + if(DEFINED VulkanHpp_INCLUDE_DIR AND EXISTS "${VulkanHpp_INCLUDE_DIR}/vulkan/vulkan.cppm") + set(VulkanHpp_CPPM_DIR ${VulkanHpp_INCLUDE_DIR}) + message(STATUS "Found vulkan.cppm in VulkanHpp_INCLUDE_DIR: ${VulkanHpp_CPPM_DIR}") + elseif(DEFINED _vulkanhpp_source_dir AND EXISTS "${_vulkanhpp_source_dir}/vulkan/vulkan.cppm") + set(VulkanHpp_CPPM_DIR ${_vulkanhpp_source_dir}) + message(STATUS "Found vulkan.cppm in _vulkanhpp_source_dir: ${VulkanHpp_CPPM_DIR}") + elseif(DEFINED VulkanHpp_SOURCE_DIR AND EXISTS "${VulkanHpp_SOURCE_DIR}/vulkan/vulkan.cppm") + set(VulkanHpp_CPPM_DIR ${VulkanHpp_SOURCE_DIR}) + message(STATUS "Found vulkan.cppm in VulkanHpp_SOURCE_DIR: ${VulkanHpp_CPPM_DIR}") + elseif(DEFINED vulkanhpp_SOURCE_DIR AND EXISTS "${vulkanhpp_SOURCE_DIR}/vulkan/vulkan.cppm") + set(VulkanHpp_CPPM_DIR ${vulkanhpp_SOURCE_DIR}) + message(STATUS "Found vulkan.cppm in vulkanhpp_SOURCE_DIR: ${VulkanHpp_CPPM_DIR}") + else() + # If vulkan.cppm doesn't exist, we need to create it + set(VulkanHpp_CPPM_DIR ${CMAKE_CURRENT_BINARY_DIR}/VulkanHpp) + file(MAKE_DIRECTORY ${VulkanHpp_CPPM_DIR}/vulkan) + message(STATUS "Creating vulkan.cppm in ${VulkanHpp_CPPM_DIR}") + + # Create vulkan.cppm file + file(WRITE "${VulkanHpp_CPPM_DIR}/vulkan/vulkan.cppm" +"// Auto-generated vulkan.cppm file +module; +#include +export module vulkan; +export namespace vk { + using namespace VULKAN_HPP_NAMESPACE; +} +") + endif() + endif() + + message(STATUS "Final VulkanHpp_CPPM_DIR: ${VulkanHpp_CPPM_DIR}") +endif() + +mark_as_advanced(VulkanHpp_INCLUDE_DIR VulkanHpp_CPPM_DIR) diff --git a/CMake/Findglm.cmake b/CMake/Findglm.cmake new file mode 100644 index 0000000..fdf113c --- /dev/null +++ b/CMake/Findglm.cmake @@ -0,0 +1,133 @@ +# Findglm.cmake +# +# Finds the GLM library +# +# This will define the following variables +# +# glm_FOUND +# glm_INCLUDE_DIRS +# +# and the following imported targets +# +# glm::glm +# + +# Try to find the package using pkg-config first +find_package(PkgConfig QUIET) +if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_glm QUIET glm) +endif() + +# Find the include directory +find_path(glm_INCLUDE_DIR + NAMES glm/glm.hpp + PATHS + ${PC_glm_INCLUDE_DIRS} + /usr/include + /usr/local/include + $ENV{VULKAN_SDK}/include + ${ANDROID_NDK}/sources/third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/include + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../include + PATH_SUFFIXES glm +) + +# If the include directory wasn't found, use FetchContent to download and build +if(NOT glm_INCLUDE_DIR) + # If not found, use FetchContent to download and build + include(FetchContent) + + message(STATUS "GLM not found, fetching from GitHub...") + FetchContent_Declare( + glm + GIT_REPOSITORY https://github.com/g-truc/glm.git + GIT_TAG 0.9.9.8 # Use a specific tag for stability + ) + + # Define a function to update the CMake minimum required version + function(update_glm_cmake_version) + # Get the source directory + FetchContent_GetProperties(glm SOURCE_DIR glm_SOURCE_DIR) + + # Update the minimum required CMake version + file(READ "${glm_SOURCE_DIR}/CMakeLists.txt" GLM_CMAKE_CONTENT) + string(REPLACE "cmake_minimum_required(VERSION 3.2" + "cmake_minimum_required(VERSION 3.5" + GLM_CMAKE_CONTENT "${GLM_CMAKE_CONTENT}") + file(WRITE "${glm_SOURCE_DIR}/CMakeLists.txt" "${GLM_CMAKE_CONTENT}") + endfunction() + + # Set policy to suppress the deprecation warning + if(POLICY CMP0169) + cmake_policy(SET CMP0169 OLD) + endif() + + # First, declare and populate the content + FetchContent_GetProperties(glm) + if(NOT glm_POPULATED) + FetchContent_Populate(glm) + # Update the CMake version before making it available + update_glm_cmake_version() + endif() + + # Now make it available (this will process the CMakeLists.txt) + FetchContent_MakeAvailable(glm) + + # Get the include directory from the target + if(TARGET glm) + get_target_property(glm_INCLUDE_DIR glm INTERFACE_INCLUDE_DIRECTORIES) + if(NOT glm_INCLUDE_DIR) + # If we can't get the include directory from the target, use the source directory + set(glm_INCLUDE_DIR ${glm_SOURCE_DIR}) + endif() + else() + # GLM might not create a target, so use the source directory + set(glm_INCLUDE_DIR ${glm_SOURCE_DIR}) + endif() +endif() + +# Set the variables +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(glm + REQUIRED_VARS glm_INCLUDE_DIR +) + +if(glm_FOUND) + set(glm_INCLUDE_DIRS ${glm_INCLUDE_DIR}) + + # Create an imported target + if(NOT TARGET glm::glm) + add_library(glm::glm INTERFACE IMPORTED) + set_target_properties(glm::glm PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${glm_INCLUDE_DIRS}" + ) + endif() +elseif(TARGET glm) + # If find_package_handle_standard_args failed but we have a glm target from FetchContent + # Create an alias for the glm target + if(NOT TARGET glm::glm) + add_library(glm::glm ALIAS glm) + endif() + + # Set variables to indicate that glm was found + set(glm_FOUND TRUE) + set(GLM_FOUND TRUE) + + # Set include directories + get_target_property(glm_INCLUDE_DIR glm INTERFACE_INCLUDE_DIRECTORIES) + if(glm_INCLUDE_DIR) + set(glm_INCLUDE_DIRS ${glm_INCLUDE_DIR}) + else() + # If we can't get the include directory from the target, use the source directory + set(glm_INCLUDE_DIR ${glm_SOURCE_DIR}) + set(glm_INCLUDE_DIRS ${glm_INCLUDE_DIR}) + endif() +endif() + +mark_as_advanced(glm_INCLUDE_DIR) diff --git a/CMake/Findnlohmann_json.cmake b/CMake/Findnlohmann_json.cmake new file mode 100644 index 0000000..61dc66a --- /dev/null +++ b/CMake/Findnlohmann_json.cmake @@ -0,0 +1,154 @@ +# Findnlohmann_json.cmake +# +# Finds the nlohmann_json library +# +# This will define the following variables +# +# nlohmann_json_FOUND +# nlohmann_json_INCLUDE_DIRS +# +# and the following imported targets +# +# nlohmann_json::nlohmann_json +# + +# Try to find the package using pkg-config first +find_package(PkgConfig QUIET) +if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_nlohmann_json QUIET nlohmann_json) +endif() + +# Find the include directory +find_path(nlohmann_json_INCLUDE_DIR + NAMES nlohmann/json.hpp json.hpp + PATHS + ${PC_nlohmann_json_INCLUDE_DIRS} + /usr/include + /usr/local/include + $ENV{VULKAN_SDK}/include + ${ANDROID_NDK}/sources/third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/include + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../include + PATH_SUFFIXES nlohmann json +) + +# If the include directory wasn't found, use FetchContent to download and build +if(NOT nlohmann_json_INCLUDE_DIR) + # If not found, use FetchContent to download and build + include(FetchContent) + + message(STATUS "nlohmann_json not found, fetching from GitHub...") + FetchContent_Declare( + nlohmann_json + GIT_REPOSITORY https://github.com/nlohmann/json.git + GIT_TAG v3.12.0 # Use a specific tag for stability + ) + + # Set policy to suppress the deprecation warning + if(POLICY CMP0169) + cmake_policy(SET CMP0169 OLD) + endif() + + # Populate the content but don't configure it yet + FetchContent_GetProperties(nlohmann_json) + if(NOT nlohmann_json_POPULATED) + FetchContent_Populate(nlohmann_json) + + if(ANDROID) + # Update the minimum required CMake version before including the CMakeLists.txt + file(READ "${nlohmann_json_SOURCE_DIR}/CMakeLists.txt" NLOHMANN_JSON_CMAKE_CONTENT) + string(REPLACE "cmake_minimum_required(VERSION 3.1" + "cmake_minimum_required(VERSION 3.10" + NLOHMANN_JSON_CMAKE_CONTENT "${NLOHMANN_JSON_CMAKE_CONTENT}") + string(REPLACE "cmake_minimum_required(VERSION 3.2" + "cmake_minimum_required(VERSION 3.10" + NLOHMANN_JSON_CMAKE_CONTENT "${NLOHMANN_JSON_CMAKE_CONTENT}") + string(REPLACE "cmake_minimum_required(VERSION 3.3" + "cmake_minimum_required(VERSION 3.10" + NLOHMANN_JSON_CMAKE_CONTENT "${NLOHMANN_JSON_CMAKE_CONTENT}") + string(REPLACE "cmake_minimum_required(VERSION 3.4" + "cmake_minimum_required(VERSION 3.10" + NLOHMANN_JSON_CMAKE_CONTENT "${NLOHMANN_JSON_CMAKE_CONTENT}") + string(REPLACE "cmake_minimum_required(VERSION 3.5" + "cmake_minimum_required(VERSION 3.10" + NLOHMANN_JSON_CMAKE_CONTENT "${NLOHMANN_JSON_CMAKE_CONTENT}") + string(REPLACE "cmake_minimum_required(VERSION 3.6" + "cmake_minimum_required(VERSION 3.10" + NLOHMANN_JSON_CMAKE_CONTENT "${NLOHMANN_JSON_CMAKE_CONTENT}") + string(REPLACE "cmake_minimum_required(VERSION 3.7" + "cmake_minimum_required(VERSION 3.10" + NLOHMANN_JSON_CMAKE_CONTENT "${NLOHMANN_JSON_CMAKE_CONTENT}") + string(REPLACE "cmake_minimum_required(VERSION 3.8" + "cmake_minimum_required(VERSION 3.10" + NLOHMANN_JSON_CMAKE_CONTENT "${NLOHMANN_JSON_CMAKE_CONTENT}") + string(REPLACE "cmake_minimum_required(VERSION 3.9" + "cmake_minimum_required(VERSION 3.10" + NLOHMANN_JSON_CMAKE_CONTENT "${NLOHMANN_JSON_CMAKE_CONTENT}") + file(WRITE "${nlohmann_json_SOURCE_DIR}/CMakeLists.txt" "${NLOHMANN_JSON_CMAKE_CONTENT}") + endif() + + # Now add the subdirectory manually + add_subdirectory(${nlohmann_json_SOURCE_DIR} ${nlohmann_json_BINARY_DIR}) + else() + # If already populated, just make it available + FetchContent_MakeAvailable(nlohmann_json) + endif() + + # Get the include directory from the target + if(TARGET nlohmann_json) + get_target_property(nlohmann_json_INCLUDE_DIR nlohmann_json INTERFACE_INCLUDE_DIRECTORIES) + if(NOT nlohmann_json_INCLUDE_DIR) + # If we can't get the include directory from the target, use the source directory + set(nlohmann_json_INCLUDE_DIR ${nlohmann_json_SOURCE_DIR}/include) + endif() + else() + # nlohmann_json might not create a target, so use the source directory + set(nlohmann_json_INCLUDE_DIR ${nlohmann_json_SOURCE_DIR}/include) + endif() +endif() + +# Set the variables +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(nlohmann_json + REQUIRED_VARS nlohmann_json_INCLUDE_DIR +) + +if(nlohmann_json_FOUND) + set(nlohmann_json_INCLUDE_DIRS ${nlohmann_json_INCLUDE_DIR}) + + # Create an imported target + if(NOT TARGET nlohmann_json::nlohmann_json) + add_library(nlohmann_json::nlohmann_json INTERFACE IMPORTED) + set_target_properties(nlohmann_json::nlohmann_json PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${nlohmann_json_INCLUDE_DIRS}" + ) + endif() +elseif(TARGET nlohmann_json) + # If find_package_handle_standard_args failed but we have a nlohmann_json target from FetchContent + # Create an alias for the nlohmann_json target + if(NOT TARGET nlohmann_json::nlohmann_json) + add_library(nlohmann_json::nlohmann_json ALIAS nlohmann_json) + endif() + + # Set variables to indicate that nlohmann_json was found + set(nlohmann_json_FOUND TRUE) + set(NLOHMANN_JSON_FOUND TRUE) + + # Set include directories + get_target_property(nlohmann_json_INCLUDE_DIR nlohmann_json INTERFACE_INCLUDE_DIRECTORIES) + if(nlohmann_json_INCLUDE_DIR) + set(nlohmann_json_INCLUDE_DIRS ${nlohmann_json_INCLUDE_DIR}) + else() + # If we can't get the include directory from the target, use the source directory + set(nlohmann_json_INCLUDE_DIR ${nlohmann_json_SOURCE_DIR}/include) + set(nlohmann_json_INCLUDE_DIRS ${nlohmann_json_INCLUDE_DIR}) + endif() +endif() + +mark_as_advanced(nlohmann_json_INCLUDE_DIR) diff --git a/CMake/Findstb.cmake b/CMake/Findstb.cmake new file mode 100644 index 0000000..6ccf72f --- /dev/null +++ b/CMake/Findstb.cmake @@ -0,0 +1,86 @@ +# Findstb.cmake +# +# Finds the stb library (specifically stb_image.h) +# +# This will define the following variables +# +# stb_FOUND +# stb_INCLUDE_DIRS +# +# and the following imported targets +# +# stb::stb +# + +# Try to find the package using pkg-config first +find_package(PkgConfig QUIET) +if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_stb QUIET stb) +endif() + +# Find the include directory +find_path(stb_INCLUDE_DIR + NAMES stb_image.h + PATHS + ${PC_stb_INCLUDE_DIRS} + /usr/include + /usr/local/include + $ENV{VULKAN_SDK}/include + ${ANDROID_NDK}/sources/third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/include + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../include + PATH_SUFFIXES stb +) + +# If the include directory wasn't found, use FetchContent to download and build +if(NOT stb_INCLUDE_DIR) + # If not found, use FetchContent to download and build + include(FetchContent) + + message(STATUS "stb_image.h not found, fetching from GitHub...") + FetchContent_Declare( + stb + GIT_REPOSITORY https://github.com/nothings/stb.git + GIT_TAG master # stb doesn't use version tags, so we use master + ) + + # Set policy to suppress the deprecation warning + if(POLICY CMP0169) + cmake_policy(SET CMP0169 OLD) + endif() + + # Populate the content + FetchContent_GetProperties(stb) + if(NOT stb_POPULATED) + FetchContent_Populate(stb) + endif() + + # stb is a header-only library with no CMakeLists.txt, so we just need to set the include directory + set(stb_INCLUDE_DIR ${stb_SOURCE_DIR}) +endif() + +# Set the variables +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(stb + REQUIRED_VARS stb_INCLUDE_DIR +) + +if(stb_FOUND) + set(stb_INCLUDE_DIRS ${stb_INCLUDE_DIR}) + + # Create an imported target + if(NOT TARGET stb::stb) + add_library(stb::stb INTERFACE IMPORTED) + set_target_properties(stb::stb PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${stb_INCLUDE_DIRS}" + ) + endif() +endif() + +mark_as_advanced(stb_INCLUDE_DIR) \ No newline at end of file diff --git a/CMake/Findtinygltf.cmake b/CMake/Findtinygltf.cmake new file mode 100644 index 0000000..b241235 --- /dev/null +++ b/CMake/Findtinygltf.cmake @@ -0,0 +1,162 @@ +# Findtinygltf.cmake +# +# Finds the tinygltf library +# +# This will define the following variables +# +# tinygltf_FOUND +# tinygltf_INCLUDE_DIRS +# +# and the following imported targets +# +# tinygltf::tinygltf +# + +# First, try to find nlohmann_json +find_package(nlohmann_json QUIET) +if(NOT nlohmann_json_FOUND) + include(FetchContent) + message(STATUS "nlohmann_json not found, fetching v3.12.0 from GitHub...") + FetchContent_Declare( + nlohmann_json + GIT_REPOSITORY https://github.com/nlohmann/json.git + GIT_TAG v3.12.0 # Use a specific tag for stability + ) + FetchContent_MakeAvailable(nlohmann_json) +endif() + +# Try to find tinygltf using standard find_package +find_path(tinygltf_INCLUDE_DIR + NAMES tiny_gltf.h + PATHS + ${PC_tinygltf_INCLUDE_DIRS} + /usr/include + /usr/local/include + $ENV{VULKAN_SDK}/include + ${ANDROID_NDK}/sources/third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/include + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../include + PATH_SUFFIXES tinygltf include +) + +# If not found, use FetchContent to download and build +if(NOT tinygltf_INCLUDE_DIR) + # If not found, use FetchContent to download and build + include(FetchContent) + + message(STATUS "tinygltf not found, fetching from GitHub...") + FetchContent_Declare( + tinygltf + GIT_REPOSITORY https://github.com/syoyo/tinygltf.git + GIT_TAG v2.8.18 # Use a specific tag for stability + ) + + # Set policy to suppress the deprecation warning + if(POLICY CMP0169) + cmake_policy(SET CMP0169 OLD) + endif() + + # Populate the content but don't configure it yet + FetchContent_GetProperties(tinygltf) + if(NOT tinygltf_POPULATED) + FetchContent_Populate(tinygltf) + + # Update the minimum required CMake version to avoid deprecation warning + file(READ "${tinygltf_SOURCE_DIR}/CMakeLists.txt" TINYGLTF_CMAKE_CONTENT) + string(REPLACE "cmake_minimum_required(VERSION 3.6)" + "cmake_minimum_required(VERSION 3.10)" + TINYGLTF_CMAKE_CONTENT "${TINYGLTF_CMAKE_CONTENT}") + file(WRITE "${tinygltf_SOURCE_DIR}/CMakeLists.txt" "${TINYGLTF_CMAKE_CONTENT}") + + # Create a symbolic link to make nlohmann/json.hpp available + if(EXISTS "${tinygltf_SOURCE_DIR}/json.hpp") + file(MAKE_DIRECTORY "${tinygltf_SOURCE_DIR}/nlohmann") + file(CREATE_LINK "${tinygltf_SOURCE_DIR}/json.hpp" "${tinygltf_SOURCE_DIR}/nlohmann/json.hpp" SYMBOLIC) + endif() + + # Set tinygltf to header-only mode + set(TINYGLTF_HEADER_ONLY ON CACHE BOOL "Use header only version" FORCE) + set(TINYGLTF_INSTALL OFF CACHE BOOL "Do not install tinygltf" FORCE) + + # Add the subdirectory after modifying the CMakeLists.txt + add_subdirectory(${tinygltf_SOURCE_DIR} ${tinygltf_BINARY_DIR}) + else() + # If already populated, just make it available + FetchContent_MakeAvailable(tinygltf) + endif() + + # Get the include directory from the target + get_target_property(tinygltf_INCLUDE_DIR tinygltf INTERFACE_INCLUDE_DIRECTORIES) + if(NOT tinygltf_INCLUDE_DIR) + # If we can't get the include directory from the target, use the source directory + FetchContent_GetProperties(tinygltf SOURCE_DIR tinygltf_SOURCE_DIR) + set(tinygltf_INCLUDE_DIR ${tinygltf_SOURCE_DIR}) + endif() +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(tinygltf + REQUIRED_VARS tinygltf_INCLUDE_DIR +) + +if(tinygltf_FOUND) + set(tinygltf_INCLUDE_DIRS ${tinygltf_INCLUDE_DIR}) + + if(NOT TARGET tinygltf::tinygltf) + add_library(tinygltf::tinygltf INTERFACE IMPORTED) + set_target_properties(tinygltf::tinygltf PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${tinygltf_INCLUDE_DIRS}" + INTERFACE_COMPILE_DEFINITIONS "TINYGLTF_IMPLEMENTATION;TINYGLTF_NO_EXTERNAL_IMAGE;TINYGLTF_NO_STB_IMAGE;TINYGLTF_NO_STB_IMAGE_WRITE" + ) + if(TARGET nlohmann_json::nlohmann_json) + target_link_libraries(tinygltf::tinygltf INTERFACE nlohmann_json::nlohmann_json) + endif() + endif() +elseif(TARGET tinygltf) + # If find_package_handle_standard_args failed but we have a tinygltf target from FetchContent + # Create an alias for the tinygltf target + if(NOT TARGET tinygltf::tinygltf) + add_library(tinygltf_wrapper INTERFACE) + target_link_libraries(tinygltf_wrapper INTERFACE tinygltf) + target_compile_definitions(tinygltf_wrapper INTERFACE + TINYGLTF_IMPLEMENTATION + TINYGLTF_NO_EXTERNAL_IMAGE + TINYGLTF_NO_STB_IMAGE + TINYGLTF_NO_STB_IMAGE_WRITE + ) + if(TARGET nlohmann_json::nlohmann_json) + target_link_libraries(tinygltf_wrapper INTERFACE nlohmann_json::nlohmann_json) + endif() + add_library(tinygltf::tinygltf ALIAS tinygltf_wrapper) + endif() + + # Set variables to indicate that tinygltf was found + set(tinygltf_FOUND TRUE) + set(TINYGLTF_FOUND TRUE) + + # Set include directories + get_target_property(tinygltf_INCLUDE_DIR tinygltf INTERFACE_INCLUDE_DIRECTORIES) + if(tinygltf_INCLUDE_DIR) + set(tinygltf_INCLUDE_DIRS ${tinygltf_INCLUDE_DIR}) + else() + # If we can't get the include directory from the target, use the source directory + FetchContent_GetProperties(tinygltf SOURCE_DIR tinygltf_SOURCE_DIR) + set(tinygltf_INCLUDE_DIR ${tinygltf_SOURCE_DIR}) + set(tinygltf_INCLUDE_DIRS ${tinygltf_INCLUDE_DIR}) + + # Explicitly set the include directory on the target + if(TARGET tinygltf) + set_target_properties(tinygltf PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${tinygltf_INCLUDE_DIR}" + ) + endif() +endif() +endif() + +mark_as_advanced(tinygltf_INCLUDE_DIR) diff --git a/CMake/Findtinyobjloader.cmake b/CMake/Findtinyobjloader.cmake new file mode 100644 index 0000000..4b1fb44 --- /dev/null +++ b/CMake/Findtinyobjloader.cmake @@ -0,0 +1,160 @@ +# Findtinyobjloader.cmake +# Find the tinyobjloader library +# +# This module defines the following variables: +# tinyobjloader_FOUND - True if tinyobjloader was found +# tinyobjloader_INCLUDE_DIRS - Include directories for tinyobjloader +# tinyobjloader_LIBRARIES - Libraries to link against tinyobjloader +# +# It also defines the following targets: +# tinyobjloader::tinyobjloader + +# Try to find the package using pkg-config first +find_package(PkgConfig QUIET) +if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_tinyobjloader QUIET tinyobjloader) +endif() + +# Find the include directory +find_path(tinyobjloader_INCLUDE_DIR + NAMES tiny_obj_loader.h + PATHS + ${PC_tinyobjloader_INCLUDE_DIRS} + /usr/include + /usr/local/include + $ENV{VULKAN_SDK}/include + ${ANDROID_NDK}/sources/third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/include + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../include + PATH_SUFFIXES tinyobjloader tiny_obj_loader +) + +# Find the library +find_library(tinyobjloader_LIBRARY + NAMES tinyobjloader + PATHS + ${PC_tinyobjloader_LIBRARY_DIRS} + /usr/lib + /usr/local/lib + $ENV{VULKAN_SDK}/lib + ${ANDROID_NDK}/sources/third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/lib + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../lib + PATH_SUFFIXES lib +) + +# If the include directory wasn't found, use FetchContent to download and build +if(NOT tinyobjloader_INCLUDE_DIR) + # If not found, use FetchContent to download and build + include(FetchContent) + + message(STATUS "tinyobjloader not found, fetching from GitHub...") + FetchContent_Declare( + tinyobjloader + GIT_REPOSITORY https://github.com/tinyobjloader/tinyobjloader.git + GIT_TAG v2.0.0rc10 # Use a specific tag for stability + ) + + # Set options before making tinyobjloader available + set(TINYOBJLOADER_BUILD_TEST_LOADER OFF CACHE BOOL "Do not build test loader" FORCE) + set(TINYOBJLOADER_BUILD_OBJ_STICHER OFF CACHE BOOL "Do not build obj sticher" FORCE) + set(TINYOBJLOADER_INSTALL OFF CACHE BOOL "Do not install tinyobjloader" FORCE) + + # Update CMake policy to suppress the deprecation warning + if(POLICY CMP0169) + cmake_policy(SET CMP0169 OLD) + endif() + + # Populate the content but don't configure it yet + FetchContent_GetProperties(tinyobjloader) + if(NOT tinyobjloader_POPULATED) + FetchContent_Populate(tinyobjloader) + + # Update the minimum required CMake version before including the CMakeLists.txt + file(READ "${tinyobjloader_SOURCE_DIR}/CMakeLists.txt" TINYOBJLOADER_CMAKE_CONTENT) + string(REPLACE "cmake_minimum_required(VERSION 3.2)" + "cmake_minimum_required(VERSION 3.10)" + TINYOBJLOADER_CMAKE_CONTENT "${TINYOBJLOADER_CMAKE_CONTENT}") + string(REPLACE "cmake_minimum_required(VERSION 3.5)" + "cmake_minimum_required(VERSION 3.10)" + TINYOBJLOADER_CMAKE_CONTENT "${TINYOBJLOADER_CMAKE_CONTENT}") + file(WRITE "${tinyobjloader_SOURCE_DIR}/CMakeLists.txt" "${TINYOBJLOADER_CMAKE_CONTENT}") + + # Now add the subdirectory manually + add_subdirectory(${tinyobjloader_SOURCE_DIR} ${tinyobjloader_BINARY_DIR}) + else() + # If already populated, just make it available + FetchContent_MakeAvailable(tinyobjloader) + endif() + + # Get the include directory from the target + get_target_property(tinyobjloader_INCLUDE_DIR tinyobjloader INTERFACE_INCLUDE_DIRECTORIES) + if(NOT tinyobjloader_INCLUDE_DIR) + # If we can't get the include directory from the target, use the source directory + set(tinyobjloader_INCLUDE_DIR ${tinyobjloader_SOURCE_DIR}) + endif() +endif() + +# Set the variables +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(tinyobjloader + REQUIRED_VARS tinyobjloader_INCLUDE_DIR +) + +if(tinyobjloader_FOUND) + set(tinyobjloader_INCLUDE_DIRS ${tinyobjloader_INCLUDE_DIR}) + + if(tinyobjloader_LIBRARY) + set(tinyobjloader_LIBRARIES ${tinyobjloader_LIBRARY}) + else() + # tinyobjloader is a header-only library, so no library is needed + set(tinyobjloader_LIBRARIES "") + endif() + + # Create an imported target + if(NOT TARGET tinyobjloader::tinyobjloader) + add_library(tinyobjloader::tinyobjloader INTERFACE IMPORTED) + set_target_properties(tinyobjloader::tinyobjloader PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${tinyobjloader_INCLUDE_DIRS}" + ) + if(tinyobjloader_LIBRARIES) + set_target_properties(tinyobjloader::tinyobjloader PROPERTIES + INTERFACE_LINK_LIBRARIES "${tinyobjloader_LIBRARIES}" + ) + endif() + endif() +elseif(TARGET tinyobjloader) + # If find_package_handle_standard_args failed but we have a tinyobjloader target from FetchContent + # Create an alias for the tinyobjloader target + if(NOT TARGET tinyobjloader::tinyobjloader) + add_library(tinyobjloader::tinyobjloader ALIAS tinyobjloader) + endif() + + # Set variables to indicate that tinyobjloader was found + set(tinyobjloader_FOUND TRUE) + set(TINYOBJLOADER_FOUND TRUE) + + # Set include directories + get_target_property(tinyobjloader_INCLUDE_DIR tinyobjloader INTERFACE_INCLUDE_DIRECTORIES) + if(tinyobjloader_INCLUDE_DIR) + set(tinyobjloader_INCLUDE_DIRS ${tinyobjloader_INCLUDE_DIR}) + else() + # If we can't get the include directory from the target, use the source directory + set(tinyobjloader_INCLUDE_DIR ${tinyobjloader_SOURCE_DIR}) + set(tinyobjloader_INCLUDE_DIRS ${tinyobjloader_INCLUDE_DIR}) + endif() +endif() + +mark_as_advanced(tinyobjloader_INCLUDE_DIR tinyobjloader_LIBRARY) diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..aabff81 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,324 @@ +cmake_minimum_required (VERSION 3.29) + +project (VulkanTutorial) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/CMake") + +# Add option to enable/disable C++ 20 module +option(ENABLE_CPP20_MODULE "Enable C++ 20 module support for Vulkan" OFF) + +# Enable C++ module dependency scanning only if C++ 20 module is enabled +if(ENABLE_CPP20_MODULE) + set(CMAKE_CXX_SCAN_FOR_MODULES ON) +endif() + +find_package (glfw3 REQUIRED) +find_package (glm REQUIRED) +find_package (Vulkan REQUIRED) +find_package (tinyobjloader REQUIRED) +find_package (tinygltf REQUIRED) +find_package (KTX REQUIRED) + +# set up Vulkan C++ module only if enabled +if(ENABLE_CPP20_MODULE) + add_library(VulkanCppModule) + add_library(Vulkan::cppm ALIAS VulkanCppModule) + + target_compile_definitions(VulkanCppModule + PUBLIC VULKAN_HPP_DISPATCH_LOADER_DYNAMIC=1 VULKAN_HPP_NO_STRUCT_CONSTRUCTORS=1 + ) + target_include_directories(VulkanCppModule + PUBLIC + "${Vulkan_INCLUDE_DIR}" + ) + target_link_libraries(VulkanCppModule + PUBLIC + Vulkan::Vulkan + ) + + set_target_properties(VulkanCppModule PROPERTIES CXX_STANDARD 20) + + # Add MSVC-specific compiler options for proper C++ module support + if(MSVC) + target_compile_options(VulkanCppModule PRIVATE + /std:c++latest # Use latest C++ standard for better module support + /permissive- # Standards conformance mode + /Zc:__cplusplus # Enable correct __cplusplus macro + /EHsc # Enable C++ exception handling + /Zc:preprocessor # Use conforming preprocessor + /translateInclude # Automatically translate #include to import for standard library + ) + endif() + + target_sources(VulkanCppModule + PUBLIC + FILE_SET cxx_modules TYPE CXX_MODULES + BASE_DIRS + "${Vulkan_INCLUDE_DIR}" + FILES + "${Vulkan_INCLUDE_DIR}/vulkan/vulkan.cppm" + ) + + + # Add the vulkan.cppm file directly as a source file + target_sources(VulkanCppModule + PRIVATE + "${Vulkan_INCLUDE_DIR}/vulkan/vulkan.cppm" + ) +else() + # Create a dummy interface library when C++ 20 module is disabled + add_library(VulkanCppModule INTERFACE) + add_library(Vulkan::cppm ALIAS VulkanCppModule) + target_link_libraries(VulkanCppModule INTERFACE Vulkan::Vulkan) + target_compile_definitions(VulkanCppModule + INTERFACE VULKAN_HPP_DISPATCH_LOADER_DYNAMIC=1 VULKAN_HPP_NO_STRUCT_CONSTRUCTORS=1 + ) +endif() + +find_package(stb REQUIRED) +set(STB_INCLUDEDIR ${stb_INCLUDE_DIRS}) + +add_executable (glslang::validator IMPORTED) +find_program (GLSLANG_VALIDATOR "glslangValidator" HINTS $ENV{VULKAN_SDK}/bin REQUIRED) +set_property (TARGET glslang::validator PROPERTY IMPORTED_LOCATION "${GLSLANG_VALIDATOR}") +find_program(SLANGC_EXECUTABLE slangc HINTS $ENV{VULKAN_SDK}/bin REQUIRED) + +function (add_shaders_target TARGET) + cmake_parse_arguments ("SHADER" "" "CHAPTER_NAME" "SOURCES" ${ARGN}) + set (SHADERS_DIR shaders) + add_custom_command ( + OUTPUT ${SHADERS_DIR} + COMMAND ${CMAKE_COMMAND} -E make_directory ${SHADERS_DIR} + ) + add_custom_command ( + OUTPUT ${SHADERS_DIR}/frag.spv ${SHADERS_DIR}/vert.spv + COMMAND glslang::validator + ARGS --target-env vulkan1.0 ${SHADER_SOURCES} --quiet + WORKING_DIRECTORY ${SHADERS_DIR} + DEPENDS ${SHADERS_DIR} ${SHADER_SOURCES} + COMMENT "Compiling Shaders" + VERBATIM + ) + add_custom_target (${TARGET} DEPENDS ${SHADERS_DIR}/frag.spv ${SHADERS_DIR}/vert.spv) +endfunction () + +function (add_slang_shader_target TARGET) + cmake_parse_arguments ("SHADER" "" "CHAPTER_NAME" "SOURCES" ${ARGN}) + set (SHADERS_DIR ${CHAPTER_NAME}/shaders) + file(GLOB HAS_COMPUTE shaders/${CHAPTER_SHADER}.comp) + set (ENTRY_POINTS -entry vertMain -entry fragMain) + if(HAS_COMPUTE) + list(APPEND ENTRY_POINTS -entry compMain) + endif() + add_custom_command ( + OUTPUT ${SHADERS_DIR} + COMMAND ${CMAKE_COMMAND} -E make_directory ${SHADERS_DIR} + ) + add_custom_command ( + OUTPUT ${SHADERS_DIR}/${CHAPTER_SHADER}.spv + COMMAND ${SLANGC_EXECUTABLE} ${SHADER_SOURCES} -target spirv -profile spirv_1_4+spvRayQueryKHR -emit-spirv-directly -fvk-use-entrypoint-name ${ENTRY_POINTS} -o ${CHAPTER_SHADER}.spv + WORKING_DIRECTORY ${SHADERS_DIR} + DEPENDS ${SHADERS_DIR} ${SHADER_SOURCES} + COMMENT "Compiling Slang Shaders" + VERBATIM + ) + add_custom_target (${TARGET} DEPENDS ${SHADERS_DIR}/${CHAPTER_SHADER}.spv) +endfunction() + +function (add_chapter CHAPTER_NAME) + cmake_parse_arguments (CHAPTER "" "SHADER" "LIBS;TEXTURES;MODELS" ${ARGN}) + add_executable (${CHAPTER_NAME} ${CHAPTER_NAME}.cpp) + set_target_properties (${CHAPTER_NAME} PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CHAPTER_NAME}) + set_target_properties (${CHAPTER_NAME} PROPERTIES CXX_STANDARD 20) + target_link_libraries (${CHAPTER_NAME} Vulkan::cppm glfw) + target_include_directories (${CHAPTER_NAME} PRIVATE ${STB_INCLUDEDIR}) + + # Add compile definition if C++ 20 module is enabled + if(ENABLE_CPP20_MODULE) + target_compile_definitions(${CHAPTER_NAME} PRIVATE USE_CPP20_MODULES=1) + endif() + + if(WIN32) + if(${CMAKE_GENERATOR} MATCHES "Visual Studio.*") + set_target_properties(${CHAPTER_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/${CHAPTER_NAME}") + endif() + endif() + + if (DEFINED CHAPTER_SHADER) + set (CHAPTER_SHADER_TARGET ${CHAPTER_NAME}_shader) + file (GLOB SHADER_SOURCES shaders/${CHAPTER_SHADER}.frag shaders/${CHAPTER_SHADER}.vert shaders/${CHAPTER_SHADER}.comp) + if(SHADER_SOURCES) + add_shaders_target (${CHAPTER_SHADER_TARGET} CHAPTER_NAME ${CHAPTER_NAME} SOURCES ${SHADER_SOURCES}) + add_dependencies (${CHAPTER_NAME} ${CHAPTER_SHADER_TARGET}) + endif() + + set (CHAPTER_SHADER_SLANG_TARGET ${CHAPTER_NAME}_slang_shader) + file (GLOB SHADER_SLANG_SOURCES shaders/${CHAPTER_SHADER}.slang) + if(SHADER_SLANG_SOURCES) + add_slang_shader_target( ${CHAPTER_SHADER_SLANG_TARGET} CHAPTER_NAME ${CHAPTER_NAME} SOURCES ${SHADER_SLANG_SOURCES}) + add_dependencies(${CHAPTER_NAME} ${CHAPTER_SHADER_SLANG_TARGET}) + endif() + endif () + if (DEFINED CHAPTER_LIBS) + target_link_libraries (${CHAPTER_NAME} ${CHAPTER_LIBS}) + endif () + if (DEFINED CHAPTER_MODELS) + list(TRANSFORM CHAPTER_MODELS PREPEND "${CMAKE_SOURCE_DIR}/assets/") + file (COPY ${CHAPTER_MODELS} DESTINATION ${CMAKE_BINARY_DIR}/${CHAPTER_NAME}/models) + endif () + if (DEFINED CHAPTER_TEXTURES) + list(TRANSFORM CHAPTER_TEXTURES PREPEND "${CMAKE_SOURCE_DIR}/assets/") + file (COPY ${CHAPTER_TEXTURES} DESTINATION ${CMAKE_BINARY_DIR}/${CHAPTER_NAME}/textures) + endif () +endfunction () + +add_chapter (00_base_code) + +add_chapter (01_instance_creation) + +add_chapter (02_validation_layers) + +add_chapter (03_physical_device_selection) + +add_chapter (04_logical_device) + +add_chapter (05_window_surface) + +add_chapter (06_swap_chain_creation) + +add_chapter (07_image_views) + +add_chapter (08_graphics_pipeline) + +add_chapter (09_shader_modules + SHADER 09_shader_base) + +# add_chapter (10_fixed_functions +# SHADER 09_shader_base) +# +# add_chapter (12_graphics_pipeline_complete +# SHADER 09_shader_base) +# +# add_chapter (14_command_buffers +# SHADER 09_shader_base) +# +# add_chapter (15_hello_triangle +# SHADER 09_shader_base) +# +# add_chapter (16_frames_in_flight +# SHADER 09_shader_base) +# +# add_chapter (17_swap_chain_recreation +# SHADER 09_shader_base) +# +# add_chapter (18_vertex_input +# SHADER 18_shader_vertexbuffer +# LIBS glm::glm) +# +# add_chapter (19_vertex_buffer +# SHADER 18_shader_vertexbuffer +# LIBS glm::glm) +# +# add_chapter (20_staging_buffer +# SHADER 18_shader_vertexbuffer +# LIBS glm::glm) +# +# add_chapter (21_index_buffer +# SHADER 18_shader_vertexbuffer +# LIBS glm::glm) +# +# add_chapter (22_descriptor_layout +# SHADER 22_shader_ubo +# LIBS glm::glm) +# +# add_chapter (23_descriptor_sets +# SHADER 22_shader_ubo +# LIBS glm::glm) +# +# add_chapter (24_texture_image +# SHADER 22_shader_ubo +# TEXTURES ../../images/texture.jpg +# LIBS glm::glm) +# +# add_chapter (25_sampler +# SHADER 22_shader_ubo +# TEXTURES ../../images/texture.jpg +# LIBS glm::glm) +# +# add_chapter (26_texture_mapping +# SHADER 26_shader_textures +# TEXTURES ../../images/texture.jpg +# LIBS glm::glm) +# +# add_chapter (27_depth_buffering +# SHADER 27_shader_depth +# TEXTURES ../../images/texture.jpg +# LIBS glm::glm) +# +# add_chapter (28_model_loading +# SHADER 27_shader_depth +# MODELS viking_room.obj +# TEXTURES viking_room.png +# LIBS glm::glm tinyobjloader::tinyobjloader) +# +# add_chapter (29_mipmapping +# SHADER 27_shader_depth +# MODELS viking_room.obj +# TEXTURES viking_room.png +# LIBS glm::glm tinyobjloader::tinyobjloader) +# +# add_chapter (30_multisampling +# SHADER 27_shader_depth +# MODELS viking_room.obj +# TEXTURES viking_room.png +# LIBS glm::glm tinyobjloader::tinyobjloader) +# +# add_chapter (31_compute_shader +# SHADER 31_shader_compute +# LIBS glm::glm) +# +# add_chapter (32_ecosystem_utilities +# SHADER 27_shader_depth +# MODELS viking_room.obj +# TEXTURES viking_room.png +# LIBS glm::glm tinyobjloader::tinyobjloader) +# +# add_chapter (33_vulkan_profiles +# SHADER 27_shader_depth +# MODELS viking_room.obj +# TEXTURES viking_room.png +# LIBS glm::glm tinyobjloader::tinyobjloader) +# +# add_chapter (34_android +# SHADER 27_shader_depth +# MODELS viking_room.obj +# TEXTURES viking_room.png +# LIBS glm::glm tinyobjloader::tinyobjloader) +# +# add_chapter (35_gltf_ktx +# SHADER 27_shader_depth +# MODELS viking_room.glb +# TEXTURES viking_room.ktx2 +# LIBS glm::glm tinygltf::tinygltf KTX::ktx) +# +# add_chapter (36_multiple_objects +# SHADER 27_shader_depth +# MODELS viking_room.glb +# TEXTURES viking_room.ktx2 +# LIBS glm::glm tinygltf::tinygltf KTX::ktx) +# +# add_chapter (37_multithreading +# SHADER 37_shader_compute +# LIBS glm::glm) +# +# add_chapter (38_ray_tracing +# SHADER 38_ray_tracing +# MODELS plant_on_table.obj +# MODELS plant_on_table.mtl +# TEXTURES plant_on_table_textures/nettle_plant_diff_4k.png +# TEXTURES plant_on_table_textures/potted_plant_02_pot_diff_1k.png +# TEXTURES plant_on_table_textures/wooden_picnic_table_bottom_diff_1k.png +# TEXTURES plant_on_table_textures/wooden_picnic_table_top_diff_1k.png +# LIBS glm::glm tinyobjloader::tinyobjloader) diff --git a/build b/build new file mode 100755 index 0000000..e207c5f --- /dev/null +++ b/build @@ -0,0 +1,6 @@ +#!/bin/bash + +CMAKE_BUILD_DIR=cmake-build + +cmake -S . -B $CMAKE_BUILD_DIR -G Ninja +cmake --build $CMAKE_BUILD_DIR diff --git a/incremental_patch.sh b/incremental_patch.sh new file mode 100755 index 0000000..6255af8 --- /dev/null +++ b/incremental_patch.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +# Check if both a starting file and patch are provided +if [ $# != 2 ]; then + echo "usage: " + echo "specified patch will be applied to first_file.cpp and every code file larger than it (from later chapters)" + exit 1 +fi + +# Iterate over code files in order of increasing size +# i.e. in order of chapters (every chapter adds code) +apply_patch=false + +for f in `ls -Sr *.cpp` +do + # Apply patch on every code file including and after initial one + if [ $f = $1 ] || [ $apply_patch = true ]; then + apply_patch=true + + patch -f $f < $2 | grep -q "FAILED" > /dev/null + if [ $? = 0 ]; then + echo "failed to apply patch to $f" + exit 1 + fi + + rm -f *.orig + fi +done + +echo "patch successfully applied to all files" +exit 0 diff --git a/run b/run new file mode 100755 index 0000000..3e3a26c --- /dev/null +++ b/run @@ -0,0 +1,5 @@ +#!/bin/bash + +TARGET="$1" +cd cmake-build/$TARGET +./$TARGET diff --git a/shaders/09_shader_base.slang b/shaders/09_shader_base.slang new file mode 100644 index 0000000..ce75d26 --- /dev/null +++ b/shaders/09_shader_base.slang @@ -0,0 +1,29 @@ +static float2 positions[3] = float2[]( + float2( 0.0, -0.5), + float2( 0.5, 0.5), + float2(-0.5, 0.5), +); + +static float3 colors[3] = float3[]( + float3(1.0, 0.0, 0.0), + float3(0.0, 1.0, 0.0), + float3(0.0, 0.0, 1.0), +); + +struct VertexOutput { + float3 color; + float4 sv_position : SV_Position; +}; + +[shader("vertex")] +VertexOutput vertMain(uint vid : SV_VertexID) { + VertexOutput output; + output.color = colors[vid]; + output.sv_position = float4(positions[vid], 0.0, 1.0); + return output; +} + +[shader("fragment")] +float4 fragMain(VertexOutput inVert) : SV_Target { + return float4(inVert.color, 1.0); +}