From 14e266f6c29022c8f36e50d5f41541c81f61a8f2 Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Wed, 6 Aug 2025 21:57:07 +0200 Subject: [PATCH 1/6] Add clang format from samples repo Format compute shader chapter as an example --- attachments/.clang-format | 111 ++ attachments/31_compute_shader.cpp | 1872 +++++++++++++++-------------- 2 files changed, 1067 insertions(+), 916 deletions(-) create mode 100644 attachments/.clang-format diff --git a/attachments/.clang-format b/attachments/.clang-format new file mode 100644 index 00000000..109e0b7f --- /dev/null +++ b/attachments/.clang-format @@ -0,0 +1,111 @@ +--- +Language: Cpp +# BasedOnStyle: LLVM +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: true +AlignConsecutiveDeclarations: true +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: true +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterCaseLabel: true + AfterClass: true + AfterControlStatement: true + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: true + AfterStruct: true + AfterUnion: true + AfterExternBlock: true + BeforeCatch: true + BeforeElse: true + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: false +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +BreakBeforeInheritanceComma: false +BreakBeforeTernaryOperators: false +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: AfterColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 0 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Preserve +IncludeIsMainRegex: '(Test)?$' +IndentCaseLabels: true +IndentPPDirectives: AfterHash +IndentWidth: 4 +IndentWrappedFunctionNames: true +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Right +ReflowComments: true +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: true +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 8 +SpacesInAngles: false +SpacesInContainerLiterals: false +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +TabWidth: 4 +UseTab: ForIndentation +--- +Language: ObjC +DisableFormat: true +... diff --git a/attachments/31_compute_shader.cpp b/attachments/31_compute_shader.cpp index 5cfafd85..d394cbde 100644 --- a/attachments/31_compute_shader.cpp +++ b/attachments/31_compute_shader.cpp @@ -1,44 +1,43 @@ // Sample by Sascha Willems // Contact: webmaster@saschawillems.de -#include -#include -#include -#include -#include -#include -#include #include -#include #include #include +#include +#include +#include +#include +#include +#include #include +#include +#include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include #define GLM_FORCE_RADIANS #include #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -constexpr uint64_t FenceTimeout = 100000000; +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; +constexpr uint64_t FenceTimeout = 100000000; constexpr uint32_t PARTICLE_COUNT = 8192; constexpr int MAX_FRAMES_IN_FLIGHT = 2; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -46,914 +45,955 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -struct UniformBufferObject { - float deltaTime = 1.0f; +struct UniformBufferObject +{ + float deltaTime = 1.0f; }; -struct Particle { - glm::vec2 position; - glm::vec2 velocity; - glm::vec4 color; - - static vk::VertexInputBindingDescription getBindingDescription() { - return { 0, sizeof(Particle), vk::VertexInputRate::eVertex }; - } - - static std::array getAttributeDescriptions() { - return { - vk::VertexInputAttributeDescription( 0, 0, vk::Format::eR32G32Sfloat, offsetof(Particle, position) ), - vk::VertexInputAttributeDescription( 1, 0, vk::Format::eR32G32B32A32Sfloat, offsetof(Particle, color) ), - }; - } +struct Particle +{ + glm::vec2 position; + glm::vec2 velocity; + glm::vec4 color; + + static vk::VertexInputBindingDescription getBindingDescription() + { + return {0, sizeof(Particle), vk::VertexInputRate::eVertex}; + } + + static std::array getAttributeDescriptions() + { + return { + vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32Sfloat, offsetof(Particle, position)), + vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32A32Sfloat, offsetof(Particle, color)), + }; + } }; -class ComputeShaderApplication { -public: - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } - -private: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::SurfaceFormatKHR swapChainImageFormat; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - - vk::raii::DescriptorSetLayout computeDescriptorSetLayout = nullptr; - vk::raii::PipelineLayout computePipelineLayout = nullptr; - vk::raii::Pipeline computePipeline = nullptr; - - - std::vector shaderStorageBuffers; - std::vector shaderStorageBuffersMemory; - - std::vector uniformBuffers; - std::vector uniformBuffersMemory; - std::vector uniformBuffersMapped; - - vk::raii::DescriptorPool descriptorPool = nullptr; - std::vector computeDescriptorSets; - - vk::raii::CommandPool commandPool = nullptr; - std::vector commandBuffers; - std::vector computeCommandBuffers; - - vk::raii::Semaphore semaphore = nullptr; - uint64_t timelineValue = 0; - std::vector inFlightFences; - uint32_t currentFrame = 0; - - double lastFrameTime = 0.0; - - bool framebufferResized = false; - - double lastTime = 0.0f; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); - - lastTime = glfwGetTime(); - } - - static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { - auto app = static_cast(glfwGetWindowUserPointer(window)); - app->framebufferResized = true; - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - createSurface(); - pickPhysicalDevice(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createComputeDescriptorSetLayout(); - createGraphicsPipeline(); - createComputePipeline(); - createCommandPool(); - createShaderStorageBuffers(); - createUniformBuffers(); - createDescriptorPool(); - createComputeDescriptorSets(); - createCommandBuffers(); - createComputeCommandBuffers(); - createSyncObjects(); - } - - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - drawFrame(); - // We want to animate the particle system using the last frames time to get smooth, frame-rate independent animation - double currentTime = glfwGetTime(); - lastFrameTime = (currentTime - lastTime) * 1000.0; - lastTime = currentTime; - } - - device.waitIdle(); - } - - void cleanupSwapChain() { - swapChainImageViews.clear(); - swapChain = nullptr; - } - - void cleanup() const { - glfwDestroyWindow(window); - - glfwTerminate(); - } - - void recreateSwapChain() { - int width = 0, height = 0; - glfwGetFramebufferSize(window, &width, &height); - while (width == 0 || height == 0) { - glfwGetFramebufferSize(window, &width, &height); - glfwWaitEvents(); - } - - device.waitIdle(); - - cleanupSwapChain(); - createSwapChain(); - createImageViews(); - } - - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations +class ComputeShaderApplication +{ + public: + void run() + { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + + private: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::SurfaceFormatKHR swapChainImageFormat; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + vk::raii::DescriptorSetLayout computeDescriptorSetLayout = nullptr; + vk::raii::PipelineLayout computePipelineLayout = nullptr; + vk::raii::Pipeline computePipeline = nullptr; + std::vector shaderStorageBuffers; + std::vector shaderStorageBuffersMemory; + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + std::vector uniformBuffersMapped; + vk::raii::DescriptorPool descriptorPool = nullptr; + std::vector computeDescriptorSets; + vk::raii::CommandPool commandPool = nullptr; + std::vector commandBuffers; + std::vector computeCommandBuffers; + vk::raii::Semaphore semaphore = nullptr; + uint64_t timelineValue = 0; + std::vector inFlightFences; + uint32_t currentFrame = 0; + double lastFrameTime = 0.0; + bool framebufferResized = false; + double lastTime = 0.0f; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + + lastTime = glfwGetTime(); + } + + static void framebufferResizeCallback(GLFWwindow *window, int width, int height) + { + auto app = static_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createComputeDescriptorSetLayout(); + createGraphicsPipeline(); + createComputePipeline(); + createCommandPool(); + createShaderStorageBuffers(); + createUniformBuffers(); + createDescriptorPool(); + createComputeDescriptorSets(); + createCommandBuffers(); + createComputeCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() + { + while (!glfwWindowShouldClose(window)) + { + glfwPollEvents(); + drawFrame(); + // We want to animate the particle system using the last frames time to get smooth, frame-rate independent animation + double currentTime = glfwGetTime(); + lastFrameTime = (currentTime - lastTime) * 1000.0; + lastTime = currentTime; + } + + device.waitIdle(); + } + + void cleanupSwapChain() + { + swapChainImageViews.clear(); + swapChain = nullptr; + } + + void cleanup() const + { + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() + { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) + { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + device.waitIdle(); + + cleanupSwapChain(); + createSwapChain(); + createImageViews(); + } + + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().features.samplerAnisotropy && - features.template get().dynamicRendering && - features.template get().extendedDynamicState && - features.template get().timelineSemaphore; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - }); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error("failed to find a suitable GPU!"); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().features.samplerAnisotropy && + features.template get().dynamicRendering && + features.template get().extendedDynamicState && + features.template get().timelineSemaphore; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && (queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eCompute) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain - featureChain = { - {.features = {.samplerAnisotropy = true } }, // vk::PhysicalDeviceFeatures2 - {.synchronization2 = true, .dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true }, // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - {.timelineSemaphore = true } // vk::PhysicalDeviceTimelineSemaphoreFeaturesKHR - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( surface ); - swapChainImageFormat = 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 = swapChainImageFormat.format, .imageColorSpace = swapChainImageFormat.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(); - } - - void createImageViews() { - vk::ImageViewCreateInfo imageViewCreateInfo{ - .viewType = vk::ImageViewType::e2D, - .format = swapChainImageFormat.format, - .components = {vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity}, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } - }; - for ( auto image : swapChainImages ) - { - imageViewCreateInfo.image = image; - swapChainImageViews.emplace_back( device, imageViewCreateInfo ); - } - } - - void createComputeDescriptorSetLayout() { - std::array layoutBindings{ - vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eCompute, nullptr), - vk::DescriptorSetLayoutBinding(1, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eCompute, nullptr), - vk::DescriptorSetLayoutBinding(2, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eCompute, nullptr) - }; - - vk::DescriptorSetLayoutCreateInfo layoutInfo{ .bindingCount = static_cast(layoutBindings.size()), .pBindings = layoutBindings.data() }; - computeDescriptorSetLayout = vk::raii::DescriptorSetLayout( device, layoutInfo ); - } - - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; - - auto bindingDescription = Particle::getBindingDescription(); - auto attributeDescriptions = Particle::getAttributeDescriptions(); - - vk::PipelineVertexInputStateCreateInfo vertexInputInfo{ .vertexBindingDescriptionCount = 1, .pVertexBindingDescriptions = &bindingDescription, .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), .pVertexAttributeDescriptions = attributeDescriptions.data() }; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ .topology = vk::PrimitiveTopology::ePointList, .primitiveRestartEnable = vk::False }; - vk::PipelineViewportStateCreateInfo viewportState{ .viewportCount = 1, .scissorCount = 1 }; - vk::PipelineRasterizationStateCreateInfo rasterizer{ - .depthClampEnable = vk::False, - .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, - .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eCounterClockwise, - .depthBiasEnable = vk::False, - .lineWidth = 1.0f - }; - vk::PipelineMultisampleStateCreateInfo multisampling{ .rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False }; - - vk::PipelineColorBlendAttachmentState colorBlendAttachment{ - .blendEnable = vk::True, - .srcColorBlendFactor = vk::BlendFactor::eSrcAlpha, - .dstColorBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha, - .colorBlendOp = vk::BlendOp::eAdd, - .srcAlphaBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha, - .dstAlphaBlendFactor = vk::BlendFactor::eZero, - .alphaBlendOp = vk::BlendOp::eAdd, - .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA, - }; - - vk::PipelineColorBlendStateCreateInfo colorBlending{ .logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo; - pipelineLayout = vk::raii::PipelineLayout( device, pipelineLayoutInfo ); - - vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ .colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainImageFormat.format }; - vk::GraphicsPipelineCreateInfo pipelineInfo{ .pNext = &pipelineRenderingCreateInfo, - .stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = *pipelineLayout, - .subpass = 0 - }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - void createComputePipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.spv")); - - vk::PipelineShaderStageCreateInfo computeShaderStageInfo{ .stage = vk::ShaderStageFlagBits::eCompute, .module = shaderModule, .pName = "compMain" }; - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 1, .pSetLayouts = &*computeDescriptorSetLayout }; - computePipelineLayout = vk::raii::PipelineLayout( device, pipelineLayoutInfo ); - vk::ComputePipelineCreateInfo pipelineInfo{ .stage = computeShaderStageInfo, .layout = *computePipelineLayout }; - computePipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{}; - poolInfo.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer; - poolInfo.queueFamilyIndex = queueIndex; - commandPool = vk::raii::CommandPool(device, poolInfo); - } - - void createShaderStorageBuffers() { - // Initialize particles - std::default_random_engine rndEngine(static_cast(time(nullptr))); - std::uniform_real_distribution rndDist(0.0f, 1.0f); - - // Initial particle positions on a circle - std::vector particles(PARTICLE_COUNT); - for (auto& particle : particles) { - float r = 0.25f * sqrtf(rndDist(rndEngine)); - float theta = rndDist(rndEngine) * 2.0f * 3.14159265358979323846f; - float x = r * cosf(theta) * HEIGHT / WIDTH; - float y = r * sinf(theta); - particle.position = glm::vec2(x, y); - particle.velocity = normalize(glm::vec2(x,y)) * 0.00025f; - particle.color = glm::vec4(rndDist(rndEngine), rndDist(rndEngine), rndDist(rndEngine), 1.0f); - } - - vk::DeviceSize bufferSize = sizeof(Particle) * PARTICLE_COUNT; - - // Create a staging buffer used to upload data to the gpu - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(dataStaging, particles.data(), (size_t)bufferSize); - stagingBufferMemory.unmapMemory(); - - shaderStorageBuffers.clear(); - shaderStorageBuffersMemory.clear(); - - // Copy initial particle data to all storage buffers - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::raii::Buffer shaderStorageBufferTemp({}); - vk::raii::DeviceMemory shaderStorageBufferTempMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eStorageBuffer | vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eTransferDst, vk::MemoryPropertyFlagBits::eDeviceLocal, shaderStorageBufferTemp, shaderStorageBufferTempMemory); - copyBuffer(stagingBuffer, shaderStorageBufferTemp, bufferSize); - shaderStorageBuffers.emplace_back(std::move(shaderStorageBufferTemp)); - shaderStorageBuffersMemory.emplace_back(std::move(shaderStorageBufferTempMemory)); - } - } - - void createUniformBuffers() { - uniformBuffers.clear(); - uniformBuffersMemory.clear(); - uniformBuffersMapped.clear(); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DeviceSize bufferSize = sizeof(UniformBufferObject); - vk::raii::Buffer buffer({}); - vk::raii::DeviceMemory bufferMem({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); - uniformBuffers.emplace_back(std::move(buffer)); - uniformBuffersMemory.emplace_back(std::move(bufferMem)); - uniformBuffersMapped.emplace_back( uniformBuffersMemory[i].mapMemory(0, bufferSize)); - } - } - - void createDescriptorPool() { - std::array poolSize { - vk::DescriptorPoolSize( vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT), - vk::DescriptorPoolSize( vk::DescriptorType::eStorageBuffer, MAX_FRAMES_IN_FLIGHT * 2) - }; - vk::DescriptorPoolCreateInfo poolInfo{}; - poolInfo.flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet; - poolInfo.maxSets = MAX_FRAMES_IN_FLIGHT; - poolInfo.poolSizeCount = poolSize.size(); - poolInfo.pPoolSizes = poolSize.data(); - descriptorPool = vk::raii::DescriptorPool(device, poolInfo); - } - - void createComputeDescriptorSets() { - std::vector layouts(MAX_FRAMES_IN_FLIGHT, computeDescriptorSetLayout); - vk::DescriptorSetAllocateInfo allocInfo{}; - allocInfo.descriptorPool = *descriptorPool; - allocInfo.descriptorSetCount = MAX_FRAMES_IN_FLIGHT; - allocInfo.pSetLayouts = layouts.data(); - computeDescriptorSets.clear(); - computeDescriptorSets = device.allocateDescriptorSets(allocInfo); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DescriptorBufferInfo bufferInfo(uniformBuffers[i], 0, sizeof(UniformBufferObject)); - - vk::DescriptorBufferInfo storageBufferInfoLastFrame(shaderStorageBuffers[(i - 1) % MAX_FRAMES_IN_FLIGHT], 0, sizeof(Particle) * PARTICLE_COUNT); - vk::DescriptorBufferInfo storageBufferInfoCurrentFrame(shaderStorageBuffers[i], 0, sizeof(Particle) * PARTICLE_COUNT); - std::array descriptorWrites{ - vk::WriteDescriptorSet{ .dstSet = *computeDescriptorSets[i], .dstBinding = 0, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eUniformBuffer, .pImageInfo = nullptr, .pBufferInfo = &bufferInfo, .pTexelBufferView = nullptr }, - vk::WriteDescriptorSet{ .dstSet = *computeDescriptorSets[i], .dstBinding = 1, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eStorageBuffer, .pImageInfo = nullptr, .pBufferInfo = &storageBufferInfoLastFrame, .pTexelBufferView = nullptr }, - vk::WriteDescriptorSet{ .dstSet = *computeDescriptorSets[i], .dstBinding = 2, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eStorageBuffer, .pImageInfo = nullptr, .pBufferInfo = &storageBufferInfoCurrentFrame, .pTexelBufferView = nullptr }, - }; - device.updateDescriptorSets(descriptorWrites, {}); - } - } - - - void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer& buffer, vk::raii::DeviceMemory& bufferMemory) const { - vk::BufferCreateInfo bufferInfo{}; - bufferInfo.size = size; - bufferInfo.usage = usage; - bufferInfo.sharingMode = vk::SharingMode::eExclusive; - buffer = vk::raii::Buffer(device, bufferInfo); - vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{}; - allocInfo.allocationSize = memRequirements.size; - allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); - bufferMemory = vk::raii::DeviceMemory(device, allocInfo); - buffer.bindMemory(bufferMemory, 0); - } - - [[nodiscard]] vk::raii::CommandBuffer beginSingleTimeCommands() const { - vk::CommandBufferAllocateInfo allocInfo{}; - allocInfo.commandPool = *commandPool; - allocInfo.level = vk::CommandBufferLevel::ePrimary; - allocInfo.commandBufferCount = 1; - vk::raii::CommandBuffer commandBuffer = std::move(vk::raii::CommandBuffers( device, allocInfo ).front()); - - vk::CommandBufferBeginInfo beginInfo{ .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit }; - commandBuffer.begin(beginInfo); - - return commandBuffer; - } - - void endSingleTimeCommands(const vk::raii::CommandBuffer& commandBuffer) const { - commandBuffer.end(); - - vk::SubmitInfo submitInfo{}; - submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &*commandBuffer; - queue.submit(submitInfo, nullptr); - queue.waitIdle(); - } - - void copyBuffer(const vk::raii::Buffer & srcBuffer, const vk::raii::Buffer & dstBuffer, vk::DeviceSize size) const { - vk::raii::CommandBuffer commandCopyBuffer = beginSingleTimeCommands(); - commandCopyBuffer.copyBuffer(srcBuffer, dstBuffer, vk::BufferCopy(0, 0, size)); - endSingleTimeCommands(commandCopyBuffer); - } - - [[nodiscard]] uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) const { - vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); - - for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { - if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { - return i; - } - } - - throw std::runtime_error("failed to find suitable memory type!"); - } - - void createCommandBuffers() { - commandBuffers.clear(); - vk::CommandBufferAllocateInfo allocInfo{}; - allocInfo.commandPool = *commandPool; - allocInfo.level = vk::CommandBufferLevel::ePrimary; - allocInfo.commandBufferCount = MAX_FRAMES_IN_FLIGHT; - commandBuffers = vk::raii::CommandBuffers(device, allocInfo); - } - - void createComputeCommandBuffers() { - computeCommandBuffers.clear(); - vk::CommandBufferAllocateInfo allocInfo{}; - allocInfo.commandPool = *commandPool; - allocInfo.level = vk::CommandBufferLevel::ePrimary; - allocInfo.commandBufferCount = MAX_FRAMES_IN_FLIGHT; - computeCommandBuffers = vk::raii::CommandBuffers(device, allocInfo); - } - - void recordCommandBuffer( uint32_t imageIndex) { - commandBuffers[currentFrame].reset(); - commandBuffers[currentFrame].begin( {} ); - // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL - transition_image_layout( - imageIndex, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage - ); - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::RenderingAttachmentInfo attachmentInfo = { - .imageView = swapChainImageViews[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor - }; - vk::RenderingInfo renderingInfo = { - .renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &attachmentInfo - }; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor( 0, vk::Rect2D( vk::Offset2D( 0, 0 ), swapChainExtent ) ); - commandBuffers[currentFrame].bindVertexBuffers(0, { shaderStorageBuffers[currentFrame] }, {0}); - commandBuffers[currentFrame].draw( PARTICLE_COUNT, 1, 0, 0 ); - commandBuffers[currentFrame].endRendering(); - // After rendering, transition the swapchain image to PRESENT_SRC - transition_image_layout( - imageIndex, - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage - ); - commandBuffers[currentFrame].end(); - } - - void transition_image_layout( - uint32_t imageIndex, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::AccessFlags2 src_access_mask, - vk::AccessFlags2 dst_access_mask, - vk::PipelineStageFlags2 src_stage_mask, - vk::PipelineStageFlags2 dst_stage_mask - ) { - vk::ImageMemoryBarrier2 barrier = { - .srcStageMask = src_stage_mask, - .srcAccessMask = src_access_mask, - .dstStageMask = dst_stage_mask, - .dstAccessMask = dst_access_mask, - .oldLayout = old_layout, - .newLayout = new_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); - } - - void recordComputeCommandBuffer() { - computeCommandBuffers[currentFrame].reset(); - computeCommandBuffers[currentFrame].begin({}); - computeCommandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eCompute, computePipeline); - computeCommandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eCompute, computePipelineLayout, 0, {computeDescriptorSets[currentFrame]}, {}); - computeCommandBuffers[currentFrame].dispatch( PARTICLE_COUNT / 256, 1, 1 ); - computeCommandBuffers[currentFrame].end(); - } - - void createSyncObjects() { - inFlightFences.clear(); - - vk::SemaphoreTypeCreateInfo semaphoreType{ .semaphoreType = vk::SemaphoreType::eTimeline, .initialValue = 0 }; - semaphore = vk::raii::Semaphore(device, {.pNext = &semaphoreType}); - timelineValue = 0; - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::FenceCreateInfo fenceInfo{}; - inFlightFences.emplace_back(device, fenceInfo); - } - } - - void updateUniformBuffer(uint32_t currentImage) { - UniformBufferObject ubo{}; - ubo.deltaTime = static_cast(lastFrameTime) * 2.0f; - - memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); - } - - void drawFrame() { - auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, nullptr, *inFlightFences[currentFrame]); - while ( vk::Result::eTimeout == device.waitForFences( *inFlightFences[currentFrame], vk::True, UINT64_MAX ) ) - ; - device.resetFences(*inFlightFences[currentFrame]); - - // Update timeline value for this frame - uint64_t computeWaitValue = timelineValue; - uint64_t computeSignalValue = ++timelineValue; - uint64_t graphicsWaitValue = computeSignalValue; - uint64_t graphicsSignalValue = ++timelineValue; - - updateUniformBuffer(currentFrame); - - { - recordComputeCommandBuffer(); - // Submit compute work - vk::TimelineSemaphoreSubmitInfo computeTimelineInfo{ - .waitSemaphoreValueCount = 1, - .pWaitSemaphoreValues = &computeWaitValue, - .signalSemaphoreValueCount = 1, - .pSignalSemaphoreValues = &computeSignalValue - }; - - vk::PipelineStageFlags waitStages[] = {vk::PipelineStageFlagBits::eComputeShader}; - - vk::SubmitInfo computeSubmitInfo{ - .pNext = &computeTimelineInfo, - .waitSemaphoreCount = 1, - .pWaitSemaphores = &*semaphore, - .pWaitDstStageMask = waitStages, - .commandBufferCount = 1, - .pCommandBuffers = &*computeCommandBuffers[currentFrame], - .signalSemaphoreCount = 1, - .pSignalSemaphores = &*semaphore + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain + featureChain = { + {.features = {.samplerAnisotropy = true}}, // vk::PhysicalDeviceFeatures2 + {.synchronization2 = true, .dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true}, // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + {.timelineSemaphore = true} // vk::PhysicalDeviceTimelineSemaphoreFeaturesKHR + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface); + swapChainImageFormat = 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 = swapChainImageFormat.format, + .imageColorSpace = swapChainImageFormat.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(); + } + + void createImageViews() + { + vk::ImageViewCreateInfo imageViewCreateInfo{ + .viewType = vk::ImageViewType::e2D, + .format = swapChainImageFormat.format, + .components = {vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity}, + .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + for (auto image : swapChainImages) + { + imageViewCreateInfo.image = image; + swapChainImageViews.emplace_back(device, imageViewCreateInfo); + } + } + + void createComputeDescriptorSetLayout() + { + std::array layoutBindings{ + vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eCompute, nullptr), + vk::DescriptorSetLayoutBinding(1, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eCompute, nullptr), + vk::DescriptorSetLayoutBinding(2, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eCompute, nullptr)}; + + vk::DescriptorSetLayoutCreateInfo layoutInfo{.bindingCount = static_cast(layoutBindings.size()), .pBindings = layoutBindings.data()}; + computeDescriptorSetLayout = vk::raii::DescriptorSetLayout(device, layoutInfo); + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; + + auto bindingDescription = Particle::getBindingDescription(); + auto attributeDescriptions = Particle::getAttributeDescriptions(); + + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{.vertexBindingDescriptionCount = 1, .pVertexBindingDescriptions = &bindingDescription, .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), .pVertexAttributeDescriptions = attributeDescriptions.data()}; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{.topology = vk::PrimitiveTopology::ePointList, .primitiveRestartEnable = vk::False}; + vk::PipelineViewportStateCreateInfo viewportState{.viewportCount = 1, .scissorCount = 1}; + vk::PipelineRasterizationStateCreateInfo rasterizer{ + .depthClampEnable = vk::False, + .rasterizerDiscardEnable = vk::False, + .polygonMode = vk::PolygonMode::eFill, + .cullMode = vk::CullModeFlagBits::eBack, + .frontFace = vk::FrontFace::eCounterClockwise, + .depthBiasEnable = vk::False, + .lineWidth = 1.0f}; + vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; + + vk::PipelineColorBlendAttachmentState colorBlendAttachment{ + .blendEnable = vk::True, + .srcColorBlendFactor = vk::BlendFactor::eSrcAlpha, + .dstColorBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha, + .colorBlendOp = vk::BlendOp::eAdd, + .srcAlphaBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha, + .dstAlphaBlendFactor = vk::BlendFactor::eZero, + .alphaBlendOp = vk::BlendOp::eAdd, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA, + }; + + vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo; + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainImageFormat.format}; + vk::GraphicsPipelineCreateInfo pipelineInfo{.pNext = &pipelineRenderingCreateInfo, + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = *pipelineLayout, + .subpass = 0}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + void createComputePipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.spv")); + + vk::PipelineShaderStageCreateInfo computeShaderStageInfo{.stage = vk::ShaderStageFlagBits::eCompute, .module = shaderModule, .pName = "compMain"}; + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 1, .pSetLayouts = &*computeDescriptorSetLayout}; + computePipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + vk::ComputePipelineCreateInfo pipelineInfo{.stage = computeShaderStageInfo, .layout = *computePipelineLayout}; + computePipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{}; + poolInfo.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer; + poolInfo.queueFamilyIndex = queueIndex; + commandPool = vk::raii::CommandPool(device, poolInfo); + } + + void createShaderStorageBuffers() + { + // Initialize particles + std::default_random_engine rndEngine(static_cast(time(nullptr))); + std::uniform_real_distribution rndDist(0.0f, 1.0f); + + // Initial particle positions on a circle + std::vector particles(PARTICLE_COUNT); + for (auto &particle : particles) + { + float r = 0.25f * sqrtf(rndDist(rndEngine)); + float theta = rndDist(rndEngine) * 2.0f * 3.14159265358979323846f; + float x = r * cosf(theta) * HEIGHT / WIDTH; + float y = r * sinf(theta); + particle.position = glm::vec2(x, y); + particle.velocity = normalize(glm::vec2(x, y)) * 0.00025f; + particle.color = glm::vec4(rndDist(rndEngine), rndDist(rndEngine), rndDist(rndEngine), 1.0f); + } + + vk::DeviceSize bufferSize = sizeof(Particle) * PARTICLE_COUNT; + + // Create a staging buffer used to upload data to the gpu + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(dataStaging, particles.data(), (size_t) bufferSize); + stagingBufferMemory.unmapMemory(); + + shaderStorageBuffers.clear(); + shaderStorageBuffersMemory.clear(); + + // Copy initial particle data to all storage buffers + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::raii::Buffer shaderStorageBufferTemp({}); + vk::raii::DeviceMemory shaderStorageBufferTempMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eStorageBuffer | vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eTransferDst, vk::MemoryPropertyFlagBits::eDeviceLocal, shaderStorageBufferTemp, shaderStorageBufferTempMemory); + copyBuffer(stagingBuffer, shaderStorageBufferTemp, bufferSize); + shaderStorageBuffers.emplace_back(std::move(shaderStorageBufferTemp)); + shaderStorageBuffersMemory.emplace_back(std::move(shaderStorageBufferTempMemory)); + } + } + + void createUniformBuffers() + { + uniformBuffers.clear(); + uniformBuffersMemory.clear(); + uniformBuffersMapped.clear(); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DeviceSize bufferSize = sizeof(UniformBufferObject); + vk::raii::Buffer buffer({}); + vk::raii::DeviceMemory bufferMem({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); + uniformBuffers.emplace_back(std::move(buffer)); + uniformBuffersMemory.emplace_back(std::move(bufferMem)); + uniformBuffersMapped.emplace_back(uniformBuffersMemory[i].mapMemory(0, bufferSize)); + } + } + + void createDescriptorPool() + { + std::array poolSize{ + vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT), + vk::DescriptorPoolSize(vk::DescriptorType::eStorageBuffer, MAX_FRAMES_IN_FLIGHT * 2)}; + vk::DescriptorPoolCreateInfo poolInfo{}; + poolInfo.flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet; + poolInfo.maxSets = MAX_FRAMES_IN_FLIGHT; + poolInfo.poolSizeCount = poolSize.size(); + poolInfo.pPoolSizes = poolSize.data(); + descriptorPool = vk::raii::DescriptorPool(device, poolInfo); + } + + void createComputeDescriptorSets() + { + std::vector layouts(MAX_FRAMES_IN_FLIGHT, computeDescriptorSetLayout); + vk::DescriptorSetAllocateInfo allocInfo{}; + allocInfo.descriptorPool = *descriptorPool; + allocInfo.descriptorSetCount = MAX_FRAMES_IN_FLIGHT; + allocInfo.pSetLayouts = layouts.data(); + computeDescriptorSets.clear(); + computeDescriptorSets = device.allocateDescriptorSets(allocInfo); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DescriptorBufferInfo bufferInfo(uniformBuffers[i], 0, sizeof(UniformBufferObject)); + + vk::DescriptorBufferInfo storageBufferInfoLastFrame(shaderStorageBuffers[(i - 1) % MAX_FRAMES_IN_FLIGHT], 0, sizeof(Particle) * PARTICLE_COUNT); + vk::DescriptorBufferInfo storageBufferInfoCurrentFrame(shaderStorageBuffers[i], 0, sizeof(Particle) * PARTICLE_COUNT); + std::array descriptorWrites{ + vk::WriteDescriptorSet{.dstSet = *computeDescriptorSets[i], .dstBinding = 0, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eUniformBuffer, .pImageInfo = nullptr, .pBufferInfo = &bufferInfo, .pTexelBufferView = nullptr}, + vk::WriteDescriptorSet{.dstSet = *computeDescriptorSets[i], .dstBinding = 1, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eStorageBuffer, .pImageInfo = nullptr, .pBufferInfo = &storageBufferInfoLastFrame, .pTexelBufferView = nullptr}, + vk::WriteDescriptorSet{.dstSet = *computeDescriptorSets[i], .dstBinding = 2, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eStorageBuffer, .pImageInfo = nullptr, .pBufferInfo = &storageBufferInfoCurrentFrame, .pTexelBufferView = nullptr}, }; - - queue.submit(computeSubmitInfo, nullptr); - } - { - // Record graphics command buffer - recordCommandBuffer(imageIndex); - - // Submit graphics work (waits for compute to finish) - vk::PipelineStageFlags waitStage = vk::PipelineStageFlagBits::eVertexInput; - vk::TimelineSemaphoreSubmitInfo graphicsTimelineInfo{ - .waitSemaphoreValueCount = 1, - .pWaitSemaphoreValues = &graphicsWaitValue, - .signalSemaphoreValueCount = 1, - .pSignalSemaphoreValues = &graphicsSignalValue - }; - - vk::SubmitInfo graphicsSubmitInfo{ - .pNext = &graphicsTimelineInfo, - .waitSemaphoreCount = 1, - .pWaitSemaphores = &*semaphore, - .pWaitDstStageMask = &waitStage, - .commandBufferCount = 1, - .pCommandBuffers = &*commandBuffers[currentFrame], - .signalSemaphoreCount = 1, - .pSignalSemaphores = &*semaphore - }; - - queue.submit(graphicsSubmitInfo, nullptr); - - // Present the image (wait for graphics to finish) - vk::SemaphoreWaitInfo waitInfo{ - .semaphoreCount = 1, - .pSemaphores = &*semaphore, - .pValues = &graphicsSignalValue - }; - - // Wait for graphics to complete before presenting - while ( vk::Result::eTimeout ==device.waitSemaphores(waitInfo, UINT64_MAX) ) - ; - - vk::PresentInfoKHR presentInfo{ - .waitSemaphoreCount = 0, // No binary semaphores needed - .pWaitSemaphores = nullptr, - .swapchainCount = 1, - .pSwapchains = &*swapChain, - .pImageIndices = &imageIndex - }; - - result = queue.presentKHR(presentInfo); - if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { - framebufferResized = false; - recreateSwapChain(); - } else if (result != vk::Result::eSuccess) { - throw std::runtime_error("failed to present swap chain image!"); - } - } - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; - } - - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size(), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static 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]; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - for (const auto& availablePresentMode : availablePresentModes) { - if (availablePresentMode == vk::PresentModeKHR::eMailbox) { - return availablePresentMode; - } - } - return vk::PresentModeKHR::eFifo; - } - - [[nodiscard]] vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) const { - 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) - }; - } - - [[nodiscard]] std::vector getRequiredExtensions() const { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - 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; - } + device.updateDescriptorSets(descriptorWrites, {}); + } + } + + void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer &buffer, vk::raii::DeviceMemory &bufferMemory) const + { + vk::BufferCreateInfo bufferInfo{}; + bufferInfo.size = size; + bufferInfo.usage = usage; + bufferInfo.sharingMode = vk::SharingMode::eExclusive; + buffer = vk::raii::Buffer(device, bufferInfo); + vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{}; + allocInfo.allocationSize = memRequirements.size; + allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); + bufferMemory = vk::raii::DeviceMemory(device, allocInfo); + buffer.bindMemory(bufferMemory, 0); + } + + [[nodiscard]] vk::raii::CommandBuffer beginSingleTimeCommands() const + { + vk::CommandBufferAllocateInfo allocInfo{}; + allocInfo.commandPool = *commandPool; + allocInfo.level = vk::CommandBufferLevel::ePrimary; + allocInfo.commandBufferCount = 1; + vk::raii::CommandBuffer commandBuffer = std::move(vk::raii::CommandBuffers(device, allocInfo).front()); + + vk::CommandBufferBeginInfo beginInfo{.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; + commandBuffer.begin(beginInfo); + + return commandBuffer; + } + + void endSingleTimeCommands(const vk::raii::CommandBuffer &commandBuffer) const + { + commandBuffer.end(); + + vk::SubmitInfo submitInfo{}; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &*commandBuffer; + queue.submit(submitInfo, nullptr); + queue.waitIdle(); + } + + void copyBuffer(const vk::raii::Buffer &srcBuffer, const vk::raii::Buffer &dstBuffer, vk::DeviceSize size) const + { + vk::raii::CommandBuffer commandCopyBuffer = beginSingleTimeCommands(); + commandCopyBuffer.copyBuffer(srcBuffer, dstBuffer, vk::BufferCopy(0, 0, size)); + endSingleTimeCommands(commandCopyBuffer); + } + + [[nodiscard]] uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) const + { + vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) + { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) + { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + void createCommandBuffers() + { + commandBuffers.clear(); + vk::CommandBufferAllocateInfo allocInfo{}; + allocInfo.commandPool = *commandPool; + allocInfo.level = vk::CommandBufferLevel::ePrimary; + allocInfo.commandBufferCount = MAX_FRAMES_IN_FLIGHT; + commandBuffers = vk::raii::CommandBuffers(device, allocInfo); + } + + void createComputeCommandBuffers() + { + computeCommandBuffers.clear(); + vk::CommandBufferAllocateInfo allocInfo{}; + allocInfo.commandPool = *commandPool; + allocInfo.level = vk::CommandBufferLevel::ePrimary; + allocInfo.commandBufferCount = MAX_FRAMES_IN_FLIGHT; + computeCommandBuffers = vk::raii::CommandBuffers(device, allocInfo); + } + + void recordCommandBuffer(uint32_t imageIndex) + { + commandBuffers[currentFrame].reset(); + commandBuffers[currentFrame].begin({}); + // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL + transition_image_layout( + imageIndex, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage + ); + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::RenderingAttachmentInfo attachmentInfo = { + .imageView = swapChainImageViews[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor}; + vk::RenderingInfo renderingInfo = { + .renderArea = {.offset = {0, 0}, .extent = swapChainExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &attachmentInfo}; + commandBuffers[currentFrame].beginRendering(renderingInfo); + commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffers[currentFrame].bindVertexBuffers(0, {shaderStorageBuffers[currentFrame]}, {0}); + commandBuffers[currentFrame].draw(PARTICLE_COUNT, 1, 0, 0); + commandBuffers[currentFrame].endRendering(); + // After rendering, transition the swapchain image to PRESENT_SRC + transition_image_layout( + imageIndex, + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage + ); + commandBuffers[currentFrame].end(); + } + + void transition_image_layout( + uint32_t imageIndex, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout, + vk::AccessFlags2 src_access_mask, + vk::AccessFlags2 dst_access_mask, + vk::PipelineStageFlags2 src_stage_mask, + vk::PipelineStageFlags2 dst_stage_mask) + { + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = src_stage_mask, + .srcAccessMask = src_access_mask, + .dstStageMask = dst_stage_mask, + .dstAccessMask = dst_access_mask, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo dependency_info = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + } + + void recordComputeCommandBuffer() + { + computeCommandBuffers[currentFrame].reset(); + computeCommandBuffers[currentFrame].begin({}); + computeCommandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eCompute, computePipeline); + computeCommandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eCompute, computePipelineLayout, 0, {computeDescriptorSets[currentFrame]}, {}); + computeCommandBuffers[currentFrame].dispatch(PARTICLE_COUNT / 256, 1, 1); + computeCommandBuffers[currentFrame].end(); + } + + void createSyncObjects() + { + inFlightFences.clear(); + + vk::SemaphoreTypeCreateInfo semaphoreType{.semaphoreType = vk::SemaphoreType::eTimeline, .initialValue = 0}; + semaphore = vk::raii::Semaphore(device, {.pNext = &semaphoreType}); + timelineValue = 0; + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::FenceCreateInfo fenceInfo{}; + inFlightFences.emplace_back(device, fenceInfo); + } + } + + void updateUniformBuffer(uint32_t currentImage) + { + UniformBufferObject ubo{}; + ubo.deltaTime = static_cast(lastFrameTime) * 2.0f; + + memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); + } + + void drawFrame() + { + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, nullptr, *inFlightFences[currentFrame]); + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + ; + device.resetFences(*inFlightFences[currentFrame]); + + // Update timeline value for this frame + uint64_t computeWaitValue = timelineValue; + uint64_t computeSignalValue = ++timelineValue; + uint64_t graphicsWaitValue = computeSignalValue; + uint64_t graphicsSignalValue = ++timelineValue; + + updateUniformBuffer(currentFrame); + + { + recordComputeCommandBuffer(); + // Submit compute work + vk::TimelineSemaphoreSubmitInfo computeTimelineInfo{ + .waitSemaphoreValueCount = 1, + .pWaitSemaphoreValues = &computeWaitValue, + .signalSemaphoreValueCount = 1, + .pSignalSemaphoreValues = &computeSignalValue}; + + vk::PipelineStageFlags waitStages[] = {vk::PipelineStageFlagBits::eComputeShader}; + + vk::SubmitInfo computeSubmitInfo{ + .pNext = &computeTimelineInfo, + .waitSemaphoreCount = 1, + .pWaitSemaphores = &*semaphore, + .pWaitDstStageMask = waitStages, + .commandBufferCount = 1, + .pCommandBuffers = &*computeCommandBuffers[currentFrame], + .signalSemaphoreCount = 1, + .pSignalSemaphores = &*semaphore}; + + queue.submit(computeSubmitInfo, nullptr); + } + { + // Record graphics command buffer + recordCommandBuffer(imageIndex); + + // Submit graphics work (waits for compute to finish) + vk::PipelineStageFlags waitStage = vk::PipelineStageFlagBits::eVertexInput; + vk::TimelineSemaphoreSubmitInfo graphicsTimelineInfo{ + .waitSemaphoreValueCount = 1, + .pWaitSemaphoreValues = &graphicsWaitValue, + .signalSemaphoreValueCount = 1, + .pSignalSemaphoreValues = &graphicsSignalValue}; + + vk::SubmitInfo graphicsSubmitInfo{ + .pNext = &graphicsTimelineInfo, + .waitSemaphoreCount = 1, + .pWaitSemaphores = &*semaphore, + .pWaitDstStageMask = &waitStage, + .commandBufferCount = 1, + .pCommandBuffers = &*commandBuffers[currentFrame], + .signalSemaphoreCount = 1, + .pSignalSemaphores = &*semaphore}; + + queue.submit(graphicsSubmitInfo, nullptr); + + // Present the image (wait for graphics to finish) + vk::SemaphoreWaitInfo waitInfo{ + .semaphoreCount = 1, + .pSemaphores = &*semaphore, + .pValues = &graphicsSignalValue}; + + // Wait for graphics to complete before presenting + while (vk::Result::eTimeout == device.waitSemaphores(waitInfo, UINT64_MAX)) + ; + + vk::PresentInfoKHR presentInfo{ + .waitSemaphoreCount = 0, // No binary semaphores needed + .pWaitSemaphores = nullptr, + .swapchainCount = 1, + .pSwapchains = &*swapChain, + .pImageIndices = &imageIndex}; + + result = queue.presentKHR(presentInfo); + if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) + { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != vk::Result::eSuccess) + { + throw std::runtime_error("failed to present swap chain image!"); + } + } + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size(), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static 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]; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + for (const auto &availablePresentMode : availablePresentModes) + { + if (availablePresentMode == vk::PresentModeKHR::eMailbox) + { + return availablePresentMode; + } + } + return vk::PresentModeKHR::eFifo; + } + + [[nodiscard]] vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR &capabilities) const + { + 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)}; + } + + [[nodiscard]] std::vector getRequiredExtensions() const + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + 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; + } }; -int main() { - try { - ComputeShaderApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + ComputeShaderApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } From adf6d3258a843a82d4bcbc1b9e07ca1eecddf405 Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Sun, 17 Aug 2025 19:09:54 +0200 Subject: [PATCH 2/6] Apply clang format to all C++ source files --- attachments/00_base_code.cpp | 92 +- attachments/01_instance_creation.cpp | 179 +- attachments/02_validation_layers.cpp | 299 +- attachments/03_physical_device_selection.cpp | 398 +- attachments/04_logical_device.cpp | 468 +-- attachments/05_window_surface.cpp | 499 +-- attachments/06_swap_chain_creation.cpp | 611 +-- attachments/07_image_views.cpp | 635 +-- attachments/08_graphics_pipeline.cpp | 645 ++-- attachments/09_shader_modules.cpp | 699 ++-- attachments/10_fixed_functions.cpp | 752 ++-- attachments/12_graphics_pipeline_complete.cpp | 780 ++-- attachments/14_command_buffers.cpp | 969 ++--- attachments/15_hello_triangle.cpp | 1056 ++--- attachments/16_frames_in_flight.cpp | 1089 +++--- attachments/17_swap_chain_recreation.cpp | 1167 +++--- attachments/18_vertex_input.cpp | 1209 +++--- attachments/19_vertex_buffer.cpp | 1273 +++--- attachments/20_staging_buffer.cpp | 1312 +++---- attachments/21_index_buffer.cpp | 1357 +++---- attachments/22_descriptor_layout.cpp | 1462 +++---- attachments/23_descriptor_sets.cpp | 1515 ++++---- attachments/24_texture_image.cpp | 1722 +++++---- attachments/25_sampler.cpp | 1795 ++++----- attachments/26_texture_mapping.cpp | 1937 +++++----- attachments/27_depth_buffering.cpp | 2167 +++++------ attachments/28_model_loading.cpp | 2279 +++++------ attachments/29_mipmapping.cpp | 2411 ++++++------ attachments/30_multisampling.cpp | 2579 +++++++------ attachments/32_ecosystem_utilities.cpp | 3396 +++++++++-------- attachments/33_vulkan_profiles.cpp | 3393 ++++++++-------- attachments/34_android.cpp | 3202 ++++++++-------- attachments/35_gltf_ktx.cpp | 2686 ++++++------- attachments/36_multiple_objects.cpp | 2958 +++++++------- attachments/37_multithreading.cpp | 2431 ++++++------ 35 files changed, 26317 insertions(+), 25105 deletions(-) diff --git a/attachments/00_base_code.cpp b/attachments/00_base_code.cpp index 6d1074bf..ae5dea59 100644 --- a/attachments/00_base_code.cpp +++ b/attachments/00_base_code.cpp @@ -1,65 +1,75 @@ #include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif -#include #include +#include +#include #include #include -#include -const uint32_t WIDTH = 800; +const uint32_t WIDTH = 800; const uint32_t HEIGHT = 600; -class HelloTriangleApplication { -public: - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } - -private: - GLFWwindow* window = nullptr; +class HelloTriangleApplication +{ + public: + void run() + { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } - void initWindow() { - glfwInit(); + private: + GLFWwindow *window = nullptr; - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + void initWindow() + { + glfwInit(); - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - } + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); - void initVulkan() { + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + } - } + void initVulkan() + { + } - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - } - } + void mainLoop() + { + while (!glfwWindowShouldClose(window)) + { + glfwPollEvents(); + } + } - void cleanup() { - glfwDestroyWindow(window); + void cleanup() + { + glfwDestroyWindow(window); - glfwTerminate(); - } + glfwTerminate(); + } }; -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } - return EXIT_SUCCESS; + return EXIT_SUCCESS; } diff --git a/attachments/01_instance_creation.cpp b/attachments/01_instance_creation.cpp index 7e68df7f..526f7714 100644 --- a/attachments/01_instance_creation.cpp +++ b/attachments/01_instance_creation.cpp @@ -1,102 +1,113 @@ #include -#include -#include #include +#include #include +#include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include -constexpr uint32_t WIDTH = 800; +constexpr uint32_t WIDTH = 800; constexpr uint32_t HEIGHT = 600; -class HelloTriangleApplication { -public: - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } - -private: - GLFWwindow* window = nullptr; - - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - - void initWindow() { - glfwInit(); - - 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); - } +class HelloTriangleApplication +{ + public: + void run() + { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + + private: + GLFWwindow *window = nullptr; + + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + + void initWindow() + { + glfwInit(); + + 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); + } }; -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/02_validation_layers.cpp b/attachments/02_validation_layers.cpp index 6aef3863..d96a33a9 100644 --- a/attachments/02_validation_layers.cpp +++ b/attachments/02_validation_layers.cpp @@ -1,28 +1,27 @@ +#include +#include +#include #include +#include #include #include -#include -#include -#include -#include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include -constexpr uint32_t WIDTH = 800; +constexpr uint32_t WIDTH = 800; constexpr uint32_t HEIGHT = 600; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -30,139 +29,155 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -class HelloTriangleApplication { -public: - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } - -private: - GLFWwindow* window = nullptr; - - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - } - - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const & requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } +class HelloTriangleApplication +{ + public: + void run() + { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + + private: + GLFWwindow *window = nullptr; + + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + } + + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + std::vector getRequiredExtensions() + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } }; -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/03_physical_device_selection.cpp b/attachments/03_physical_device_selection.cpp index 63fecd50..e64f6357 100644 --- a/attachments/03_physical_device_selection.cpp +++ b/attachments/03_physical_device_selection.cpp @@ -1,28 +1,27 @@ +#include +#include +#include #include +#include #include #include -#include -#include -#include -#include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include -constexpr uint32_t WIDTH = 800; +constexpr uint32_t WIDTH = 800; constexpr uint32_t HEIGHT = 600; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -30,190 +29,203 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -class HelloTriangleApplication { -public: - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } - -private: - GLFWwindow* window = nullptr; - - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - - vk::raii::PhysicalDevice physicalDevice = nullptr; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - void pickPhysicalDevice() { - std::vector devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - } ); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error( "failed to find a suitable GPU!" ); - } - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } +class HelloTriangleApplication +{ + public: + void run() + { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + + private: + GLFWwindow *window = nullptr; + + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + + vk::raii::PhysicalDevice physicalDevice = nullptr; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + void pickPhysicalDevice() + { + std::vector devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + std::vector getRequiredExtensions() + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } }; -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/04_logical_device.cpp b/attachments/04_logical_device.cpp index ee42963a..fdb7ae59 100644 --- a/attachments/04_logical_device.cpp +++ b/attachments/04_logical_device.cpp @@ -1,29 +1,28 @@ +#include #include +#include +#include #include +#include #include #include -#include -#include -#include -#include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include -constexpr uint32_t WIDTH = 800; +constexpr uint32_t WIDTH = 800; constexpr uint32_t HEIGHT = 600; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -31,225 +30,238 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -class HelloTriangleApplication { -public: - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } - -private: - GLFWwindow* window = nullptr; - - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - - vk::raii::Queue graphicsQueue = nullptr; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - void pickPhysicalDevice() { - std::vector devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - } ); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error( "failed to find a suitable GPU!" ); - } - } - - void createLogicalDevice() { - // 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::ranges::find_if( queueFamilyProperties, []( auto const & qfp ) - { return (qfp.queueFlags & vk::QueueFlagBits::eGraphics) != static_cast(0); } ); - assert(graphicsQueueFamilyProperty != queueFamilyProperties.end() && "No graphics queue family found!"); - - auto graphicsIndex = static_cast( std::distance( queueFamilyProperties.begin(), graphicsQueueFamilyProperty ) ); - - // query for Vulkan 1.3 features - vk::StructureChain featureChain = { - {}, // vk::PhysicalDeviceFeatures2 - {.dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = graphicsIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device(physicalDevice, deviceCreateInfo); - graphicsQueue = vk::raii::Queue( device, graphicsIndex, 0 ); - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } +class HelloTriangleApplication +{ + public: + void run() + { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + + private: + GLFWwindow *window = nullptr; + + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + + vk::raii::Queue graphicsQueue = nullptr; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + void pickPhysicalDevice() + { + std::vector devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + // 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::ranges::find_if(queueFamilyProperties, [](auto const &qfp) { return (qfp.queueFlags & vk::QueueFlagBits::eGraphics) != static_cast(0); }); + assert(graphicsQueueFamilyProperty != queueFamilyProperties.end() && "No graphics queue family found!"); + + auto graphicsIndex = static_cast(std::distance(queueFamilyProperties.begin(), graphicsQueueFamilyProperty)); + + // query for Vulkan 1.3 features + vk::StructureChain featureChain = { + {}, // vk::PhysicalDeviceFeatures2 + {.dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = graphicsIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + graphicsQueue = vk::raii::Queue(device, graphicsIndex, 0); + } + + std::vector getRequiredExtensions() + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } }; -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/05_window_surface.cpp b/attachments/05_window_surface.cpp index b08fa68c..7fd9e648 100644 --- a/attachments/05_window_surface.cpp +++ b/attachments/05_window_surface.cpp @@ -1,28 +1,27 @@ +#include +#include +#include #include +#include #include #include -#include -#include -#include -#include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include -constexpr uint32_t WIDTH = 800; +constexpr uint32_t WIDTH = 800; constexpr uint32_t HEIGHT = 600; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -30,241 +29,257 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -class HelloTriangleApplication { -public: - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } - -private: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - vk::raii::Queue queue = nullptr; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - } ); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error( "failed to find a suitable GPU!" ); - } - } - - void createLogicalDevice() { +class HelloTriangleApplication +{ + public: + void run() + { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + + private: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + vk::raii::Queue queue = nullptr; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - // get the first index into queueFamilyProperties which supports both 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)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain featureChain = { - {}, // vk::PhysicalDeviceFeatures2 - {.dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } + // get the first index into queueFamilyProperties which supports both 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)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain featureChain = { + {}, // vk::PhysicalDeviceFeatures2 + {.dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + std::vector getRequiredExtensions() + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } }; -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/06_swap_chain_creation.cpp b/attachments/06_swap_chain_creation.cpp index 5f668372..4e735a44 100644 --- a/attachments/06_swap_chain_creation.cpp +++ b/attachments/06_swap_chain_creation.cpp @@ -1,29 +1,28 @@ +#include +#include +#include #include +#include +#include #include #include -#include -#include -#include -#include -#include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include -constexpr uint32_t WIDTH = 800; +constexpr uint32_t WIDTH = 800; constexpr uint32_t HEIGHT = 600; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -31,294 +30,310 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -class HelloTriangleApplication { -public: - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } - -private: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - vk::raii::Queue queue = nullptr; - - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::Format swapChainImageFormat = vk::Format::eUndefined; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - } ); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error( "failed to find a suitable GPU!" ); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both 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)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain featureChain = { - {}, // vk::PhysicalDeviceFeatures2 - {.dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( surface ); - swapChainImageFormat = 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{ - .surface = surface, .minImageCount = minImageCount, - .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, - .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 }; - - swapChain = vk::raii::SwapchainKHR( device, swapChainCreateInfo ); - swapChainImages = swapChain.getImages(); - } - - static vk::Format chooseSwapSurfaceFormat(const std::vector& availableFormats) { - const auto formatIt = std::ranges::find_if(availableFormats, - [](const auto& format) { - return format.format == vk::Format::eB8G8R8A8Srgb && - format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; - }); - return formatIt != availableFormats.end() ? formatIt->format : availableFormats[0].format; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : 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) - }; - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } +class HelloTriangleApplication +{ + public: + void run() + { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + + private: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + vk::raii::Queue queue = nullptr; + + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::Format swapChainImageFormat = vk::Format::eUndefined; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both 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)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain featureChain = { + {}, // vk::PhysicalDeviceFeatures2 + {.dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface); + swapChainImageFormat = 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{ + .surface = surface, .minImageCount = minImageCount, .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + static vk::Format chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + const auto formatIt = std::ranges::find_if(availableFormats, + [](const auto &format) { + return format.format == vk::Format::eB8G8R8A8Srgb && + format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; + }); + return formatIt != availableFormats.end() ? formatIt->format : availableFormats[0].format; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + 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)}; + } + + std::vector getRequiredExtensions() + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } }; -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/07_image_views.cpp b/attachments/07_image_views.cpp index c43f981f..e8da050b 100644 --- a/attachments/07_image_views.cpp +++ b/attachments/07_image_views.cpp @@ -1,29 +1,28 @@ +#include +#include +#include #include +#include +#include #include #include -#include -#include -#include -#include -#include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include -constexpr uint32_t WIDTH = 800; +constexpr uint32_t WIDTH = 800; constexpr uint32_t HEIGHT = 600; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -31,307 +30,323 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -class HelloTriangleApplication { -public: - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } - -private: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - vk::raii::Queue queue = nullptr; - - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::Format swapChainImageFormat = vk::Format::eUndefined; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - } ); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error( "failed to find a suitable GPU!" ); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both 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)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain featureChain = { - {}, // vk::PhysicalDeviceFeatures2 - {.dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( surface ); - swapChainImageFormat = 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{ - .surface = surface, .minImageCount = minImageCount, - .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, - .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 }; - - swapChain = vk::raii::SwapchainKHR( device, swapChainCreateInfo ); - swapChainImages = swapChain.getImages(); - } - - 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( device, imageViewCreateInfo ); - } - } - - static vk::Format chooseSwapSurfaceFormat(const std::vector& availableFormats) { - const auto formatIt = std::ranges::find_if(availableFormats, - [](const auto& format) { - return format.format == vk::Format::eB8G8R8A8Srgb && - format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; +class HelloTriangleApplication +{ + public: + void run() + { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + + private: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + vk::raii::Queue queue = nullptr; + + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::Format swapChainImageFormat = vk::Format::eUndefined; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; }); - return formatIt != availableFormats.end() ? formatIt->format : availableFormats[0].format; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : 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) - }; - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both 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)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain featureChain = { + {}, // vk::PhysicalDeviceFeatures2 + {.dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface); + swapChainImageFormat = 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{ + .surface = surface, .minImageCount = minImageCount, .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + 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(device, imageViewCreateInfo); + } + } + + static vk::Format chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + const auto formatIt = std::ranges::find_if(availableFormats, + [](const auto &format) { + return format.format == vk::Format::eB8G8R8A8Srgb && + format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; + }); + return formatIt != availableFormats.end() ? formatIt->format : availableFormats[0].format; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + 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)}; + } + + std::vector getRequiredExtensions() + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } }; -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/08_graphics_pipeline.cpp b/attachments/08_graphics_pipeline.cpp index cecbccd8..d0389710 100644 --- a/attachments/08_graphics_pipeline.cpp +++ b/attachments/08_graphics_pipeline.cpp @@ -1,29 +1,28 @@ +#include +#include +#include #include +#include +#include #include #include -#include -#include -#include -#include -#include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include -constexpr uint32_t WIDTH = 800; +constexpr uint32_t WIDTH = 800; constexpr uint32_t HEIGHT = 600; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -31,312 +30,328 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -class HelloTriangleApplication { -public: - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } - -private: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - vk::raii::Queue queue = nullptr; - - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::Format swapChainImageFormat = vk::Format::eUndefined; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - } ); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error( "failed to find a suitable GPU!" ); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both 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)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain featureChain = { - {}, // vk::PhysicalDeviceFeatures2 - {.dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( surface ); - swapChainImageFormat = 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{ - .surface = surface, .minImageCount = minImageCount, - .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, - .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 }; - - swapChain = vk::raii::SwapchainKHR( device, swapChainCreateInfo ); - swapChainImages = swapChain.getImages(); - } - - 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( device, imageViewCreateInfo ); - } - } - - void createGraphicsPipeline() { - - } - - static vk::Format chooseSwapSurfaceFormat(const std::vector& availableFormats) { - const auto formatIt = std::ranges::find_if(availableFormats, - [](const auto& format) { - return format.format == vk::Format::eB8G8R8A8Srgb && - format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; +class HelloTriangleApplication +{ + public: + void run() + { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + + private: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + vk::raii::Queue queue = nullptr; + + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::Format swapChainImageFormat = vk::Format::eUndefined; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; }); - return formatIt != availableFormats.end() ? formatIt->format : availableFormats[0].format; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : 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) - }; - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both 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)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain featureChain = { + {}, // vk::PhysicalDeviceFeatures2 + {.dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface); + swapChainImageFormat = 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{ + .surface = surface, .minImageCount = minImageCount, .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + 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(device, imageViewCreateInfo); + } + } + + void createGraphicsPipeline() + { + } + + static vk::Format chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + const auto formatIt = std::ranges::find_if(availableFormats, + [](const auto &format) { + return format.format == vk::Format::eB8G8R8A8Srgb && + format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; + }); + return formatIt != availableFormats.end() ? formatIt->format : availableFormats[0].format; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + 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)}; + } + + std::vector getRequiredExtensions() + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } }; -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/09_shader_modules.cpp b/attachments/09_shader_modules.cpp index 09816eb5..f7b443dd 100644 --- a/attachments/09_shader_modules.cpp +++ b/attachments/09_shader_modules.cpp @@ -1,30 +1,29 @@ -#include +#include +#include +#include #include +#include +#include +#include #include #include -#include -#include -#include -#include -#include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include -constexpr uint32_t WIDTH = 800; +constexpr uint32_t WIDTH = 800; constexpr uint32_t HEIGHT = 600; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -32,335 +31,355 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -class HelloTriangleApplication { -public: - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } - -private: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - vk::raii::Queue queue = nullptr; - - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::Format swapChainImageFormat = vk::Format::eUndefined; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - } ); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error( "failed to find a suitable GPU!" ); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both 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)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain featureChain = { - {}, // vk::PhysicalDeviceFeatures2 - {.dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( surface ); - swapChainImageFormat = 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{ - .surface = surface, .minImageCount = minImageCount, - .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, - .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 }; - - swapChain = vk::raii::SwapchainKHR( device, swapChainCreateInfo ); - swapChainImages = swapChain.getImages(); - } - - 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( device, imageViewCreateInfo ); - } - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static vk::Format chooseSwapSurfaceFormat(const std::vector& availableFormats) { - const auto formatIt = std::ranges::find_if(availableFormats, - [](const auto& format) { - return format.format == vk::Format::eB8G8R8A8Srgb && - format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; - }); - return formatIt != availableFormats.end() ? formatIt->format : availableFormats[0].format; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : 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) - }; - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - 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: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + vk::raii::Queue queue = nullptr; + + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::Format swapChainImageFormat = vk::Format::eUndefined; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both 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)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain featureChain = { + {}, // vk::PhysicalDeviceFeatures2 + {.dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface); + swapChainImageFormat = 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{ + .surface = surface, .minImageCount = minImageCount, .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + 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(device, imageViewCreateInfo); + } + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static vk::Format chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + const auto formatIt = std::ranges::find_if(availableFormats, + [](const auto &format) { + return format.format == vk::Format::eB8G8R8A8Srgb && + format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; + }); + return formatIt != availableFormats.end() ? formatIt->format : availableFormats[0].format; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + 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)}; + } + + std::vector getRequiredExtensions() + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + 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; + } }; -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/10_fixed_functions.cpp b/attachments/10_fixed_functions.cpp index d0af79d4..7459dbd4 100644 --- a/attachments/10_fixed_functions.cpp +++ b/attachments/10_fixed_functions.cpp @@ -1,30 +1,29 @@ -#include +#include +#include +#include #include +#include +#include +#include #include #include -#include -#include -#include -#include -#include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include -constexpr uint32_t WIDTH = 800; +constexpr uint32_t WIDTH = 800; constexpr uint32_t HEIGHT = 600; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -32,364 +31,379 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -class HelloTriangleApplication { -public: - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } - -private: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - vk::raii::Queue queue = nullptr; - - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::Format swapChainImageFormat = vk::Format::eUndefined; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::PipelineLayout pipelineLayout = nullptr; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - } ); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error( "failed to find a suitable GPU!" ); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both 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)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain featureChain = { - {}, // vk::PhysicalDeviceFeatures2 - {.dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( surface ); - swapChainImageFormat = 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{ - .surface = surface, .minImageCount = minImageCount, - .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, - .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 }; - - swapChain = vk::raii::SwapchainKHR( device, swapChainCreateInfo ); - swapChainImages = swapChain.getImages(); - } - - 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( device, imageViewCreateInfo ); - } - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; - - vk::PipelineVertexInputStateCreateInfo vertexInputInfo; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ .topology = vk::PrimitiveTopology::eTriangleList }; - vk::PipelineViewportStateCreateInfo viewportState{ .viewportCount = 1, .scissorCount = 1 }; - - vk::PipelineRasterizationStateCreateInfo rasterizer{ .depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = vk::False, - .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f }; - - vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; - - vk::PipelineColorBlendAttachmentState colorBlendAttachment{ .blendEnable = vk::False, - .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA - }; - - vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo; - - pipelineLayout = vk::raii::PipelineLayout( device, pipelineLayoutInfo ); - } - - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size() * sizeof(char), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static vk::Format chooseSwapSurfaceFormat(const std::vector& availableFormats) { - const auto formatIt = std::ranges::find_if(availableFormats, - [](const auto& format) { - return format.format == vk::Format::eB8G8R8A8Srgb && - format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; - }); - return formatIt != availableFormats.end() ? formatIt->format : availableFormats[0].format; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : 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) - }; - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - 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: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + vk::raii::Queue queue = nullptr; + + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::Format swapChainImageFormat = vk::Format::eUndefined; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::PipelineLayout pipelineLayout = nullptr; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both 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)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain featureChain = { + {}, // vk::PhysicalDeviceFeatures2 + {.dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface); + swapChainImageFormat = 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{ + .surface = surface, .minImageCount = minImageCount, .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + 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(device, imageViewCreateInfo); + } + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; + + vk::PipelineVertexInputStateCreateInfo vertexInputInfo; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{.topology = vk::PrimitiveTopology::eTriangleList}; + vk::PipelineViewportStateCreateInfo viewportState{.viewportCount = 1, .scissorCount = 1}; + + vk::PipelineRasterizationStateCreateInfo rasterizer{.depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = vk::False, .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f}; + + vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; + + vk::PipelineColorBlendAttachmentState colorBlendAttachment{.blendEnable = vk::False, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA}; + + vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo; + + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size() * sizeof(char), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static vk::Format chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + const auto formatIt = std::ranges::find_if(availableFormats, + [](const auto &format) { + return format.format == vk::Format::eB8G8R8A8Srgb && + format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; + }); + return formatIt != availableFormats.end() ? formatIt->format : availableFormats[0].format; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + 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)}; + } + + std::vector getRequiredExtensions() + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + 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; + } }; -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/12_graphics_pipeline_complete.cpp b/attachments/12_graphics_pipeline_complete.cpp index 70279ee5..08b4ca97 100644 --- a/attachments/12_graphics_pipeline_complete.cpp +++ b/attachments/12_graphics_pipeline_complete.cpp @@ -1,30 +1,29 @@ -#include +#include +#include +#include #include +#include +#include +#include #include #include -#include -#include -#include -#include -#include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include -constexpr uint32_t WIDTH = 800; +constexpr uint32_t WIDTH = 800; constexpr uint32_t HEIGHT = 600; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -32,375 +31,396 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -class HelloTriangleApplication { -public: - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } - -private: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - vk::raii::Queue queue = nullptr; - - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::Format swapChainImageFormat = vk::Format::eUndefined; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - } ); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error( "failed to find a suitable GPU!" ); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both 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)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain featureChain = { - {}, // vk::PhysicalDeviceFeatures2 - {.dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( surface ); - swapChainImageFormat = 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{ - .surface = surface, .minImageCount = minImageCount, - .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, - .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 }; - - swapChain = vk::raii::SwapchainKHR( device, swapChainCreateInfo ); - swapChainImages = swapChain.getImages(); - } - - 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( device, imageViewCreateInfo ); - } - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; - - vk::PipelineVertexInputStateCreateInfo vertexInputInfo; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ .topology = vk::PrimitiveTopology::eTriangleList }; - vk::PipelineViewportStateCreateInfo viewportState{ .viewportCount = 1, .scissorCount = 1 }; - - vk::PipelineRasterizationStateCreateInfo rasterizer{ .depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = vk::False, - .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f }; - - vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; - - vk::PipelineColorBlendAttachmentState colorBlendAttachment{ .blendEnable = vk::False, - .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA - }; - - vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 0, .pushConstantRangeCount = 0 }; - - pipelineLayout = vk::raii::PipelineLayout( device, pipelineLayoutInfo ); - - vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ .colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainImageFormat }; - vk::GraphicsPipelineCreateInfo pipelineInfo{ .pNext = &pipelineRenderingCreateInfo, - .stageCount = 2, .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, .layout = pipelineLayout, .renderPass = nullptr }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size() * sizeof(char), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static vk::Format chooseSwapSurfaceFormat(const std::vector& availableFormats) { - const auto formatIt = std::ranges::find_if(availableFormats, - [](const auto& format) { - return format.format == vk::Format::eB8G8R8A8Srgb && - format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; - }); - return formatIt != availableFormats.end() ? formatIt->format : availableFormats[0].format; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : 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) - }; - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - 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: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + vk::raii::Queue queue = nullptr; + + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::Format swapChainImageFormat = vk::Format::eUndefined; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both 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)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain featureChain = { + {}, // vk::PhysicalDeviceFeatures2 + {.dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface); + swapChainImageFormat = 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{ + .surface = surface, .minImageCount = minImageCount, .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + 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(device, imageViewCreateInfo); + } + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; + + vk::PipelineVertexInputStateCreateInfo vertexInputInfo; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{.topology = vk::PrimitiveTopology::eTriangleList}; + vk::PipelineViewportStateCreateInfo viewportState{.viewportCount = 1, .scissorCount = 1}; + + vk::PipelineRasterizationStateCreateInfo rasterizer{.depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = vk::False, .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f}; + + vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; + + vk::PipelineColorBlendAttachmentState colorBlendAttachment{.blendEnable = vk::False, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA}; + + vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 0, .pushConstantRangeCount = 0}; + + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainImageFormat}; + vk::GraphicsPipelineCreateInfo pipelineInfo{.pNext = &pipelineRenderingCreateInfo, + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size() * sizeof(char), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static vk::Format chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + const auto formatIt = std::ranges::find_if(availableFormats, + [](const auto &format) { + return format.format == vk::Format::eB8G8R8A8Srgb && + format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; + }); + return formatIt != availableFormats.end() ? formatIt->format : availableFormats[0].format; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + 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)}; + } + + std::vector getRequiredExtensions() + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + 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; + } }; -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/14_command_buffers.cpp b/attachments/14_command_buffers.cpp index f0d24467..cf058db1 100644 --- a/attachments/14_command_buffers.cpp +++ b/attachments/14_command_buffers.cpp @@ -1,30 +1,29 @@ -#include +#include +#include +#include #include +#include +#include +#include #include #include -#include -#include -#include -#include -#include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include -constexpr uint32_t WIDTH = 800; +constexpr uint32_t WIDTH = 800; constexpr uint32_t HEIGHT = 600; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -32,471 +31,489 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -class HelloTriangleApplication { -public: - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } - -private: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::Format swapChainImageFormat = vk::Format::eUndefined; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - vk::raii::CommandPool commandPool = nullptr; - vk::raii::CommandBuffer commandBuffer = nullptr; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - createSurface(); - pickPhysicalDevice(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createGraphicsPipeline(); - createCommandPool(); - createCommandBuffer(); - } - - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - } ); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error( "failed to find a suitable GPU!" ); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain featureChain = { - {}, // vk::PhysicalDeviceFeatures2 - {.dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( surface ); - swapChainImageFormat = 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{ - .surface = surface, .minImageCount = minImageCount, - .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, - .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 }; - - swapChain = vk::raii::SwapchainKHR( device, swapChainCreateInfo ); - swapChainImages = swapChain.getImages(); - } - - 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( device, imageViewCreateInfo ); - } - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; - - vk::PipelineVertexInputStateCreateInfo vertexInputInfo; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ .topology = vk::PrimitiveTopology::eTriangleList }; - vk::PipelineViewportStateCreateInfo viewportState{ .viewportCount = 1, .scissorCount = 1 }; - - vk::PipelineRasterizationStateCreateInfo rasterizer{ .depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = vk::False, - .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f }; - - vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; - - vk::PipelineColorBlendAttachmentState colorBlendAttachment{ .blendEnable = vk::False, - .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA - }; - - vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 0, .pushConstantRangeCount = 0 }; - - pipelineLayout = vk::raii::PipelineLayout( device, pipelineLayoutInfo ); - - vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ .colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainImageFormat }; - vk::GraphicsPipelineCreateInfo pipelineInfo{ .pNext = &pipelineRenderingCreateInfo, - .stageCount = 2, .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, .layout = pipelineLayout, .renderPass = nullptr }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{ .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex }; - commandPool = vk::raii::CommandPool(device, poolInfo); - } - - void createCommandBuffer() { - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = 1 }; - commandBuffer = std::move(vk::raii::CommandBuffers( device, allocInfo ).front()); - } - - void recordCommandBuffer(uint32_t imageIndex) { - commandBuffer.begin( {} ); - // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL - transition_image_layout( - imageIndex, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage - ); - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::RenderingAttachmentInfo attachmentInfo = { - .imageView = swapChainImageViews[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor - }; - vk::RenderingInfo renderingInfo = { - .renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &attachmentInfo - }; - - commandBuffer.beginRendering(renderingInfo); - commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffer.setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffer.setScissor( 0, vk::Rect2D( vk::Offset2D( 0, 0 ), swapChainExtent ) ); - commandBuffer.draw(3, 1, 0, 0); - commandBuffer.endRendering(); - // After rendering, transition the swapchain image to PRESENT_SRC - transition_image_layout( - imageIndex, - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage - ); - commandBuffer.end(); - } - - void transition_image_layout( - uint32_t currentFrame, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::AccessFlags2 src_access_mask, - vk::AccessFlags2 dst_access_mask, - vk::PipelineStageFlags2 src_stage_mask, - vk::PipelineStageFlags2 dst_stage_mask - ) { - vk::ImageMemoryBarrier2 barrier = { - .srcStageMask = src_stage_mask, - .srcAccessMask = src_access_mask, - .dstStageMask = dst_stage_mask, - .dstAccessMask = dst_access_mask, - .oldLayout = old_layout, - .newLayout = new_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[currentFrame], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - commandBuffer.pipelineBarrier2(dependency_info); - } - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size() * sizeof(char), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static vk::Format chooseSwapSurfaceFormat(const std::vector& availableFormats) { - const auto formatIt = std::ranges::find_if(availableFormats, - [](const auto& format) { - return format.format == vk::Format::eB8G8R8A8Srgb && - format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; - }); - return formatIt != availableFormats.end() ? formatIt->format : availableFormats[0].format; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : 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) - }; - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - 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: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::Format swapChainImageFormat = vk::Format::eUndefined; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + vk::raii::CommandPool commandPool = nullptr; + vk::raii::CommandBuffer commandBuffer = nullptr; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createGraphicsPipeline(); + createCommandPool(); + createCommandBuffer(); + } + + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain featureChain = { + {}, // vk::PhysicalDeviceFeatures2 + {.dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface); + swapChainImageFormat = 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{ + .surface = surface, .minImageCount = minImageCount, .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + 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(device, imageViewCreateInfo); + } + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; + + vk::PipelineVertexInputStateCreateInfo vertexInputInfo; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{.topology = vk::PrimitiveTopology::eTriangleList}; + vk::PipelineViewportStateCreateInfo viewportState{.viewportCount = 1, .scissorCount = 1}; + + vk::PipelineRasterizationStateCreateInfo rasterizer{.depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = vk::False, .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f}; + + vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; + + vk::PipelineColorBlendAttachmentState colorBlendAttachment{.blendEnable = vk::False, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA}; + + vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 0, .pushConstantRangeCount = 0}; + + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainImageFormat}; + vk::GraphicsPipelineCreateInfo pipelineInfo{.pNext = &pipelineRenderingCreateInfo, + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex}; + commandPool = vk::raii::CommandPool(device, poolInfo); + } + + void createCommandBuffer() + { + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1}; + commandBuffer = std::move(vk::raii::CommandBuffers(device, allocInfo).front()); + } + + void recordCommandBuffer(uint32_t imageIndex) + { + commandBuffer.begin({}); + // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL + transition_image_layout( + imageIndex, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage + ); + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::RenderingAttachmentInfo attachmentInfo = { + .imageView = swapChainImageViews[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor}; + vk::RenderingInfo renderingInfo = { + .renderArea = {.offset = {0, 0}, .extent = swapChainExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &attachmentInfo}; + + commandBuffer.beginRendering(renderingInfo); + commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffer.setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffer.setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffer.draw(3, 1, 0, 0); + commandBuffer.endRendering(); + // After rendering, transition the swapchain image to PRESENT_SRC + transition_image_layout( + imageIndex, + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage + ); + commandBuffer.end(); + } + + void transition_image_layout( + uint32_t currentFrame, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout, + vk::AccessFlags2 src_access_mask, + vk::AccessFlags2 dst_access_mask, + vk::PipelineStageFlags2 src_stage_mask, + vk::PipelineStageFlags2 dst_stage_mask) + { + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = src_stage_mask, + .srcAccessMask = src_access_mask, + .dstStageMask = dst_stage_mask, + .dstAccessMask = dst_access_mask, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[currentFrame], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo dependency_info = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + commandBuffer.pipelineBarrier2(dependency_info); + } + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size() * sizeof(char), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static vk::Format chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + const auto formatIt = std::ranges::find_if(availableFormats, + [](const auto &format) { + return format.format == vk::Format::eB8G8R8A8Srgb && + format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; + }); + return formatIt != availableFormats.end() ? formatIt->format : availableFormats[0].format; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + 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)}; + } + + std::vector getRequiredExtensions() + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + 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; + } }; -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/15_hello_triangle.cpp b/attachments/15_hello_triangle.cpp index eedf7ef5..f25775f5 100644 --- a/attachments/15_hello_triangle.cpp +++ b/attachments/15_hello_triangle.cpp @@ -1,30 +1,29 @@ -#include +#include +#include +#include #include +#include +#include +#include #include #include -#include -#include -#include -#include -#include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include -constexpr uint32_t WIDTH = 800; +constexpr uint32_t WIDTH = 800; constexpr uint32_t HEIGHT = 600; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -32,513 +31,534 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -class HelloTriangleApplication { -public: - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } - -private: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::Format swapChainImageFormat = vk::Format::eUndefined; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - - vk::raii::CommandPool commandPool = nullptr; - vk::raii::CommandBuffer commandBuffer = nullptr; - - vk::raii::Semaphore presentCompleteSemaphore = nullptr; - vk::raii::Semaphore renderFinishedSemaphore = nullptr; - vk::raii::Fence drawFence = nullptr; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - createSurface(); - pickPhysicalDevice(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createGraphicsPipeline(); - createCommandPool(); - createCommandBuffer(); - createSyncObjects(); - } - - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - drawFrame(); - } - - device.waitIdle(); - } - - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - } ); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error( "failed to find a suitable GPU!" ); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain featureChain = { - {}, // vk::PhysicalDeviceFeatures2 - {.synchronization2 = true, .dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( surface ); - swapChainImageFormat = 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{ - .surface = surface, .minImageCount = minImageCount, - .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, - .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 }; - - swapChain = vk::raii::SwapchainKHR( device, swapChainCreateInfo ); - swapChainImages = swapChain.getImages(); - } - - 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( device, imageViewCreateInfo ); - } - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; - - vk::PipelineVertexInputStateCreateInfo vertexInputInfo; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ .topology = vk::PrimitiveTopology::eTriangleList }; - vk::PipelineViewportStateCreateInfo viewportState{ .viewportCount = 1, .scissorCount = 1 }; - - vk::PipelineRasterizationStateCreateInfo rasterizer{ .depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = vk::False, - .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f }; - - vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; - - vk::PipelineColorBlendAttachmentState colorBlendAttachment{ .blendEnable = vk::False, - .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA - }; - - vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 0, .pushConstantRangeCount = 0 }; - - pipelineLayout = vk::raii::PipelineLayout( device, pipelineLayoutInfo ); - - vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ .colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainImageFormat }; - vk::GraphicsPipelineCreateInfo pipelineInfo{ .pNext = &pipelineRenderingCreateInfo, - .stageCount = 2, .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, .layout = pipelineLayout, .renderPass = nullptr }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{ .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex }; - commandPool = vk::raii::CommandPool(device, poolInfo); - } - - void createCommandBuffer() { - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = 1 }; - commandBuffer = std::move(vk::raii::CommandBuffers( device, allocInfo ).front()); - } - - void recordCommandBuffer(uint32_t imageIndex) { - commandBuffer.begin( {} ); - // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL - transition_image_layout( - imageIndex, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage - ); - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::RenderingAttachmentInfo attachmentInfo = { - .imageView = swapChainImageViews[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor - }; - vk::RenderingInfo renderingInfo = { - .renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &attachmentInfo - }; - - commandBuffer.beginRendering(renderingInfo); - commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffer.setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffer.setScissor( 0, vk::Rect2D( vk::Offset2D( 0, 0 ), swapChainExtent ) ); - commandBuffer.draw(3, 1, 0, 0); - commandBuffer.endRendering(); - // After rendering, transition the swapchain image to PRESENT_SRC - transition_image_layout( - imageIndex, - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage - ); - commandBuffer.end(); - } - - void transition_image_layout( - uint32_t currentFrame, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::AccessFlags2 src_access_mask, - vk::AccessFlags2 dst_access_mask, - vk::PipelineStageFlags2 src_stage_mask, - vk::PipelineStageFlags2 dst_stage_mask - ) { - vk::ImageMemoryBarrier2 barrier = { - .srcStageMask = src_stage_mask, - .srcAccessMask = src_access_mask, - .dstStageMask = dst_stage_mask, - .dstAccessMask = dst_access_mask, - .oldLayout = old_layout, - .newLayout = new_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[currentFrame], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - commandBuffer.pipelineBarrier2(dependency_info); - } - - void createSyncObjects() { - presentCompleteSemaphore =vk::raii::Semaphore(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore = vk::raii::Semaphore(device, vk::SemaphoreCreateInfo()); - drawFence = vk::raii::Fence(device, {.flags = vk::FenceCreateFlagBits::eSignaled}); - } - - void drawFrame() { - queue.waitIdle(); - - auto [result, imageIndex] = swapChain.acquireNextImage( UINT64_MAX, *presentCompleteSemaphore, nullptr ); - recordCommandBuffer(imageIndex); - - device.resetFences( *drawFence ); - vk::PipelineStageFlags waitDestinationStageMask( vk::PipelineStageFlagBits::eColorAttachmentOutput ); - const vk::SubmitInfo submitInfo{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore, - .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffer, - .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore }; - queue.submit(submitInfo, *drawFence); - while ( vk::Result::eTimeout == device.waitForFences( *drawFence, vk::True, UINT64_MAX ) ) - ; - - const vk::PresentInfoKHR presentInfoKHR{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore, - .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex }; - result = queue.presentKHR( presentInfoKHR ); - switch ( result ) - { - case vk::Result::eSuccess: break; - case vk::Result::eSuboptimalKHR: std::cout << "vk::Queue::presentKHR returned vk::Result::eSuboptimalKHR !\n"; break; - default: break; // an unexpected result is returned! - } - } - - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size() * sizeof(char), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static vk::Format chooseSwapSurfaceFormat(const std::vector& availableFormats) { - const auto formatIt = std::ranges::find_if(availableFormats, - [](const auto& format) { - return format.format == vk::Format::eB8G8R8A8Srgb && - format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; - }); - return formatIt != availableFormats.end() ? formatIt->format : availableFormats[0].format; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : 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) - }; - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - 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: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::Format swapChainImageFormat = vk::Format::eUndefined; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + + vk::raii::CommandPool commandPool = nullptr; + vk::raii::CommandBuffer commandBuffer = nullptr; + + vk::raii::Semaphore presentCompleteSemaphore = nullptr; + vk::raii::Semaphore renderFinishedSemaphore = nullptr; + vk::raii::Fence drawFence = nullptr; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createGraphicsPipeline(); + createCommandPool(); + createCommandBuffer(); + createSyncObjects(); + } + + void mainLoop() + { + while (!glfwWindowShouldClose(window)) + { + glfwPollEvents(); + drawFrame(); + } + + device.waitIdle(); + } + + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain featureChain = { + {}, // vk::PhysicalDeviceFeatures2 + {.synchronization2 = true, .dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface); + swapChainImageFormat = 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{ + .surface = surface, .minImageCount = minImageCount, .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + 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(device, imageViewCreateInfo); + } + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; + + vk::PipelineVertexInputStateCreateInfo vertexInputInfo; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{.topology = vk::PrimitiveTopology::eTriangleList}; + vk::PipelineViewportStateCreateInfo viewportState{.viewportCount = 1, .scissorCount = 1}; + + vk::PipelineRasterizationStateCreateInfo rasterizer{.depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = vk::False, .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f}; + + vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; + + vk::PipelineColorBlendAttachmentState colorBlendAttachment{.blendEnable = vk::False, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA}; + + vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 0, .pushConstantRangeCount = 0}; + + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainImageFormat}; + vk::GraphicsPipelineCreateInfo pipelineInfo{.pNext = &pipelineRenderingCreateInfo, + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex}; + commandPool = vk::raii::CommandPool(device, poolInfo); + } + + void createCommandBuffer() + { + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1}; + commandBuffer = std::move(vk::raii::CommandBuffers(device, allocInfo).front()); + } + + void recordCommandBuffer(uint32_t imageIndex) + { + commandBuffer.begin({}); + // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL + transition_image_layout( + imageIndex, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage + ); + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::RenderingAttachmentInfo attachmentInfo = { + .imageView = swapChainImageViews[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor}; + vk::RenderingInfo renderingInfo = { + .renderArea = {.offset = {0, 0}, .extent = swapChainExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &attachmentInfo}; + + commandBuffer.beginRendering(renderingInfo); + commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffer.setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffer.setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffer.draw(3, 1, 0, 0); + commandBuffer.endRendering(); + // After rendering, transition the swapchain image to PRESENT_SRC + transition_image_layout( + imageIndex, + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage + ); + commandBuffer.end(); + } + + void transition_image_layout( + uint32_t currentFrame, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout, + vk::AccessFlags2 src_access_mask, + vk::AccessFlags2 dst_access_mask, + vk::PipelineStageFlags2 src_stage_mask, + vk::PipelineStageFlags2 dst_stage_mask) + { + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = src_stage_mask, + .srcAccessMask = src_access_mask, + .dstStageMask = dst_stage_mask, + .dstAccessMask = dst_access_mask, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[currentFrame], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo dependency_info = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + commandBuffer.pipelineBarrier2(dependency_info); + } + + void createSyncObjects() + { + presentCompleteSemaphore = vk::raii::Semaphore(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphore = vk::raii::Semaphore(device, vk::SemaphoreCreateInfo()); + drawFence = vk::raii::Fence(device, {.flags = vk::FenceCreateFlagBits::eSignaled}); + } + + void drawFrame() + { + queue.waitIdle(); + + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore, nullptr); + recordCommandBuffer(imageIndex); + + device.resetFences(*drawFence); + vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore, .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffer, .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore}; + queue.submit(submitInfo, *drawFence); + while (vk::Result::eTimeout == device.waitForFences(*drawFence, vk::True, UINT64_MAX)) + ; + + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore, .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + result = queue.presentKHR(presentInfoKHR); + switch (result) + { + case vk::Result::eSuccess: + break; + case vk::Result::eSuboptimalKHR: + std::cout << "vk::Queue::presentKHR returned vk::Result::eSuboptimalKHR !\n"; + break; + default: + break; // an unexpected result is returned! + } + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size() * sizeof(char), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static vk::Format chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + const auto formatIt = std::ranges::find_if(availableFormats, + [](const auto &format) { + return format.format == vk::Format::eB8G8R8A8Srgb && + format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; + }); + return formatIt != availableFormats.end() ? formatIt->format : availableFormats[0].format; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + 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)}; + } + + std::vector getRequiredExtensions() + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + 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; + } }; -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/16_frames_in_flight.cpp b/attachments/16_frames_in_flight.cpp index 35dedd6c..0dce495f 100644 --- a/attachments/16_frames_in_flight.cpp +++ b/attachments/16_frames_in_flight.cpp @@ -1,31 +1,30 @@ -#include +#include +#include +#include #include +#include +#include +#include #include #include -#include -#include -#include -#include -#include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -constexpr int MAX_FRAMES_IN_FLIGHT = 2; +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; +constexpr int MAX_FRAMES_IN_FLIGHT = 2; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -33,528 +32,548 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -class HelloTriangleApplication { -public: - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } - -private: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::Format swapChainImageFormat = vk::Format::eUndefined; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - - vk::raii::CommandPool commandPool = nullptr; - std::vector commandBuffers; - - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; - std::vector inFlightFences; - uint32_t semaphoreIndex = 0; - uint32_t currentFrame = 0; - - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - createSurface(); - pickPhysicalDevice(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createGraphicsPipeline(); - createCommandPool(); - createCommandBuffers(); - createSyncObjects(); - } - - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - drawFrame(); - } - - device.waitIdle(); - } - - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - } ); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error( "failed to find a suitable GPU!" ); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain featureChain = { - {}, // vk::PhysicalDeviceFeatures2 - {.synchronization2 = true, .dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( surface ); - swapChainImageFormat = 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{ - .surface = surface, .minImageCount = minImageCount, - .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, - .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 }; - - swapChain = vk::raii::SwapchainKHR( device, swapChainCreateInfo ); - swapChainImages = swapChain.getImages(); - } - - 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( device, imageViewCreateInfo ); - } - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; - - vk::PipelineVertexInputStateCreateInfo vertexInputInfo; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ .topology = vk::PrimitiveTopology::eTriangleList }; - vk::PipelineViewportStateCreateInfo viewportState{ .viewportCount = 1, .scissorCount = 1 }; - - vk::PipelineRasterizationStateCreateInfo rasterizer{ .depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = vk::False, - .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f }; - - vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; - - vk::PipelineColorBlendAttachmentState colorBlendAttachment{ .blendEnable = vk::False, - .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA - }; - - vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 0, .pushConstantRangeCount = 0 }; - - pipelineLayout = vk::raii::PipelineLayout( device, pipelineLayoutInfo ); - - vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ .colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainImageFormat }; - vk::GraphicsPipelineCreateInfo pipelineInfo{ .pNext = &pipelineRenderingCreateInfo, - .stageCount = 2, .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, .layout = pipelineLayout, .renderPass = nullptr }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{ .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex }; - commandPool = vk::raii::CommandPool(device, poolInfo); - } - - void createCommandBuffers() { - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = MAX_FRAMES_IN_FLIGHT }; - commandBuffers = vk::raii::CommandBuffers( device, allocInfo ); - } - - void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin( {} ); - // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL - transition_image_layout( - imageIndex, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage - ); - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::RenderingAttachmentInfo attachmentInfo = { - .imageView = swapChainImageViews[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor - }; - vk::RenderingInfo renderingInfo = { - .renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &attachmentInfo - }; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor( 0, vk::Rect2D( vk::Offset2D( 0, 0 ), swapChainExtent ) ); - commandBuffers[currentFrame].draw(3, 1, 0, 0); - commandBuffers[currentFrame].endRendering(); - // After rendering, transition the swapchain image to PRESENT_SRC - transition_image_layout( - imageIndex, - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage - ); - commandBuffers[currentFrame].end(); - } - - void transition_image_layout( - uint32_t imageIndex, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::AccessFlags2 src_access_mask, - vk::AccessFlags2 dst_access_mask, - vk::PipelineStageFlags2 src_stage_mask, - vk::PipelineStageFlags2 dst_stage_mask - ) { - vk::ImageMemoryBarrier2 barrier = { - .srcStageMask = src_stage_mask, - .srcAccessMask = src_access_mask, - .dstStageMask = dst_stage_mask, - .dstAccessMask = dst_access_mask, - .oldLayout = old_layout, - .newLayout = new_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); - } - - void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); - - for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - } - - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - inFlightFences.emplace_back(device, vk::FenceCreateInfo { .flags = vk::FenceCreateFlagBits::eSignaled }); - } - } - - void drawFrame() { - while ( vk::Result::eTimeout == device.waitForFences( *inFlightFences[currentFrame], vk::True, UINT64_MAX ) ) - ; - auto [result, imageIndex] = swapChain.acquireNextImage( UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr ); - - device.resetFences( *inFlightFences[currentFrame] ); - commandBuffers[currentFrame].reset(); - recordCommandBuffer(imageIndex); - - vk::PipelineStageFlags waitDestinationStageMask( vk::PipelineStageFlagBits::eColorAttachmentOutput ); - const vk::SubmitInfo submitInfo{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], - .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], - .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex] }; - queue.submit(submitInfo, *inFlightFences[currentFrame]); - - - const vk::PresentInfoKHR presentInfoKHR{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], - .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex }; - result = queue.presentKHR( presentInfoKHR ); - switch ( result ) - { - case vk::Result::eSuccess: break; - case vk::Result::eSuboptimalKHR: std::cout << "vk::Queue::presentKHR returned vk::Result::eSuboptimalKHR !\n"; break; - default: break; // an unexpected result is returned! - } - semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; - } - - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size() * sizeof(char), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static vk::Format chooseSwapSurfaceFormat(const std::vector& availableFormats) { - const auto formatIt = std::ranges::find_if(availableFormats, - [](const auto& format) { - return format.format == vk::Format::eB8G8R8A8Srgb && - format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; - }); - return formatIt != availableFormats.end() ? formatIt->format : availableFormats[0].format; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : 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) - }; - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - 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: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::Format swapChainImageFormat = vk::Format::eUndefined; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + + vk::raii::CommandPool commandPool = nullptr; + std::vector commandBuffers; + + std::vector presentCompleteSemaphore; + std::vector renderFinishedSemaphore; + std::vector inFlightFences; + uint32_t semaphoreIndex = 0; + uint32_t currentFrame = 0; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createGraphicsPipeline(); + createCommandPool(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() + { + while (!glfwWindowShouldClose(window)) + { + glfwPollEvents(); + drawFrame(); + } + + device.waitIdle(); + } + + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain featureChain = { + {}, // vk::PhysicalDeviceFeatures2 + {.synchronization2 = true, .dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface); + swapChainImageFormat = 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{ + .surface = surface, .minImageCount = minImageCount, .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + 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(device, imageViewCreateInfo); + } + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; + + vk::PipelineVertexInputStateCreateInfo vertexInputInfo; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{.topology = vk::PrimitiveTopology::eTriangleList}; + vk::PipelineViewportStateCreateInfo viewportState{.viewportCount = 1, .scissorCount = 1}; + + vk::PipelineRasterizationStateCreateInfo rasterizer{.depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = vk::False, .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f}; + + vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; + + vk::PipelineColorBlendAttachmentState colorBlendAttachment{.blendEnable = vk::False, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA}; + + vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 0, .pushConstantRangeCount = 0}; + + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainImageFormat}; + vk::GraphicsPipelineCreateInfo pipelineInfo{.pNext = &pipelineRenderingCreateInfo, + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex}; + commandPool = vk::raii::CommandPool(device, poolInfo); + } + + void createCommandBuffers() + { + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = MAX_FRAMES_IN_FLIGHT}; + commandBuffers = vk::raii::CommandBuffers(device, allocInfo); + } + + void recordCommandBuffer(uint32_t imageIndex) + { + commandBuffers[currentFrame].begin({}); + // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL + transition_image_layout( + imageIndex, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage + ); + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::RenderingAttachmentInfo attachmentInfo = { + .imageView = swapChainImageViews[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor}; + vk::RenderingInfo renderingInfo = { + .renderArea = {.offset = {0, 0}, .extent = swapChainExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &attachmentInfo}; + commandBuffers[currentFrame].beginRendering(renderingInfo); + commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffers[currentFrame].draw(3, 1, 0, 0); + commandBuffers[currentFrame].endRendering(); + // After rendering, transition the swapchain image to PRESENT_SRC + transition_image_layout( + imageIndex, + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage + ); + commandBuffers[currentFrame].end(); + } + + void transition_image_layout( + uint32_t imageIndex, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout, + vk::AccessFlags2 src_access_mask, + vk::AccessFlags2 dst_access_mask, + vk::PipelineStageFlags2 src_stage_mask, + vk::PipelineStageFlags2 dst_stage_mask) + { + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = src_stage_mask, + .srcAccessMask = src_access_mask, + .dstStageMask = dst_stage_mask, + .dstAccessMask = dst_access_mask, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo dependency_info = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + } + + void createSyncObjects() + { + presentCompleteSemaphore.clear(); + renderFinishedSemaphore.clear(); + inFlightFences.clear(); + + for (size_t i = 0; i < swapChainImages.size(); i++) + { + presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); + } + } + + void drawFrame() + { + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + ; + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr); + + device.resetFences(*inFlightFences[currentFrame]); + commandBuffers[currentFrame].reset(); + recordCommandBuffer(imageIndex); + + vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[currentFrame]); + + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + result = queue.presentKHR(presentInfoKHR); + switch (result) + { + case vk::Result::eSuccess: + break; + case vk::Result::eSuboptimalKHR: + std::cout << "vk::Queue::presentKHR returned vk::Result::eSuboptimalKHR !\n"; + break; + default: + break; // an unexpected result is returned! + } + semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size() * sizeof(char), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static vk::Format chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + const auto formatIt = std::ranges::find_if(availableFormats, + [](const auto &format) { + return format.format == vk::Format::eB8G8R8A8Srgb && + format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; + }); + return formatIt != availableFormats.end() ? formatIt->format : availableFormats[0].format; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + 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)}; + } + + std::vector getRequiredExtensions() + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + 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; + } }; -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/17_swap_chain_recreation.cpp b/attachments/17_swap_chain_recreation.cpp index e2dd5893..8f90feff 100644 --- a/attachments/17_swap_chain_recreation.cpp +++ b/attachments/17_swap_chain_recreation.cpp @@ -1,31 +1,30 @@ -#include +#include +#include +#include #include +#include +#include +#include #include #include -#include -#include -#include -#include -#include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -constexpr int MAX_FRAMES_IN_FLIGHT = 2; +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; +constexpr int MAX_FRAMES_IN_FLIGHT = 2; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -33,564 +32,590 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -class HelloTriangleApplication { -public: - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } - -private: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::Format swapChainImageFormat = vk::Format::eUndefined; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - - vk::raii::CommandPool commandPool = nullptr; - std::vector commandBuffers; - - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; - std::vector inFlightFences; - uint32_t semaphoreIndex = 0; - uint32_t currentFrame = 0; - - bool framebufferResized = false; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); - } - - static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { - auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); - app->framebufferResized = true; - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - createSurface(); - pickPhysicalDevice(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createGraphicsPipeline(); - createCommandPool(); - createCommandBuffers(); - createSyncObjects(); - } - - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - drawFrame(); - } - - device.waitIdle(); - } - - void cleanupSwapChain() { - swapChainImageViews.clear(); - swapChain = nullptr; - } - - void cleanup() { - glfwDestroyWindow(window); - - glfwTerminate(); - } - - void recreateSwapChain() { - int width = 0, height = 0; - glfwGetFramebufferSize(window, &width, &height); - while (width == 0 || height == 0) { - glfwGetFramebufferSize(window, &width, &height); - glfwWaitEvents(); - } - - device.waitIdle(); - - cleanupSwapChain(); - createSwapChain(); - createImageViews(); - } - - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - } ); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error( "failed to find a suitable GPU!" ); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain featureChain = { - {}, // vk::PhysicalDeviceFeatures2 - {.synchronization2 = true, .dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( surface ); - swapChainImageFormat = 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{ - .surface = surface, .minImageCount = minImageCount, - .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, - .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 }; - - swapChain = vk::raii::SwapchainKHR( device, swapChainCreateInfo ); - swapChainImages = swapChain.getImages(); - } - - 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( device, imageViewCreateInfo ); - } - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; - - vk::PipelineVertexInputStateCreateInfo vertexInputInfo; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ .topology = vk::PrimitiveTopology::eTriangleList }; - vk::PipelineViewportStateCreateInfo viewportState{ .viewportCount = 1, .scissorCount = 1 }; - - vk::PipelineRasterizationStateCreateInfo rasterizer{ .depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = vk::False, - .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f }; - - vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; - - vk::PipelineColorBlendAttachmentState colorBlendAttachment{ .blendEnable = vk::False, - .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA - }; - - vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 0, .pushConstantRangeCount = 0 }; - - pipelineLayout = vk::raii::PipelineLayout( device, pipelineLayoutInfo ); - - vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ .colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainImageFormat }; - vk::GraphicsPipelineCreateInfo pipelineInfo{ .pNext = &pipelineRenderingCreateInfo, - .stageCount = 2, .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, .layout = pipelineLayout, .renderPass = nullptr }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{ .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex }; - commandPool = vk::raii::CommandPool(device, poolInfo); - } - - void createCommandBuffers() { - commandBuffers.clear(); - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = MAX_FRAMES_IN_FLIGHT }; - commandBuffers = vk::raii::CommandBuffers( device, allocInfo ); - } - - void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin( {} ); - // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL - transition_image_layout( - imageIndex, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage - ); - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::RenderingAttachmentInfo attachmentInfo = { - .imageView = swapChainImageViews[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor - }; - vk::RenderingInfo renderingInfo = { - .renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &attachmentInfo - }; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor( 0, vk::Rect2D( vk::Offset2D( 0, 0 ), swapChainExtent ) ); - commandBuffers[currentFrame].draw(3, 1, 0, 0); - commandBuffers[currentFrame].endRendering(); - // After rendering, transition the swapchain image to PRESENT_SRC - transition_image_layout( - imageIndex, - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage - ); - commandBuffers[currentFrame].end(); - } - - void transition_image_layout( - uint32_t imageIndex, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::AccessFlags2 src_access_mask, - vk::AccessFlags2 dst_access_mask, - vk::PipelineStageFlags2 src_stage_mask, - vk::PipelineStageFlags2 dst_stage_mask - ) { - vk::ImageMemoryBarrier2 barrier = { - .srcStageMask = src_stage_mask, - .srcAccessMask = src_access_mask, - .dstStageMask = dst_stage_mask, - .dstAccessMask = dst_access_mask, - .oldLayout = old_layout, - .newLayout = new_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); - } - - void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); - - for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - } - - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - inFlightFences.emplace_back(device, vk::FenceCreateInfo { .flags = vk::FenceCreateFlagBits::eSignaled }); - } - } - - void drawFrame() { - while ( vk::Result::eTimeout == device.waitForFences( *inFlightFences[currentFrame], vk::True, UINT64_MAX ) ) - ; - auto [result, imageIndex] = swapChain.acquireNextImage( UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr ); - - if (result == vk::Result::eErrorOutOfDateKHR) { - recreateSwapChain(); - return; - } - if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) { - throw std::runtime_error("failed to acquire swap chain image!"); - } - - device.resetFences( *inFlightFences[currentFrame] ); - commandBuffers[currentFrame].reset(); - recordCommandBuffer(imageIndex); - - vk::PipelineStageFlags waitDestinationStageMask( vk::PipelineStageFlagBits::eColorAttachmentOutput ); - const vk::SubmitInfo submitInfo{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], - .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], - .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex] }; - queue.submit(submitInfo, *inFlightFences[currentFrame]); - - - const vk::PresentInfoKHR presentInfoKHR{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], - .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex }; - result = queue.presentKHR( presentInfoKHR ); - if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { - framebufferResized = false; - recreateSwapChain(); - } else if (result != vk::Result::eSuccess) { - throw std::runtime_error("failed to present swap chain image!"); - } - semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; - } - - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size() * sizeof(char), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static vk::Format chooseSwapSurfaceFormat(const std::vector& availableFormats) { - const auto formatIt = std::ranges::find_if(availableFormats, - [](const auto& format) { - return format.format == vk::Format::eB8G8R8A8Srgb && - format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; - }); - return formatIt != availableFormats.end() ? formatIt->format : availableFormats[0].format; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : 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) - }; - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - 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: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::Format swapChainImageFormat = vk::Format::eUndefined; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + + vk::raii::CommandPool commandPool = nullptr; + std::vector commandBuffers; + + std::vector presentCompleteSemaphore; + std::vector renderFinishedSemaphore; + std::vector inFlightFences; + uint32_t semaphoreIndex = 0; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow *window, int width, int height) + { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createGraphicsPipeline(); + createCommandPool(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() + { + while (!glfwWindowShouldClose(window)) + { + glfwPollEvents(); + drawFrame(); + } + + device.waitIdle(); + } + + void cleanupSwapChain() + { + swapChainImageViews.clear(); + swapChain = nullptr; + } + + void cleanup() + { + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() + { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) + { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + device.waitIdle(); + + cleanupSwapChain(); + createSwapChain(); + createImageViews(); + } + + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain featureChain = { + {}, // vk::PhysicalDeviceFeatures2 + {.synchronization2 = true, .dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface); + swapChainImageFormat = 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{ + .surface = surface, .minImageCount = minImageCount, .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + 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(device, imageViewCreateInfo); + } + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; + + vk::PipelineVertexInputStateCreateInfo vertexInputInfo; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{.topology = vk::PrimitiveTopology::eTriangleList}; + vk::PipelineViewportStateCreateInfo viewportState{.viewportCount = 1, .scissorCount = 1}; + + vk::PipelineRasterizationStateCreateInfo rasterizer{.depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = vk::False, .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f}; + + vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; + + vk::PipelineColorBlendAttachmentState colorBlendAttachment{.blendEnable = vk::False, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA}; + + vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 0, .pushConstantRangeCount = 0}; + + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainImageFormat}; + vk::GraphicsPipelineCreateInfo pipelineInfo{.pNext = &pipelineRenderingCreateInfo, + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex}; + commandPool = vk::raii::CommandPool(device, poolInfo); + } + + void createCommandBuffers() + { + commandBuffers.clear(); + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = MAX_FRAMES_IN_FLIGHT}; + commandBuffers = vk::raii::CommandBuffers(device, allocInfo); + } + + void recordCommandBuffer(uint32_t imageIndex) + { + commandBuffers[currentFrame].begin({}); + // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL + transition_image_layout( + imageIndex, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage + ); + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::RenderingAttachmentInfo attachmentInfo = { + .imageView = swapChainImageViews[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor}; + vk::RenderingInfo renderingInfo = { + .renderArea = {.offset = {0, 0}, .extent = swapChainExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &attachmentInfo}; + commandBuffers[currentFrame].beginRendering(renderingInfo); + commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffers[currentFrame].draw(3, 1, 0, 0); + commandBuffers[currentFrame].endRendering(); + // After rendering, transition the swapchain image to PRESENT_SRC + transition_image_layout( + imageIndex, + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage + ); + commandBuffers[currentFrame].end(); + } + + void transition_image_layout( + uint32_t imageIndex, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout, + vk::AccessFlags2 src_access_mask, + vk::AccessFlags2 dst_access_mask, + vk::PipelineStageFlags2 src_stage_mask, + vk::PipelineStageFlags2 dst_stage_mask) + { + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = src_stage_mask, + .srcAccessMask = src_access_mask, + .dstStageMask = dst_stage_mask, + .dstAccessMask = dst_access_mask, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo dependency_info = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + } + + void createSyncObjects() + { + presentCompleteSemaphore.clear(); + renderFinishedSemaphore.clear(); + inFlightFences.clear(); + + for (size_t i = 0; i < swapChainImages.size(); i++) + { + presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); + } + } + + void drawFrame() + { + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + ; + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr); + + if (result == vk::Result::eErrorOutOfDateKHR) + { + recreateSwapChain(); + return; + } + if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) + { + throw std::runtime_error("failed to acquire swap chain image!"); + } + + device.resetFences(*inFlightFences[currentFrame]); + commandBuffers[currentFrame].reset(); + recordCommandBuffer(imageIndex); + + vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[currentFrame]); + + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + result = queue.presentKHR(presentInfoKHR); + if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) + { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != vk::Result::eSuccess) + { + throw std::runtime_error("failed to present swap chain image!"); + } + semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size() * sizeof(char), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static vk::Format chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + const auto formatIt = std::ranges::find_if(availableFormats, + [](const auto &format) { + return format.format == vk::Format::eB8G8R8A8Srgb && + format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; + }); + return formatIt != availableFormats.end() ? formatIt->format : availableFormats[0].format; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + 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)}; + } + + std::vector getRequiredExtensions() + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + 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; + } }; -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/18_vertex_input.cpp b/attachments/18_vertex_input.cpp index 3412319b..86c28d98 100644 --- a/attachments/18_vertex_input.cpp +++ b/attachments/18_vertex_input.cpp @@ -1,33 +1,32 @@ -#include +#include +#include +#include +#include #include +#include +#include +#include #include #include -#include -#include -#include -#include -#include -#include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -constexpr int MAX_FRAMES_IN_FLIGHT = 2; +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; +constexpr int MAX_FRAMES_IN_FLIGHT = 2; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -35,589 +34,615 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -struct Vertex { - glm::vec2 pos; - glm::vec3 color; - - static vk::VertexInputBindingDescription getBindingDescription() { - return { 0, sizeof(Vertex), vk::VertexInputRate::eVertex }; - } - - static std::array getAttributeDescriptions() { - return { - vk::VertexInputAttributeDescription( 0, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, pos) ), - vk::VertexInputAttributeDescription( 1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color) ) - }; - } +struct Vertex +{ + glm::vec2 pos; + glm::vec3 color; + + static vk::VertexInputBindingDescription getBindingDescription() + { + return {0, sizeof(Vertex), vk::VertexInputRate::eVertex}; + } + + static std::array getAttributeDescriptions() + { + return { + vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, pos)), + vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color))}; + } }; const std::vector vertices = { {{0.0f, -0.5f}, {1.0f, 0.0f, 0.0f}}, {{0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}}, - {{-0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}} + {{-0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}}; + +class HelloTriangleApplication +{ + public: + void run() + { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + + private: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::Format swapChainImageFormat = vk::Format::eUndefined; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + + vk::raii::CommandPool commandPool = nullptr; + std::vector commandBuffers; + + std::vector presentCompleteSemaphore; + std::vector renderFinishedSemaphore; + std::vector inFlightFences; + uint32_t semaphoreIndex = 0; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow *window, int width, int height) + { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createGraphicsPipeline(); + createCommandPool(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() + { + while (!glfwWindowShouldClose(window)) + { + glfwPollEvents(); + drawFrame(); + } + + device.waitIdle(); + } + + void cleanupSwapChain() + { + swapChainImageViews.clear(); + swapChain = nullptr; + } + + void cleanup() + { + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() + { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) + { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + device.waitIdle(); + + cleanupSwapChain(); + createSwapChain(); + createImageViews(); + } + + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain featureChain = { + {}, // vk::PhysicalDeviceFeatures2 + {.synchronization2 = true, .dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface); + swapChainImageFormat = 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{ + .surface = surface, .minImageCount = minImageCount, .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + 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(device, imageViewCreateInfo); + } + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; + + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{.vertexBindingDescriptionCount = 1, .pVertexBindingDescriptions = &bindingDescription, .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), .pVertexAttributeDescriptions = attributeDescriptions.data()}; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{.topology = vk::PrimitiveTopology::eTriangleList}; + vk::PipelineViewportStateCreateInfo viewportState{.viewportCount = 1, .scissorCount = 1}; + + vk::PipelineRasterizationStateCreateInfo rasterizer{.depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = vk::False, .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f}; + + vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; + + vk::PipelineColorBlendAttachmentState colorBlendAttachment{.blendEnable = vk::False, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA}; + + vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 0, .pushConstantRangeCount = 0}; + + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainImageFormat}; + vk::GraphicsPipelineCreateInfo pipelineInfo{.pNext = &pipelineRenderingCreateInfo, + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex}; + commandPool = vk::raii::CommandPool(device, poolInfo); + } + + void createCommandBuffers() + { + commandBuffers.clear(); + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = MAX_FRAMES_IN_FLIGHT}; + commandBuffers = vk::raii::CommandBuffers(device, allocInfo); + } + + void recordCommandBuffer(uint32_t imageIndex) + { + commandBuffers[currentFrame].begin({}); + // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL + transition_image_layout( + imageIndex, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage + ); + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::RenderingAttachmentInfo attachmentInfo = { + .imageView = swapChainImageViews[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor}; + vk::RenderingInfo renderingInfo = { + .renderArea = {.offset = {0, 0}, .extent = swapChainExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &attachmentInfo}; + commandBuffers[currentFrame].beginRendering(renderingInfo); + commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffers[currentFrame].draw(3, 1, 0, 0); + commandBuffers[currentFrame].endRendering(); + // After rendering, transition the swapchain image to PRESENT_SRC + transition_image_layout( + imageIndex, + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage + ); + commandBuffers[currentFrame].end(); + } + + void transition_image_layout( + uint32_t imageIndex, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout, + vk::AccessFlags2 src_access_mask, + vk::AccessFlags2 dst_access_mask, + vk::PipelineStageFlags2 src_stage_mask, + vk::PipelineStageFlags2 dst_stage_mask) + { + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = src_stage_mask, + .srcAccessMask = src_access_mask, + .dstStageMask = dst_stage_mask, + .dstAccessMask = dst_access_mask, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo dependency_info = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + } + + void createSyncObjects() + { + presentCompleteSemaphore.clear(); + renderFinishedSemaphore.clear(); + inFlightFences.clear(); + + for (size_t i = 0; i < swapChainImages.size(); i++) + { + presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); + } + } + + void drawFrame() + { + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + ; + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr); + + if (result == vk::Result::eErrorOutOfDateKHR) + { + recreateSwapChain(); + return; + } + if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) + { + throw std::runtime_error("failed to acquire swap chain image!"); + } + + device.resetFences(*inFlightFences[currentFrame]); + commandBuffers[currentFrame].reset(); + recordCommandBuffer(imageIndex); + + vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[currentFrame]); + + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + result = queue.presentKHR(presentInfoKHR); + if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) + { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != vk::Result::eSuccess) + { + throw std::runtime_error("failed to present swap chain image!"); + } + semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size() * sizeof(char), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static vk::Format chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + const auto formatIt = std::ranges::find_if(availableFormats, + [](const auto &format) { + return format.format == vk::Format::eB8G8R8A8Srgb && + format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; + }); + return formatIt != availableFormats.end() ? formatIt->format : availableFormats[0].format; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + 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)}; + } + + std::vector getRequiredExtensions() + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + 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: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::Format swapChainImageFormat = vk::Format::eUndefined; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - - vk::raii::CommandPool commandPool = nullptr; - std::vector commandBuffers; - - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; - std::vector inFlightFences; - uint32_t semaphoreIndex = 0; - uint32_t currentFrame = 0; - - bool framebufferResized = false; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); - } - - static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { - auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); - app->framebufferResized = true; - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - createSurface(); - pickPhysicalDevice(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createGraphicsPipeline(); - createCommandPool(); - createCommandBuffers(); - createSyncObjects(); - } - - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - drawFrame(); - } - - device.waitIdle(); - } - - void cleanupSwapChain() { - swapChainImageViews.clear(); - swapChain = nullptr; - } - - void cleanup() { - glfwDestroyWindow(window); - - glfwTerminate(); - } - - void recreateSwapChain() { - int width = 0, height = 0; - glfwGetFramebufferSize(window, &width, &height); - while (width == 0 || height == 0) { - glfwGetFramebufferSize(window, &width, &height); - glfwWaitEvents(); - } - - device.waitIdle(); - - cleanupSwapChain(); - createSwapChain(); - createImageViews(); - } - - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - } ); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error( "failed to find a suitable GPU!" ); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain featureChain = { - {}, // vk::PhysicalDeviceFeatures2 - {.synchronization2 = true, .dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( surface ); - swapChainImageFormat = 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{ - .surface = surface, .minImageCount = minImageCount, - .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, - .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 }; - - swapChain = vk::raii::SwapchainKHR( device, swapChainCreateInfo ); - swapChainImages = swapChain.getImages(); - } - - 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( device, imageViewCreateInfo ); - } - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; - - auto bindingDescription = Vertex::getBindingDescription(); - auto attributeDescriptions = Vertex::getAttributeDescriptions(); - vk::PipelineVertexInputStateCreateInfo vertexInputInfo { .vertexBindingDescriptionCount =1, .pVertexBindingDescriptions = &bindingDescription, - .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), .pVertexAttributeDescriptions = attributeDescriptions.data() }; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ .topology = vk::PrimitiveTopology::eTriangleList }; - vk::PipelineViewportStateCreateInfo viewportState{ .viewportCount = 1, .scissorCount = 1 }; - - vk::PipelineRasterizationStateCreateInfo rasterizer{ .depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = vk::False, - .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f }; - - vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; - - vk::PipelineColorBlendAttachmentState colorBlendAttachment{ .blendEnable = vk::False, - .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA - }; - - vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 0, .pushConstantRangeCount = 0 }; - - pipelineLayout = vk::raii::PipelineLayout( device, pipelineLayoutInfo ); - - vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ .colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainImageFormat }; - vk::GraphicsPipelineCreateInfo pipelineInfo{ .pNext = &pipelineRenderingCreateInfo, - .stageCount = 2, .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, .layout = pipelineLayout, .renderPass = nullptr }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{ .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex }; - commandPool = vk::raii::CommandPool(device, poolInfo); - } - - void createCommandBuffers() { - commandBuffers.clear(); - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = MAX_FRAMES_IN_FLIGHT }; - commandBuffers = vk::raii::CommandBuffers( device, allocInfo ); - } - - void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin( {} ); - // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL - transition_image_layout( - imageIndex, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage - ); - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::RenderingAttachmentInfo attachmentInfo = { - .imageView = swapChainImageViews[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor - }; - vk::RenderingInfo renderingInfo = { - .renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &attachmentInfo - }; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor( 0, vk::Rect2D( vk::Offset2D( 0, 0 ), swapChainExtent ) ); - commandBuffers[currentFrame].draw(3, 1, 0, 0); - commandBuffers[currentFrame].endRendering(); - // After rendering, transition the swapchain image to PRESENT_SRC - transition_image_layout( - imageIndex, - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage - ); - commandBuffers[currentFrame].end(); - } - - void transition_image_layout( - uint32_t imageIndex, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::AccessFlags2 src_access_mask, - vk::AccessFlags2 dst_access_mask, - vk::PipelineStageFlags2 src_stage_mask, - vk::PipelineStageFlags2 dst_stage_mask - ) { - vk::ImageMemoryBarrier2 barrier = { - .srcStageMask = src_stage_mask, - .srcAccessMask = src_access_mask, - .dstStageMask = dst_stage_mask, - .dstAccessMask = dst_access_mask, - .oldLayout = old_layout, - .newLayout = new_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); - } - - void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); - - for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - } - - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - inFlightFences.emplace_back(device, vk::FenceCreateInfo { .flags = vk::FenceCreateFlagBits::eSignaled }); - } - } - - void drawFrame() { - while ( vk::Result::eTimeout == device.waitForFences( *inFlightFences[currentFrame], vk::True, UINT64_MAX ) ) - ; - auto [result, imageIndex] = swapChain.acquireNextImage( UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr ); - - if (result == vk::Result::eErrorOutOfDateKHR) { - recreateSwapChain(); - return; - } - if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) { - throw std::runtime_error("failed to acquire swap chain image!"); - } - - device.resetFences( *inFlightFences[currentFrame] ); - commandBuffers[currentFrame].reset(); - recordCommandBuffer(imageIndex); - - vk::PipelineStageFlags waitDestinationStageMask( vk::PipelineStageFlagBits::eColorAttachmentOutput ); - const vk::SubmitInfo submitInfo{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], - .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], - .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex] }; - queue.submit(submitInfo, *inFlightFences[currentFrame]); - - - const vk::PresentInfoKHR presentInfoKHR{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], - .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex }; - result = queue.presentKHR( presentInfoKHR ); - if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { - framebufferResized = false; - recreateSwapChain(); - } else if (result != vk::Result::eSuccess) { - throw std::runtime_error("failed to present swap chain image!"); - } - semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; - } - - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size() * sizeof(char), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static vk::Format chooseSwapSurfaceFormat(const std::vector& availableFormats) { - const auto formatIt = std::ranges::find_if(availableFormats, - [](const auto& format) { - return format.format == vk::Format::eB8G8R8A8Srgb && - format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; - }); - return formatIt != availableFormats.end() ? formatIt->format : availableFormats[0].format; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : 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) - }; - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - 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; - } -}; - -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/19_vertex_buffer.cpp b/attachments/19_vertex_buffer.cpp index 2a427649..1a5e1f67 100644 --- a/attachments/19_vertex_buffer.cpp +++ b/attachments/19_vertex_buffer.cpp @@ -1,33 +1,32 @@ -#include +#include +#include +#include +#include #include +#include +#include +#include #include #include -#include -#include -#include -#include -#include -#include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -constexpr int MAX_FRAMES_IN_FLIGHT = 2; +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; +constexpr int MAX_FRAMES_IN_FLIGHT = 2; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -35,619 +34,649 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -struct Vertex { - glm::vec2 pos; - glm::vec3 color; - - static vk::VertexInputBindingDescription getBindingDescription() { - return { 0, sizeof(Vertex), vk::VertexInputRate::eVertex }; - } - - static std::array getAttributeDescriptions() { - return { - vk::VertexInputAttributeDescription( 0, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, pos) ), - vk::VertexInputAttributeDescription( 1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color) ) - }; - } +struct Vertex +{ + glm::vec2 pos; + glm::vec3 color; + + static vk::VertexInputBindingDescription getBindingDescription() + { + return {0, sizeof(Vertex), vk::VertexInputRate::eVertex}; + } + + static std::array getAttributeDescriptions() + { + return { + vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, pos)), + vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color))}; + } }; const std::vector vertices = { {{0.0f, -0.5f}, {1.0f, 0.0f, 0.0f}}, {{0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}}, - {{-0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}} + {{-0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}}; + +class HelloTriangleApplication +{ + public: + void run() + { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + + private: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::Format swapChainImageFormat = vk::Format::eUndefined; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + + vk::raii::Buffer vertexBuffer = nullptr; + vk::raii::DeviceMemory vertexBufferMemory = nullptr; + + vk::raii::CommandPool commandPool = nullptr; + std::vector commandBuffers; + + std::vector presentCompleteSemaphore; + std::vector renderFinishedSemaphore; + std::vector inFlightFences; + uint32_t semaphoreIndex = 0; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow *window, int width, int height) + { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createGraphicsPipeline(); + createCommandPool(); + createVertexBuffer(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() + { + while (!glfwWindowShouldClose(window)) + { + glfwPollEvents(); + drawFrame(); + } + + device.waitIdle(); + } + + void cleanupSwapChain() + { + swapChainImageViews.clear(); + swapChain = nullptr; + } + + void cleanup() + { + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() + { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) + { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + device.waitIdle(); + + cleanupSwapChain(); + createSwapChain(); + createImageViews(); + } + + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain featureChain = { + {}, // vk::PhysicalDeviceFeatures2 + {.synchronization2 = true, .dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface); + swapChainImageFormat = 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{ + .surface = surface, .minImageCount = minImageCount, .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + void createImageViews() + { + 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(device, imageViewCreateInfo); + } + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; + + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{.vertexBindingDescriptionCount = 1, .pVertexBindingDescriptions = &bindingDescription, .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), .pVertexAttributeDescriptions = attributeDescriptions.data()}; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{.topology = vk::PrimitiveTopology::eTriangleList}; + vk::PipelineViewportStateCreateInfo viewportState{.viewportCount = 1, .scissorCount = 1}; + + vk::PipelineRasterizationStateCreateInfo rasterizer{.depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = vk::False, .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f}; + + vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; + + vk::PipelineColorBlendAttachmentState colorBlendAttachment{.blendEnable = vk::False, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA}; + + vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 0, .pushConstantRangeCount = 0}; + + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainImageFormat}; + vk::GraphicsPipelineCreateInfo pipelineInfo{.pNext = &pipelineRenderingCreateInfo, + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex}; + commandPool = vk::raii::CommandPool(device, poolInfo); + } + + void createVertexBuffer() + { + vk::BufferCreateInfo bufferInfo{.size = sizeof(vertices[0]) * vertices.size(), .usage = vk::BufferUsageFlagBits::eVertexBuffer, .sharingMode = vk::SharingMode::eExclusive}; + vertexBuffer = vk::raii::Buffer(device, bufferInfo); + + vk::MemoryRequirements memRequirements = vertexBuffer.getMemoryRequirements(); + vk::MemoryAllocateInfo memoryAllocateInfo{.allocationSize = memRequirements.size, .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent)}; + vertexBufferMemory = vk::raii::DeviceMemory(device, memoryAllocateInfo); + + vertexBuffer.bindMemory(*vertexBufferMemory, 0); + + void *data = vertexBufferMemory.mapMemory(0, bufferInfo.size); + memcpy(data, vertices.data(), bufferInfo.size); + vertexBufferMemory.unmapMemory(); + } + + uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) + { + vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) + { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) + { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + void createCommandBuffers() + { + commandBuffers.clear(); + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = MAX_FRAMES_IN_FLIGHT}; + commandBuffers = vk::raii::CommandBuffers(device, allocInfo); + } + + void recordCommandBuffer(uint32_t imageIndex) + { + commandBuffers[currentFrame].begin({}); + // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL + transition_image_layout( + imageIndex, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage + ); + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::RenderingAttachmentInfo attachmentInfo = { + .imageView = swapChainImageViews[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor}; + vk::RenderingInfo renderingInfo = { + .renderArea = {.offset = {0, 0}, .extent = swapChainExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &attachmentInfo}; + commandBuffers[currentFrame].beginRendering(renderingInfo); + commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffers[currentFrame].draw(3, 1, 0, 0); + commandBuffers[currentFrame].endRendering(); + // After rendering, transition the swapchain image to PRESENT_SRC + transition_image_layout( + imageIndex, + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage + ); + commandBuffers[currentFrame].end(); + } + + void transition_image_layout( + uint32_t imageIndex, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout, + vk::AccessFlags2 src_access_mask, + vk::AccessFlags2 dst_access_mask, + vk::PipelineStageFlags2 src_stage_mask, + vk::PipelineStageFlags2 dst_stage_mask) + { + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = src_stage_mask, + .srcAccessMask = src_access_mask, + .dstStageMask = dst_stage_mask, + .dstAccessMask = dst_access_mask, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo dependency_info = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + } + + void createSyncObjects() + { + presentCompleteSemaphore.clear(); + renderFinishedSemaphore.clear(); + inFlightFences.clear(); + + for (size_t i = 0; i < swapChainImages.size(); i++) + { + presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); + } + } + + void drawFrame() + { + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + ; + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr); + + if (result == vk::Result::eErrorOutOfDateKHR) + { + recreateSwapChain(); + return; + } + if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) + { + throw std::runtime_error("failed to acquire swap chain image!"); + } + + device.resetFences(*inFlightFences[currentFrame]); + commandBuffers[currentFrame].reset(); + recordCommandBuffer(imageIndex); + + vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[currentFrame]); + + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + result = queue.presentKHR(presentInfoKHR); + if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) + { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != vk::Result::eSuccess) + { + throw std::runtime_error("failed to present swap chain image!"); + } + semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size() * sizeof(char), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static vk::Format chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + const auto formatIt = std::ranges::find_if(availableFormats, + [](const auto &format) { + return format.format == vk::Format::eB8G8R8A8Srgb && + format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; + }); + return formatIt != availableFormats.end() ? formatIt->format : availableFormats[0].format; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + 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)}; + } + + std::vector getRequiredExtensions() + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + 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: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::Format swapChainImageFormat = vk::Format::eUndefined; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - - vk::raii::Buffer vertexBuffer = nullptr; - vk::raii::DeviceMemory vertexBufferMemory = nullptr; - - vk::raii::CommandPool commandPool = nullptr; - std::vector commandBuffers; - - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; - std::vector inFlightFences; - uint32_t semaphoreIndex = 0; - uint32_t currentFrame = 0; - - bool framebufferResized = false; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); - } - - static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { - auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); - app->framebufferResized = true; - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - createSurface(); - pickPhysicalDevice(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createGraphicsPipeline(); - createCommandPool(); - createVertexBuffer(); - createCommandBuffers(); - createSyncObjects(); - } - - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - drawFrame(); - } - - device.waitIdle(); - } - - void cleanupSwapChain() { - swapChainImageViews.clear(); - swapChain = nullptr; - } - - void cleanup() { - glfwDestroyWindow(window); - - glfwTerminate(); - } - - void recreateSwapChain() { - int width = 0, height = 0; - glfwGetFramebufferSize(window, &width, &height); - while (width == 0 || height == 0) { - glfwGetFramebufferSize(window, &width, &height); - glfwWaitEvents(); - } - - device.waitIdle(); - - cleanupSwapChain(); - createSwapChain(); - createImageViews(); - } - - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - } ); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error( "failed to find a suitable GPU!" ); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain featureChain = { - {}, // vk::PhysicalDeviceFeatures2 - {.synchronization2 = true, .dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( surface ); - swapChainImageFormat = 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{ - .surface = surface, .minImageCount = minImageCount, - .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, - .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 }; - - swapChain = vk::raii::SwapchainKHR( device, swapChainCreateInfo ); - swapChainImages = swapChain.getImages(); - } - - void createImageViews() { - 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( device, imageViewCreateInfo ); - } - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; - - auto bindingDescription = Vertex::getBindingDescription(); - auto attributeDescriptions = Vertex::getAttributeDescriptions(); - vk::PipelineVertexInputStateCreateInfo vertexInputInfo { .vertexBindingDescriptionCount =1, .pVertexBindingDescriptions = &bindingDescription, - .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), .pVertexAttributeDescriptions = attributeDescriptions.data() }; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ .topology = vk::PrimitiveTopology::eTriangleList }; - vk::PipelineViewportStateCreateInfo viewportState{ .viewportCount = 1, .scissorCount = 1 }; - - vk::PipelineRasterizationStateCreateInfo rasterizer{ .depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = vk::False, - .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f }; - - vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; - - vk::PipelineColorBlendAttachmentState colorBlendAttachment{ .blendEnable = vk::False, - .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA - }; - - vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 0, .pushConstantRangeCount = 0 }; - - pipelineLayout = vk::raii::PipelineLayout( device, pipelineLayoutInfo ); - - vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ .colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainImageFormat }; - vk::GraphicsPipelineCreateInfo pipelineInfo{ .pNext = &pipelineRenderingCreateInfo, - .stageCount = 2, .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, .layout = pipelineLayout, .renderPass = nullptr }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{ .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex }; - commandPool = vk::raii::CommandPool(device, poolInfo); - } - - void createVertexBuffer() { - vk::BufferCreateInfo bufferInfo{ .size = sizeof(vertices[0]) * vertices.size(), .usage = vk::BufferUsageFlagBits::eVertexBuffer, .sharingMode = vk::SharingMode::eExclusive }; - vertexBuffer = vk::raii::Buffer(device, bufferInfo); - - vk::MemoryRequirements memRequirements = vertexBuffer.getMemoryRequirements(); - vk::MemoryAllocateInfo memoryAllocateInfo{ .allocationSize = memRequirements.size, .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent) }; - vertexBufferMemory = vk::raii::DeviceMemory( device, memoryAllocateInfo ); - - vertexBuffer.bindMemory( *vertexBufferMemory, 0 ); - - void* data = vertexBufferMemory.mapMemory(0, bufferInfo.size); - memcpy(data, vertices.data(), bufferInfo.size); - vertexBufferMemory.unmapMemory(); - } - - uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) { - vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); - - for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { - if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { - return i; - } - } - - throw std::runtime_error("failed to find suitable memory type!"); - } - - void createCommandBuffers() { - commandBuffers.clear(); - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = MAX_FRAMES_IN_FLIGHT }; - commandBuffers = vk::raii::CommandBuffers( device, allocInfo ); - } - - void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin( {} ); - // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL - transition_image_layout( - imageIndex, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage - ); - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::RenderingAttachmentInfo attachmentInfo = { - .imageView = swapChainImageViews[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor - }; - vk::RenderingInfo renderingInfo = { - .renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &attachmentInfo - }; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor( 0, vk::Rect2D( vk::Offset2D( 0, 0 ), swapChainExtent ) ); - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].draw(3, 1, 0, 0); - commandBuffers[currentFrame].endRendering(); - // After rendering, transition the swapchain image to PRESENT_SRC - transition_image_layout( - imageIndex, - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage - ); - commandBuffers[currentFrame].end(); - } - - void transition_image_layout( - uint32_t imageIndex, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::AccessFlags2 src_access_mask, - vk::AccessFlags2 dst_access_mask, - vk::PipelineStageFlags2 src_stage_mask, - vk::PipelineStageFlags2 dst_stage_mask - ) { - vk::ImageMemoryBarrier2 barrier = { - .srcStageMask = src_stage_mask, - .srcAccessMask = src_access_mask, - .dstStageMask = dst_stage_mask, - .dstAccessMask = dst_access_mask, - .oldLayout = old_layout, - .newLayout = new_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); - } - - void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); - - for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - } - - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - inFlightFences.emplace_back(device, vk::FenceCreateInfo { .flags = vk::FenceCreateFlagBits::eSignaled }); - } - } - - void drawFrame() { - while ( vk::Result::eTimeout == device.waitForFences( *inFlightFences[currentFrame], vk::True, UINT64_MAX ) ) - ; - auto [result, imageIndex] = swapChain.acquireNextImage( UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr ); - - if (result == vk::Result::eErrorOutOfDateKHR) { - recreateSwapChain(); - return; - } - if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) { - throw std::runtime_error("failed to acquire swap chain image!"); - } - - device.resetFences( *inFlightFences[currentFrame] ); - commandBuffers[currentFrame].reset(); - recordCommandBuffer(imageIndex); - - vk::PipelineStageFlags waitDestinationStageMask( vk::PipelineStageFlagBits::eColorAttachmentOutput ); - const vk::SubmitInfo submitInfo{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], - .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], - .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex] }; - queue.submit(submitInfo, *inFlightFences[currentFrame]); - - - const vk::PresentInfoKHR presentInfoKHR{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], - .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex }; - result = queue.presentKHR( presentInfoKHR ); - if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { - framebufferResized = false; - recreateSwapChain(); - } else if (result != vk::Result::eSuccess) { - throw std::runtime_error("failed to present swap chain image!"); - } - semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; - } - - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size() * sizeof(char), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static vk::Format chooseSwapSurfaceFormat(const std::vector& availableFormats) { - const auto formatIt = std::ranges::find_if(availableFormats, - [](const auto& format) { - return format.format == vk::Format::eB8G8R8A8Srgb && - format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; - }); - return formatIt != availableFormats.end() ? formatIt->format : availableFormats[0].format; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : 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) - }; - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - 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; - } -}; - -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/20_staging_buffer.cpp b/attachments/20_staging_buffer.cpp index 85023fef..9be8c867 100644 --- a/attachments/20_staging_buffer.cpp +++ b/attachments/20_staging_buffer.cpp @@ -1,33 +1,32 @@ -#include +#include +#include +#include +#include #include +#include +#include +#include #include #include -#include -#include -#include -#include -#include -#include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -constexpr int MAX_FRAMES_IN_FLIGHT = 2; +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; +constexpr int MAX_FRAMES_IN_FLIGHT = 2; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -35,638 +34,669 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -struct Vertex { - glm::vec2 pos; - glm::vec3 color; - - static vk::VertexInputBindingDescription getBindingDescription() { - return { 0, sizeof(Vertex), vk::VertexInputRate::eVertex }; - } - - static std::array getAttributeDescriptions() { - return { - vk::VertexInputAttributeDescription( 0, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, pos) ), - vk::VertexInputAttributeDescription( 1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color) ) - }; - } +struct Vertex +{ + glm::vec2 pos; + glm::vec3 color; + + static vk::VertexInputBindingDescription getBindingDescription() + { + return {0, sizeof(Vertex), vk::VertexInputRate::eVertex}; + } + + static std::array getAttributeDescriptions() + { + return { + vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, pos)), + vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color))}; + } }; const std::vector vertices = { {{0.0f, -0.5f}, {1.0f, 0.0f, 0.0f}}, {{0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}}, - {{-0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}} + {{-0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}}; + +class HelloTriangleApplication +{ + public: + void run() + { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + + private: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::Format swapChainImageFormat = vk::Format::eUndefined; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + + vk::raii::Buffer vertexBuffer = nullptr; + vk::raii::DeviceMemory vertexBufferMemory = nullptr; + + vk::raii::CommandPool commandPool = nullptr; + std::vector commandBuffers; + + std::vector presentCompleteSemaphore; + std::vector renderFinishedSemaphore; + std::vector inFlightFences; + uint32_t semaphoreIndex = 0; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow *window, int width, int height) + { + auto app = static_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createGraphicsPipeline(); + createCommandPool(); + createVertexBuffer(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() + { + while (!glfwWindowShouldClose(window)) + { + glfwPollEvents(); + drawFrame(); + } + + device.waitIdle(); + } + + void cleanupSwapChain() + { + swapChainImageViews.clear(); + swapChain = nullptr; + } + + void cleanup() + { + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() + { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) + { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + device.waitIdle(); + + cleanupSwapChain(); + createSwapChain(); + createImageViews(); + } + + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain featureChain = { + {}, // vk::PhysicalDeviceFeatures2 + {.synchronization2 = true, .dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface); + swapChainImageFormat = 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{ + .surface = surface, .minImageCount = minImageCount, .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + void createImageViews() + { + 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(device, imageViewCreateInfo); + } + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; + + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{.vertexBindingDescriptionCount = 1, .pVertexBindingDescriptions = &bindingDescription, .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), .pVertexAttributeDescriptions = attributeDescriptions.data()}; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{.topology = vk::PrimitiveTopology::eTriangleList}; + vk::PipelineViewportStateCreateInfo viewportState{.viewportCount = 1, .scissorCount = 1}; + + vk::PipelineRasterizationStateCreateInfo rasterizer{.depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = vk::False, .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f}; + + vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; + + vk::PipelineColorBlendAttachmentState colorBlendAttachment{.blendEnable = vk::False, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA}; + + vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 0, .pushConstantRangeCount = 0}; + + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainImageFormat}; + vk::GraphicsPipelineCreateInfo pipelineInfo{.pNext = &pipelineRenderingCreateInfo, + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex}; + commandPool = vk::raii::CommandPool(device, poolInfo); + } + + void createVertexBuffer() + { + vk::BufferCreateInfo stagingInfo{.size = sizeof(vertices[0]) * vertices.size(), .usage = vk::BufferUsageFlagBits::eTransferSrc, .sharingMode = vk::SharingMode::eExclusive}; + vk::raii::Buffer stagingBuffer(device, stagingInfo); + vk::MemoryRequirements memRequirementsStaging = stagingBuffer.getMemoryRequirements(); + vk::MemoryAllocateInfo memoryAllocateInfoStaging{.allocationSize = memRequirementsStaging.size, .memoryTypeIndex = findMemoryType(memRequirementsStaging.memoryTypeBits, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent)}; + vk::raii::DeviceMemory stagingBufferMemory(device, memoryAllocateInfoStaging); + + stagingBuffer.bindMemory(stagingBufferMemory, 0); + void *dataStaging = stagingBufferMemory.mapMemory(0, stagingInfo.size); + memcpy(dataStaging, vertices.data(), stagingInfo.size); + stagingBufferMemory.unmapMemory(); + + vk::BufferCreateInfo bufferInfo{.size = sizeof(vertices[0]) * vertices.size(), .usage = vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eTransferDst, .sharingMode = vk::SharingMode::eExclusive}; + vertexBuffer = vk::raii::Buffer(device, bufferInfo); + + vk::MemoryRequirements memRequirements = vertexBuffer.getMemoryRequirements(); + vk::MemoryAllocateInfo memoryAllocateInfo{.allocationSize = memRequirements.size, .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, vk::MemoryPropertyFlagBits::eDeviceLocal)}; + vertexBufferMemory = vk::raii::DeviceMemory(device, memoryAllocateInfo); + + vertexBuffer.bindMemory(*vertexBufferMemory, 0); + + copyBuffer(stagingBuffer, vertexBuffer, stagingInfo.size); + } + + void copyBuffer(vk::raii::Buffer &srcBuffer, vk::raii::Buffer &dstBuffer, vk::DeviceSize size) + { + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1}; + vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); + commandCopyBuffer.begin(vk::CommandBufferBeginInfo{.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); + commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy(0, 0, size)); + commandCopyBuffer.end(); + queue.submit(vk::SubmitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer}, nullptr); + queue.waitIdle(); + } + + uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) + { + vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) + { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) + { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + void createCommandBuffers() + { + commandBuffers.clear(); + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = MAX_FRAMES_IN_FLIGHT}; + commandBuffers = vk::raii::CommandBuffers(device, allocInfo); + } + + void recordCommandBuffer(uint32_t imageIndex) + { + commandBuffers[currentFrame].begin({}); + // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL + transition_image_layout( + imageIndex, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage + ); + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::RenderingAttachmentInfo attachmentInfo = { + .imageView = swapChainImageViews[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor}; + vk::RenderingInfo renderingInfo = { + .renderArea = {.offset = {0, 0}, .extent = swapChainExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &attachmentInfo}; + commandBuffers[currentFrame].beginRendering(renderingInfo); + commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffers[currentFrame].draw(3, 1, 0, 0); + commandBuffers[currentFrame].endRendering(); + // After rendering, transition the swapchain image to PRESENT_SRC + transition_image_layout( + imageIndex, + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage + ); + commandBuffers[currentFrame].end(); + } + + void transition_image_layout( + uint32_t imageIndex, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout, + vk::AccessFlags2 src_access_mask, + vk::AccessFlags2 dst_access_mask, + vk::PipelineStageFlags2 src_stage_mask, + vk::PipelineStageFlags2 dst_stage_mask) + { + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = src_stage_mask, + .srcAccessMask = src_access_mask, + .dstStageMask = dst_stage_mask, + .dstAccessMask = dst_access_mask, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo dependency_info = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + } + + void createSyncObjects() + { + presentCompleteSemaphore.clear(); + renderFinishedSemaphore.clear(); + inFlightFences.clear(); + + for (size_t i = 0; i < swapChainImages.size(); i++) + { + presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); + } + } + + void drawFrame() + { + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + ; + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr); + + if (result == vk::Result::eErrorOutOfDateKHR) + { + recreateSwapChain(); + return; + } + if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) + { + throw std::runtime_error("failed to acquire swap chain image!"); + } + + device.resetFences(*inFlightFences[currentFrame]); + commandBuffers[currentFrame].reset(); + recordCommandBuffer(imageIndex); + + vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[currentFrame]); + + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + result = queue.presentKHR(presentInfoKHR); + if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) + { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != vk::Result::eSuccess) + { + throw std::runtime_error("failed to present swap chain image!"); + } + semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size() * sizeof(char), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static vk::Format chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + const auto formatIt = std::ranges::find_if(availableFormats, + [](const auto &format) { + return format.format == vk::Format::eB8G8R8A8Srgb && + format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; + }); + return formatIt != availableFormats.end() ? formatIt->format : availableFormats[0].format; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + 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)}; + } + + std::vector getRequiredExtensions() + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + 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: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::Format swapChainImageFormat = vk::Format::eUndefined; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - - vk::raii::Buffer vertexBuffer = nullptr; - vk::raii::DeviceMemory vertexBufferMemory = nullptr; - - vk::raii::CommandPool commandPool = nullptr; - std::vector commandBuffers; - - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; - std::vector inFlightFences; - uint32_t semaphoreIndex = 0; - uint32_t currentFrame = 0; - - bool framebufferResized = false; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); - } - - static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { - auto app = static_cast(glfwGetWindowUserPointer(window)); - app->framebufferResized = true; - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - createSurface(); - pickPhysicalDevice(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createGraphicsPipeline(); - createCommandPool(); - createVertexBuffer(); - createCommandBuffers(); - createSyncObjects(); - } - - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - drawFrame(); - } - - device.waitIdle(); - } - - void cleanupSwapChain() { - swapChainImageViews.clear(); - swapChain = nullptr; - } - - void cleanup() { - glfwDestroyWindow(window); - - glfwTerminate(); - } - - void recreateSwapChain() { - int width = 0, height = 0; - glfwGetFramebufferSize(window, &width, &height); - while (width == 0 || height == 0) { - glfwGetFramebufferSize(window, &width, &height); - glfwWaitEvents(); - } - - device.waitIdle(); - - cleanupSwapChain(); - createSwapChain(); - createImageViews(); - } - - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - } ); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error( "failed to find a suitable GPU!" ); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain featureChain = { - {}, // vk::PhysicalDeviceFeatures2 - {.synchronization2 = true, .dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( surface ); - swapChainImageFormat = 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{ - .surface = surface, .minImageCount = minImageCount, - .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, - .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 }; - - swapChain = vk::raii::SwapchainKHR( device, swapChainCreateInfo ); - swapChainImages = swapChain.getImages(); - } - - void createImageViews() { - 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( device, imageViewCreateInfo ); - } - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; - - auto bindingDescription = Vertex::getBindingDescription(); - auto attributeDescriptions = Vertex::getAttributeDescriptions(); - vk::PipelineVertexInputStateCreateInfo vertexInputInfo { .vertexBindingDescriptionCount =1, .pVertexBindingDescriptions = &bindingDescription, - .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), .pVertexAttributeDescriptions = attributeDescriptions.data() }; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ .topology = vk::PrimitiveTopology::eTriangleList }; - vk::PipelineViewportStateCreateInfo viewportState{ .viewportCount = 1, .scissorCount = 1 }; - - vk::PipelineRasterizationStateCreateInfo rasterizer{ .depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = vk::False, - .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f }; - - vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; - - vk::PipelineColorBlendAttachmentState colorBlendAttachment{ .blendEnable = vk::False, - .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA - }; - - vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 0, .pushConstantRangeCount = 0 }; - - pipelineLayout = vk::raii::PipelineLayout( device, pipelineLayoutInfo ); - - vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ .colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainImageFormat }; - vk::GraphicsPipelineCreateInfo pipelineInfo{ .pNext = &pipelineRenderingCreateInfo, - .stageCount = 2, .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, .layout = pipelineLayout, .renderPass = nullptr }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{ .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex }; - commandPool = vk::raii::CommandPool(device, poolInfo); - } - - void createVertexBuffer() { - vk::BufferCreateInfo stagingInfo{ .size = sizeof(vertices[0]) * vertices.size(), .usage = vk::BufferUsageFlagBits::eTransferSrc, .sharingMode = vk::SharingMode::eExclusive }; - vk::raii::Buffer stagingBuffer(device, stagingInfo); - vk::MemoryRequirements memRequirementsStaging = stagingBuffer.getMemoryRequirements(); - vk::MemoryAllocateInfo memoryAllocateInfoStaging{ .allocationSize = memRequirementsStaging.size, .memoryTypeIndex = findMemoryType(memRequirementsStaging.memoryTypeBits, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent) }; - vk::raii::DeviceMemory stagingBufferMemory(device, memoryAllocateInfoStaging); - - stagingBuffer.bindMemory(stagingBufferMemory, 0); - void* dataStaging = stagingBufferMemory.mapMemory(0, stagingInfo.size); - memcpy(dataStaging, vertices.data(), stagingInfo.size); - stagingBufferMemory.unmapMemory(); - - vk::BufferCreateInfo bufferInfo{ .size = sizeof(vertices[0]) * vertices.size(), .usage = vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eTransferDst, .sharingMode = vk::SharingMode::eExclusive }; - vertexBuffer = vk::raii::Buffer(device, bufferInfo); - - vk::MemoryRequirements memRequirements = vertexBuffer.getMemoryRequirements(); - vk::MemoryAllocateInfo memoryAllocateInfo{ .allocationSize = memRequirements.size, .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, vk::MemoryPropertyFlagBits::eDeviceLocal) }; - vertexBufferMemory = vk::raii::DeviceMemory( device, memoryAllocateInfo ); - - vertexBuffer.bindMemory( *vertexBufferMemory, 0 ); - - copyBuffer(stagingBuffer, vertexBuffer, stagingInfo.size); - } - - void copyBuffer(vk::raii::Buffer & srcBuffer, vk::raii::Buffer & dstBuffer, vk::DeviceSize size) { - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1 }; - vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); - commandCopyBuffer.begin(vk::CommandBufferBeginInfo { .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit }); - commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy(0, 0, size)); - commandCopyBuffer.end(); - queue.submit(vk::SubmitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer }, nullptr); - queue.waitIdle(); - } - - uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) { - vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); - - for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { - if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { - return i; - } - } - - throw std::runtime_error("failed to find suitable memory type!"); - } - - void createCommandBuffers() { - commandBuffers.clear(); - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = MAX_FRAMES_IN_FLIGHT }; - commandBuffers = vk::raii::CommandBuffers( device, allocInfo ); - } - - void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin( {} ); - // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL - transition_image_layout( - imageIndex, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage - ); - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::RenderingAttachmentInfo attachmentInfo = { - .imageView = swapChainImageViews[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor - }; - vk::RenderingInfo renderingInfo = { - .renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &attachmentInfo - }; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor( 0, vk::Rect2D( vk::Offset2D( 0, 0 ), swapChainExtent ) ); - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].draw(3, 1, 0, 0); - commandBuffers[currentFrame].endRendering(); - // After rendering, transition the swapchain image to PRESENT_SRC - transition_image_layout( - imageIndex, - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage - ); - commandBuffers[currentFrame].end(); - } - - void transition_image_layout( - uint32_t imageIndex, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::AccessFlags2 src_access_mask, - vk::AccessFlags2 dst_access_mask, - vk::PipelineStageFlags2 src_stage_mask, - vk::PipelineStageFlags2 dst_stage_mask - ) { - vk::ImageMemoryBarrier2 barrier = { - .srcStageMask = src_stage_mask, - .srcAccessMask = src_access_mask, - .dstStageMask = dst_stage_mask, - .dstAccessMask = dst_access_mask, - .oldLayout = old_layout, - .newLayout = new_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); - } - - void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); - - for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - } - - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - inFlightFences.emplace_back(device, vk::FenceCreateInfo { .flags = vk::FenceCreateFlagBits::eSignaled }); - } - } - - void drawFrame() { - while ( vk::Result::eTimeout == device.waitForFences( *inFlightFences[currentFrame], vk::True, UINT64_MAX ) ) - ; - auto [result, imageIndex] = swapChain.acquireNextImage( UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr ); - - if (result == vk::Result::eErrorOutOfDateKHR) { - recreateSwapChain(); - return; - } - if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) { - throw std::runtime_error("failed to acquire swap chain image!"); - } - - device.resetFences( *inFlightFences[currentFrame] ); - commandBuffers[currentFrame].reset(); - recordCommandBuffer(imageIndex); - - vk::PipelineStageFlags waitDestinationStageMask( vk::PipelineStageFlagBits::eColorAttachmentOutput ); - const vk::SubmitInfo submitInfo{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], - .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], - .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex] }; - queue.submit(submitInfo, *inFlightFences[currentFrame]); - - - const vk::PresentInfoKHR presentInfoKHR{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], - .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex }; - result = queue.presentKHR( presentInfoKHR ); - if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { - framebufferResized = false; - recreateSwapChain(); - } else if (result != vk::Result::eSuccess) { - throw std::runtime_error("failed to present swap chain image!"); - } - semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; - } - - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size() * sizeof(char), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static vk::Format chooseSwapSurfaceFormat(const std::vector& availableFormats) { - const auto formatIt = std::ranges::find_if(availableFormats, - [](const auto& format) { - return format.format == vk::Format::eB8G8R8A8Srgb && - format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; - }); - return formatIt != availableFormats.end() ? formatIt->format : availableFormats[0].format; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : 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) - }; - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - 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; - } -}; - -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/21_index_buffer.cpp b/attachments/21_index_buffer.cpp index acae477f..e363f027 100644 --- a/attachments/21_index_buffer.cpp +++ b/attachments/21_index_buffer.cpp @@ -1,33 +1,32 @@ -#include +#include +#include +#include +#include #include +#include +#include +#include #include #include -#include -#include -#include -#include -#include -#include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -constexpr int MAX_FRAMES_IN_FLIGHT = 2; +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; +constexpr int MAX_FRAMES_IN_FLIGHT = 2; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -35,663 +34,695 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -struct Vertex { - glm::vec2 pos; - glm::vec3 color; - - static vk::VertexInputBindingDescription getBindingDescription() { - return { 0, sizeof(Vertex), vk::VertexInputRate::eVertex }; - } - - static std::array getAttributeDescriptions() { - return { - vk::VertexInputAttributeDescription( 0, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, pos) ), - vk::VertexInputAttributeDescription( 1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color) ) - }; - } +struct Vertex +{ + glm::vec2 pos; + glm::vec3 color; + + static vk::VertexInputBindingDescription getBindingDescription() + { + return {0, sizeof(Vertex), vk::VertexInputRate::eVertex}; + } + + static std::array getAttributeDescriptions() + { + return { + vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, pos)), + vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color))}; + } }; const std::vector vertices = { {{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}}, {{0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}}, {{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}, - {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}} -}; + {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}}}; const std::vector indices = { - 0, 1, 2, 2, 3, 0 + 0, 1, 2, 2, 3, 0}; + +class HelloTriangleApplication +{ + public: + void run() + { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + + private: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::Format swapChainImageFormat = vk::Format::eUndefined; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + + vk::raii::Buffer vertexBuffer = nullptr; + vk::raii::DeviceMemory vertexBufferMemory = nullptr; + vk::raii::Buffer indexBuffer = nullptr; + vk::raii::DeviceMemory indexBufferMemory = nullptr; + + vk::raii::CommandPool commandPool = nullptr; + std::vector commandBuffers; + + std::vector presentCompleteSemaphore; + std::vector renderFinishedSemaphore; + std::vector inFlightFences; + uint32_t semaphoreIndex = 0; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow *window, int width, int height) + { + auto app = static_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createGraphicsPipeline(); + createCommandPool(); + createVertexBuffer(); + createIndexBuffer(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() + { + while (!glfwWindowShouldClose(window)) + { + glfwPollEvents(); + drawFrame(); + } + + device.waitIdle(); + } + + void cleanupSwapChain() + { + swapChainImageViews.clear(); + swapChain = nullptr; + } + + void cleanup() + { + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() + { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) + { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + device.waitIdle(); + + cleanupSwapChain(); + createSwapChain(); + createImageViews(); + } + + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain featureChain = { + {}, // vk::PhysicalDeviceFeatures2 + {.synchronization2 = true, .dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface); + swapChainImageFormat = 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{ + .surface = surface, .minImageCount = minImageCount, .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + void createImageViews() + { + 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(device, imageViewCreateInfo); + } + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; + + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{.vertexBindingDescriptionCount = 1, .pVertexBindingDescriptions = &bindingDescription, .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), .pVertexAttributeDescriptions = attributeDescriptions.data()}; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{.topology = vk::PrimitiveTopology::eTriangleList}; + vk::PipelineViewportStateCreateInfo viewportState{.viewportCount = 1, .scissorCount = 1}; + + vk::PipelineRasterizationStateCreateInfo rasterizer{.depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = vk::False, .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f}; + + vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; + + vk::PipelineColorBlendAttachmentState colorBlendAttachment{.blendEnable = vk::False, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA}; + + vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 0, .pushConstantRangeCount = 0}; + + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainImageFormat}; + vk::GraphicsPipelineCreateInfo pipelineInfo{.pNext = &pipelineRenderingCreateInfo, + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex}; + commandPool = vk::raii::CommandPool(device, poolInfo); + } + + void createVertexBuffer() + { + vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(dataStaging, vertices.data(), bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + } + + void createIndexBuffer() + { + vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(data, indices.data(), (size_t) bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); + + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + } + + void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer &buffer, vk::raii::DeviceMemory &bufferMemory) + { + vk::BufferCreateInfo bufferInfo{.size = size, .usage = usage, .sharingMode = vk::SharingMode::eExclusive}; + buffer = vk::raii::Buffer(device, bufferInfo); + vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{.allocationSize = memRequirements.size, .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + bufferMemory = vk::raii::DeviceMemory(device, allocInfo); + buffer.bindMemory(bufferMemory, 0); + } + + void copyBuffer(vk::raii::Buffer &srcBuffer, vk::raii::Buffer &dstBuffer, vk::DeviceSize size) + { + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1}; + vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); + commandCopyBuffer.begin(vk::CommandBufferBeginInfo{.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); + commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy(0, 0, size)); + commandCopyBuffer.end(); + queue.submit(vk::SubmitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer}, nullptr); + queue.waitIdle(); + } + + uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) + { + vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) + { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) + { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + void createCommandBuffers() + { + commandBuffers.clear(); + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = MAX_FRAMES_IN_FLIGHT}; + commandBuffers = vk::raii::CommandBuffers(device, allocInfo); + } + + void recordCommandBuffer(uint32_t imageIndex) + { + commandBuffers[currentFrame].begin({}); + // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL + transition_image_layout( + imageIndex, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage + ); + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::RenderingAttachmentInfo attachmentInfo = { + .imageView = swapChainImageViews[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor}; + vk::RenderingInfo renderingInfo = { + .renderArea = {.offset = {0, 0}, .extent = swapChainExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &attachmentInfo}; + commandBuffers[currentFrame].beginRendering(renderingInfo); + commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexTypeValue::value); + commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); + commandBuffers[currentFrame].endRendering(); + // After rendering, transition the swapchain image to PRESENT_SRC + transition_image_layout( + imageIndex, + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage + ); + commandBuffers[currentFrame].end(); + } + + void transition_image_layout( + uint32_t imageIndex, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout, + vk::AccessFlags2 src_access_mask, + vk::AccessFlags2 dst_access_mask, + vk::PipelineStageFlags2 src_stage_mask, + vk::PipelineStageFlags2 dst_stage_mask) + { + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = src_stage_mask, + .srcAccessMask = src_access_mask, + .dstStageMask = dst_stage_mask, + .dstAccessMask = dst_access_mask, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo dependency_info = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + } + + void createSyncObjects() + { + presentCompleteSemaphore.clear(); + renderFinishedSemaphore.clear(); + inFlightFences.clear(); + + for (size_t i = 0; i < swapChainImages.size(); i++) + { + presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); + } + } + + void drawFrame() + { + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + ; + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr); + + if (result == vk::Result::eErrorOutOfDateKHR) + { + recreateSwapChain(); + return; + } + if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) + { + throw std::runtime_error("failed to acquire swap chain image!"); + } + + device.resetFences(*inFlightFences[currentFrame]); + commandBuffers[currentFrame].reset(); + recordCommandBuffer(imageIndex); + + vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[currentFrame]); + + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + result = queue.presentKHR(presentInfoKHR); + if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) + { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != vk::Result::eSuccess) + { + throw std::runtime_error("failed to present swap chain image!"); + } + semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size(), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static vk::Format chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + const auto formatIt = std::ranges::find_if(availableFormats, + [](const auto &format) { + return format.format == vk::Format::eB8G8R8A8Srgb && + format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; + }); + return formatIt != availableFormats.end() ? formatIt->format : availableFormats[0].format; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + 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)}; + } + + std::vector getRequiredExtensions() + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + 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: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::Format swapChainImageFormat = vk::Format::eUndefined; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - - vk::raii::Buffer vertexBuffer = nullptr; - vk::raii::DeviceMemory vertexBufferMemory = nullptr; - vk::raii::Buffer indexBuffer = nullptr; - vk::raii::DeviceMemory indexBufferMemory = nullptr; - - vk::raii::CommandPool commandPool = nullptr; - std::vector commandBuffers; - - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; - std::vector inFlightFences; - uint32_t semaphoreIndex = 0; - uint32_t currentFrame = 0; - - bool framebufferResized = false; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); - } - - static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { - auto app = static_cast(glfwGetWindowUserPointer(window)); - app->framebufferResized = true; - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - createSurface(); - pickPhysicalDevice(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createGraphicsPipeline(); - createCommandPool(); - createVertexBuffer(); - createIndexBuffer(); - createCommandBuffers(); - createSyncObjects(); - } - - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - drawFrame(); - } - - device.waitIdle(); - } - - void cleanupSwapChain() { - swapChainImageViews.clear(); - swapChain = nullptr; - } - - void cleanup() { - glfwDestroyWindow(window); - - glfwTerminate(); - } - - void recreateSwapChain() { - int width = 0, height = 0; - glfwGetFramebufferSize(window, &width, &height); - while (width == 0 || height == 0) { - glfwGetFramebufferSize(window, &width, &height); - glfwWaitEvents(); - } - - device.waitIdle(); - - cleanupSwapChain(); - createSwapChain(); - createImageViews(); - } - - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - } ); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error( "failed to find a suitable GPU!" ); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain featureChain = { - {}, // vk::PhysicalDeviceFeatures2 - {.synchronization2 = true, .dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( surface ); - swapChainImageFormat = 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{ - .surface = surface, .minImageCount = minImageCount, - .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, - .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 }; - - swapChain = vk::raii::SwapchainKHR( device, swapChainCreateInfo ); - swapChainImages = swapChain.getImages(); - } - - void createImageViews() { - 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( device, imageViewCreateInfo ); - } - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; - - auto bindingDescription = Vertex::getBindingDescription(); - auto attributeDescriptions = Vertex::getAttributeDescriptions(); - vk::PipelineVertexInputStateCreateInfo vertexInputInfo { .vertexBindingDescriptionCount =1, .pVertexBindingDescriptions = &bindingDescription, - .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), .pVertexAttributeDescriptions = attributeDescriptions.data() }; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ .topology = vk::PrimitiveTopology::eTriangleList }; - vk::PipelineViewportStateCreateInfo viewportState{ .viewportCount = 1, .scissorCount = 1 }; - - vk::PipelineRasterizationStateCreateInfo rasterizer{ .depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = vk::False, - .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f }; - - vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; - - vk::PipelineColorBlendAttachmentState colorBlendAttachment{ .blendEnable = vk::False, - .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA - }; - - vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 0, .pushConstantRangeCount = 0 }; - - pipelineLayout = vk::raii::PipelineLayout( device, pipelineLayoutInfo ); - - vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ .colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainImageFormat }; - vk::GraphicsPipelineCreateInfo pipelineInfo{ .pNext = &pipelineRenderingCreateInfo, - .stageCount = 2, .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, .layout = pipelineLayout, .renderPass = nullptr }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{ .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex }; - commandPool = vk::raii::CommandPool(device, poolInfo); - } - - void createVertexBuffer() { - vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(dataStaging, vertices.data(), bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); - - copyBuffer(stagingBuffer, vertexBuffer, bufferSize); - } - - void createIndexBuffer() { - vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); - - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(data, indices.data(), (size_t) bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); - - copyBuffer(stagingBuffer, indexBuffer, bufferSize); - } - - void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer& buffer, vk::raii::DeviceMemory& bufferMemory) { - vk::BufferCreateInfo bufferInfo{ .size = size, .usage = usage, .sharingMode = vk::SharingMode::eExclusive }; - buffer = vk::raii::Buffer(device, bufferInfo); - vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ .allocationSize =memRequirements.size, .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) }; - bufferMemory = vk::raii::DeviceMemory(device, allocInfo); - buffer.bindMemory(bufferMemory, 0); - } - - void copyBuffer(vk::raii::Buffer & srcBuffer, vk::raii::Buffer & dstBuffer, vk::DeviceSize size) { - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1 }; - vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); - commandCopyBuffer.begin(vk::CommandBufferBeginInfo { .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit }); - commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy(0, 0, size)); - commandCopyBuffer.end(); - queue.submit(vk::SubmitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer }, nullptr); - queue.waitIdle(); - } - - uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) { - vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); - - for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { - if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { - return i; - } - } - - throw std::runtime_error("failed to find suitable memory type!"); - } - - void createCommandBuffers() { - commandBuffers.clear(); - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = MAX_FRAMES_IN_FLIGHT }; - commandBuffers = vk::raii::CommandBuffers( device, allocInfo ); - } - - void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin( {} ); - // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL - transition_image_layout( - imageIndex, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage - ); - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::RenderingAttachmentInfo attachmentInfo = { - .imageView = swapChainImageViews[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor - }; - vk::RenderingInfo renderingInfo = { - .renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &attachmentInfo - }; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor( 0, vk::Rect2D( vk::Offset2D( 0, 0 ), swapChainExtent ) ); - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].bindIndexBuffer( *indexBuffer, 0, vk::IndexTypeValue::value ); - commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); - commandBuffers[currentFrame].endRendering(); - // After rendering, transition the swapchain image to PRESENT_SRC - transition_image_layout( - imageIndex, - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage - ); - commandBuffers[currentFrame].end(); - } - - void transition_image_layout( - uint32_t imageIndex, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::AccessFlags2 src_access_mask, - vk::AccessFlags2 dst_access_mask, - vk::PipelineStageFlags2 src_stage_mask, - vk::PipelineStageFlags2 dst_stage_mask - ) { - vk::ImageMemoryBarrier2 barrier = { - .srcStageMask = src_stage_mask, - .srcAccessMask = src_access_mask, - .dstStageMask = dst_stage_mask, - .dstAccessMask = dst_access_mask, - .oldLayout = old_layout, - .newLayout = new_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); - } - - void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); - - for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - } - - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - inFlightFences.emplace_back(device, vk::FenceCreateInfo { .flags = vk::FenceCreateFlagBits::eSignaled }); - } - } - - void drawFrame() { - while ( vk::Result::eTimeout == device.waitForFences( *inFlightFences[currentFrame], vk::True, UINT64_MAX ) ) - ; - auto [result, imageIndex] = swapChain.acquireNextImage( UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr ); - - if (result == vk::Result::eErrorOutOfDateKHR) { - recreateSwapChain(); - return; - } - if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) { - throw std::runtime_error("failed to acquire swap chain image!"); - } - - device.resetFences( *inFlightFences[currentFrame] ); - commandBuffers[currentFrame].reset(); - recordCommandBuffer(imageIndex); - - vk::PipelineStageFlags waitDestinationStageMask( vk::PipelineStageFlagBits::eColorAttachmentOutput ); - const vk::SubmitInfo submitInfo{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], - .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], - .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex] }; - queue.submit(submitInfo, *inFlightFences[currentFrame]); - - - const vk::PresentInfoKHR presentInfoKHR{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], - .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex }; - result = queue.presentKHR( presentInfoKHR ); - if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { - framebufferResized = false; - recreateSwapChain(); - } else if (result != vk::Result::eSuccess) { - throw std::runtime_error("failed to present swap chain image!"); - } - semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; - } - - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size(), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static vk::Format chooseSwapSurfaceFormat(const std::vector& availableFormats) { - const auto formatIt = std::ranges::find_if(availableFormats, - [](const auto& format) { - return format.format == vk::Format::eB8G8R8A8Srgb && - format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; - }); - return formatIt != availableFormats.end() ? formatIt->format : availableFormats[0].format; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : 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) - }; - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - 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; - } -}; - -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/22_descriptor_layout.cpp b/attachments/22_descriptor_layout.cpp index f0800e1e..af9c1cc4 100644 --- a/attachments/22_descriptor_layout.cpp +++ b/attachments/22_descriptor_layout.cpp @@ -1,37 +1,36 @@ -#include -#include -#include -#include -#include -#include -#include #include -#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include #define GLM_FORCE_RADIANS #include #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -constexpr int MAX_FRAMES_IN_FLIGHT = 2; +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; +constexpr int MAX_FRAMES_IN_FLIGHT = 2; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -39,714 +38,751 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -struct Vertex { - glm::vec2 pos; - glm::vec3 color; - - static vk::VertexInputBindingDescription getBindingDescription() { - return { 0, sizeof(Vertex), vk::VertexInputRate::eVertex }; - } - - static std::array getAttributeDescriptions() { - return { - vk::VertexInputAttributeDescription( 0, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, pos) ), - vk::VertexInputAttributeDescription( 1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color) ) - }; - } +struct Vertex +{ + glm::vec2 pos; + glm::vec3 color; + + static vk::VertexInputBindingDescription getBindingDescription() + { + return {0, sizeof(Vertex), vk::VertexInputRate::eVertex}; + } + + static std::array getAttributeDescriptions() + { + return { + vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, pos)), + vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color))}; + } }; -struct UniformBufferObject { - glm::mat4 model; - glm::mat4 view; - glm::mat4 proj; +struct UniformBufferObject +{ + glm::mat4 model; + glm::mat4 view; + glm::mat4 proj; }; const std::vector vertices = { {{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}}, {{0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}}, {{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}, - {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}} -}; + {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}}}; const std::vector indices = { - 0, 1, 2, 2, 3, 0 + 0, 1, 2, 2, 3, 0}; + +class HelloTriangleApplication +{ + public: + void run() + { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + + private: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::Format swapChainImageFormat = vk::Format::eUndefined; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + + vk::raii::Buffer vertexBuffer = nullptr; + vk::raii::DeviceMemory vertexBufferMemory = nullptr; + vk::raii::Buffer indexBuffer = nullptr; + vk::raii::DeviceMemory indexBufferMemory = nullptr; + + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + std::vector uniformBuffersMapped; + + vk::raii::CommandPool commandPool = nullptr; + std::vector commandBuffers; + + std::vector presentCompleteSemaphore; + std::vector renderFinishedSemaphore; + std::vector inFlightFences; + uint32_t semaphoreIndex = 0; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow *window, int width, int height) + { + auto app = static_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createDescriptorSetLayout(); + createGraphicsPipeline(); + createCommandPool(); + createVertexBuffer(); + createIndexBuffer(); + createUniformBuffers(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() + { + while (!glfwWindowShouldClose(window)) + { + glfwPollEvents(); + drawFrame(); + } + + device.waitIdle(); + } + + void cleanupSwapChain() + { + swapChainImageViews.clear(); + swapChain = nullptr; + } + + void cleanup() + { + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() + { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) + { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + device.waitIdle(); + + cleanupSwapChain(); + createSwapChain(); + createImageViews(); + } + + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain featureChain = { + {}, // vk::PhysicalDeviceFeatures2 + {.synchronization2 = true, .dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface); + swapChainImageFormat = 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{ + .surface = surface, .minImageCount = minImageCount, .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + void createImageViews() + { + 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(device, imageViewCreateInfo); + } + } + + void createDescriptorSetLayout() + { + vk::DescriptorSetLayoutBinding uboLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr); + vk::DescriptorSetLayoutCreateInfo layoutInfo{.bindingCount = 1, .pBindings = &uboLayoutBinding}; + descriptorSetLayout = vk::raii::DescriptorSetLayout(device, layoutInfo); + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; + + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{.vertexBindingDescriptionCount = 1, .pVertexBindingDescriptions = &bindingDescription, .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), .pVertexAttributeDescriptions = attributeDescriptions.data()}; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{.topology = vk::PrimitiveTopology::eTriangleList}; + vk::PipelineViewportStateCreateInfo viewportState{.viewportCount = 1, .scissorCount = 1}; + + vk::PipelineRasterizationStateCreateInfo rasterizer{.depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, .frontFace = vk::FrontFace::eCounterClockwise, .depthBiasEnable = vk::False, .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f}; + + vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; + + vk::PipelineColorBlendAttachmentState colorBlendAttachment{.blendEnable = vk::False, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA}; + + vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0}; + + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainImageFormat}; + vk::GraphicsPipelineCreateInfo pipelineInfo{.pNext = &pipelineRenderingCreateInfo, + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex}; + commandPool = vk::raii::CommandPool(device, poolInfo); + } + + void createVertexBuffer() + { + vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(dataStaging, vertices.data(), bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + } + + void createIndexBuffer() + { + vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(data, indices.data(), (size_t) bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); + + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + } + + void createUniformBuffers() + { + uniformBuffers.clear(); + uniformBuffersMemory.clear(); + uniformBuffersMapped.clear(); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DeviceSize bufferSize = sizeof(UniformBufferObject); + vk::raii::Buffer buffer({}); + vk::raii::DeviceMemory bufferMem({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); + uniformBuffers.emplace_back(std::move(buffer)); + uniformBuffersMemory.emplace_back(std::move(bufferMem)); + uniformBuffersMapped.emplace_back(uniformBuffersMemory[i].mapMemory(0, bufferSize)); + } + } + + void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer &buffer, vk::raii::DeviceMemory &bufferMemory) + { + vk::BufferCreateInfo bufferInfo{.size = size, .usage = usage, .sharingMode = vk::SharingMode::eExclusive}; + buffer = vk::raii::Buffer(device, bufferInfo); + vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{.allocationSize = memRequirements.size, .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + bufferMemory = vk::raii::DeviceMemory(device, allocInfo); + buffer.bindMemory(bufferMemory, 0); + } + + void copyBuffer(vk::raii::Buffer &srcBuffer, vk::raii::Buffer &dstBuffer, vk::DeviceSize size) + { + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1}; + vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); + commandCopyBuffer.begin(vk::CommandBufferBeginInfo{.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); + commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy(0, 0, size)); + commandCopyBuffer.end(); + queue.submit(vk::SubmitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer}, nullptr); + queue.waitIdle(); + } + + uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) + { + vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) + { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) + { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + void createCommandBuffers() + { + commandBuffers.clear(); + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = MAX_FRAMES_IN_FLIGHT}; + commandBuffers = vk::raii::CommandBuffers(device, allocInfo); + } + + void recordCommandBuffer(uint32_t imageIndex) + { + commandBuffers[currentFrame].begin({}); + // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL + transition_image_layout( + imageIndex, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage + ); + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::RenderingAttachmentInfo attachmentInfo = { + .imageView = swapChainImageViews[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor}; + vk::RenderingInfo renderingInfo = { + .renderArea = {.offset = {0, 0}, .extent = swapChainExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &attachmentInfo}; + commandBuffers[currentFrame].beginRendering(renderingInfo); + commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexTypeValue::value); + commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); + commandBuffers[currentFrame].endRendering(); + // After rendering, transition the swapchain image to PRESENT_SRC + transition_image_layout( + imageIndex, + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage + ); + commandBuffers[currentFrame].end(); + } + + void transition_image_layout( + uint32_t imageIndex, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout, + vk::AccessFlags2 src_access_mask, + vk::AccessFlags2 dst_access_mask, + vk::PipelineStageFlags2 src_stage_mask, + vk::PipelineStageFlags2 dst_stage_mask) + { + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = src_stage_mask, + .srcAccessMask = src_access_mask, + .dstStageMask = dst_stage_mask, + .dstAccessMask = dst_access_mask, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo dependency_info = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + } + + void createSyncObjects() + { + presentCompleteSemaphore.clear(); + renderFinishedSemaphore.clear(); + inFlightFences.clear(); + + for (size_t i = 0; i < swapChainImages.size(); i++) + { + presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); + } + } + + void updateUniformBuffer(uint32_t currentImage) + { + static auto startTime = std::chrono::high_resolution_clock::now(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + UniformBufferObject ubo{}; + ubo.model = rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.view = lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); + ubo.proj[1][1] *= -1; + + memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); + } + + void drawFrame() + { + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + ; + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr); + + if (result == vk::Result::eErrorOutOfDateKHR) + { + recreateSwapChain(); + return; + } + if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) + { + throw std::runtime_error("failed to acquire swap chain image!"); + } + updateUniformBuffer(currentFrame); + + device.resetFences(*inFlightFences[currentFrame]); + commandBuffers[currentFrame].reset(); + recordCommandBuffer(imageIndex); + + vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[currentFrame]); + + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + result = queue.presentKHR(presentInfoKHR); + if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) + { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != vk::Result::eSuccess) + { + throw std::runtime_error("failed to present swap chain image!"); + } + semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size(), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static vk::Format chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + const auto formatIt = std::ranges::find_if(availableFormats, + [](const auto &format) { + return format.format == vk::Format::eB8G8R8A8Srgb && + format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; + }); + return formatIt != availableFormats.end() ? formatIt->format : availableFormats[0].format; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + 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)}; + } + + std::vector getRequiredExtensions() + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + 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: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::Format swapChainImageFormat = vk::Format::eUndefined; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - - vk::raii::Buffer vertexBuffer = nullptr; - vk::raii::DeviceMemory vertexBufferMemory = nullptr; - vk::raii::Buffer indexBuffer = nullptr; - vk::raii::DeviceMemory indexBufferMemory = nullptr; - - std::vector uniformBuffers; - std::vector uniformBuffersMemory; - std::vector uniformBuffersMapped; - - vk::raii::CommandPool commandPool = nullptr; - std::vector commandBuffers; - - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; - std::vector inFlightFences; - uint32_t semaphoreIndex = 0; - uint32_t currentFrame = 0; - - bool framebufferResized = false; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); - } - - static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { - auto app = static_cast(glfwGetWindowUserPointer(window)); - app->framebufferResized = true; - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - createSurface(); - pickPhysicalDevice(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createDescriptorSetLayout(); - createGraphicsPipeline(); - createCommandPool(); - createVertexBuffer(); - createIndexBuffer(); - createUniformBuffers(); - createCommandBuffers(); - createSyncObjects(); - } - - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - drawFrame(); - } - - device.waitIdle(); - } - - void cleanupSwapChain() { - swapChainImageViews.clear(); - swapChain = nullptr; - } - - void cleanup() { - glfwDestroyWindow(window); - - glfwTerminate(); - } - - void recreateSwapChain() { - int width = 0, height = 0; - glfwGetFramebufferSize(window, &width, &height); - while (width == 0 || height == 0) { - glfwGetFramebufferSize(window, &width, &height); - glfwWaitEvents(); - } - - device.waitIdle(); - - cleanupSwapChain(); - createSwapChain(); - createImageViews(); - } - - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - } ); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error( "failed to find a suitable GPU!" ); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain featureChain = { - {}, // vk::PhysicalDeviceFeatures2 - {.synchronization2 = true, .dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( surface ); - swapChainImageFormat = 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{ - .surface = surface, .minImageCount = minImageCount, - .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, - .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 }; - - swapChain = vk::raii::SwapchainKHR( device, swapChainCreateInfo ); - swapChainImages = swapChain.getImages(); - } - - void createImageViews() { - 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( device, imageViewCreateInfo ); - } - } - - void createDescriptorSetLayout() { - vk::DescriptorSetLayoutBinding uboLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr); - vk::DescriptorSetLayoutCreateInfo layoutInfo{ .bindingCount = 1, .pBindings = &uboLayoutBinding }; - descriptorSetLayout = vk::raii::DescriptorSetLayout( device, layoutInfo ); - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; - - auto bindingDescription = Vertex::getBindingDescription(); - auto attributeDescriptions = Vertex::getAttributeDescriptions(); - vk::PipelineVertexInputStateCreateInfo vertexInputInfo { .vertexBindingDescriptionCount =1, .pVertexBindingDescriptions = &bindingDescription, - .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), .pVertexAttributeDescriptions = attributeDescriptions.data() }; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ .topology = vk::PrimitiveTopology::eTriangleList }; - vk::PipelineViewportStateCreateInfo viewportState{ .viewportCount = 1, .scissorCount = 1 }; - - vk::PipelineRasterizationStateCreateInfo rasterizer{ .depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eCounterClockwise, .depthBiasEnable = vk::False, - .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f }; - - vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; - - vk::PipelineColorBlendAttachmentState colorBlendAttachment{ .blendEnable = vk::False, - .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA - }; - - vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0 }; - - pipelineLayout = vk::raii::PipelineLayout( device, pipelineLayoutInfo ); - - vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ .colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainImageFormat }; - vk::GraphicsPipelineCreateInfo pipelineInfo{ .pNext = &pipelineRenderingCreateInfo, - .stageCount = 2, .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, .layout = pipelineLayout, .renderPass = nullptr }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{ .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex }; - commandPool = vk::raii::CommandPool(device, poolInfo); - } - - void createVertexBuffer() { - vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(dataStaging, vertices.data(), bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); - - copyBuffer(stagingBuffer, vertexBuffer, bufferSize); - } - - void createIndexBuffer() { - vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); - - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(data, indices.data(), (size_t) bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); - - copyBuffer(stagingBuffer, indexBuffer, bufferSize); - } - - void createUniformBuffers() { - uniformBuffers.clear(); - uniformBuffersMemory.clear(); - uniformBuffersMapped.clear(); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DeviceSize bufferSize = sizeof(UniformBufferObject); - vk::raii::Buffer buffer({}); - vk::raii::DeviceMemory bufferMem({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); - uniformBuffers.emplace_back(std::move(buffer)); - uniformBuffersMemory.emplace_back(std::move(bufferMem)); - uniformBuffersMapped.emplace_back( uniformBuffersMemory[i].mapMemory(0, bufferSize)); - } - } - - void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer& buffer, vk::raii::DeviceMemory& bufferMemory) { - vk::BufferCreateInfo bufferInfo{ .size = size, .usage = usage, .sharingMode = vk::SharingMode::eExclusive }; - buffer = vk::raii::Buffer(device, bufferInfo); - vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ .allocationSize =memRequirements.size, .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) }; - bufferMemory = vk::raii::DeviceMemory(device, allocInfo); - buffer.bindMemory(bufferMemory, 0); - } - - void copyBuffer(vk::raii::Buffer & srcBuffer, vk::raii::Buffer & dstBuffer, vk::DeviceSize size) { - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1 }; - vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); - commandCopyBuffer.begin(vk::CommandBufferBeginInfo { .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit }); - commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy(0, 0, size)); - commandCopyBuffer.end(); - queue.submit(vk::SubmitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer }, nullptr); - queue.waitIdle(); - } - - uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) { - vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); - - for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { - if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { - return i; - } - } - - throw std::runtime_error("failed to find suitable memory type!"); - } - - void createCommandBuffers() { - commandBuffers.clear(); - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = MAX_FRAMES_IN_FLIGHT }; - commandBuffers = vk::raii::CommandBuffers( device, allocInfo ); - } - - void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin( {} ); - // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL - transition_image_layout( - imageIndex, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage - ); - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::RenderingAttachmentInfo attachmentInfo = { - .imageView = swapChainImageViews[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor - }; - vk::RenderingInfo renderingInfo = { - .renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &attachmentInfo - }; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor( 0, vk::Rect2D( vk::Offset2D( 0, 0 ), swapChainExtent ) ); - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].bindIndexBuffer( *indexBuffer, 0, vk::IndexTypeValue::value ); - commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); - commandBuffers[currentFrame].endRendering(); - // After rendering, transition the swapchain image to PRESENT_SRC - transition_image_layout( - imageIndex, - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage - ); - commandBuffers[currentFrame].end(); - } - - void transition_image_layout( - uint32_t imageIndex, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::AccessFlags2 src_access_mask, - vk::AccessFlags2 dst_access_mask, - vk::PipelineStageFlags2 src_stage_mask, - vk::PipelineStageFlags2 dst_stage_mask - ) { - vk::ImageMemoryBarrier2 barrier = { - .srcStageMask = src_stage_mask, - .srcAccessMask = src_access_mask, - .dstStageMask = dst_stage_mask, - .dstAccessMask = dst_access_mask, - .oldLayout = old_layout, - .newLayout = new_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); - } - - void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); - - for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - } - - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - inFlightFences.emplace_back(device, vk::FenceCreateInfo { .flags = vk::FenceCreateFlagBits::eSignaled }); - } - } - - void updateUniformBuffer(uint32_t currentImage) { - static auto startTime = std::chrono::high_resolution_clock::now(); - - auto currentTime = std::chrono::high_resolution_clock::now(); - float time = std::chrono::duration(currentTime - startTime).count(); - - UniformBufferObject ubo{}; - ubo.model = rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.view = lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); - ubo.proj[1][1] *= -1; - - memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); - } - - void drawFrame() { - while ( vk::Result::eTimeout == device.waitForFences( *inFlightFences[currentFrame], vk::True, UINT64_MAX ) ) - ; - auto [result, imageIndex] = swapChain.acquireNextImage( UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr ); - - if (result == vk::Result::eErrorOutOfDateKHR) { - recreateSwapChain(); - return; - } - if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) { - throw std::runtime_error("failed to acquire swap chain image!"); - } - updateUniformBuffer(currentFrame); - - device.resetFences( *inFlightFences[currentFrame] ); - commandBuffers[currentFrame].reset(); - recordCommandBuffer(imageIndex); - - vk::PipelineStageFlags waitDestinationStageMask( vk::PipelineStageFlagBits::eColorAttachmentOutput ); - const vk::SubmitInfo submitInfo{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], - .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], - .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex] }; - queue.submit(submitInfo, *inFlightFences[currentFrame]); - - - const vk::PresentInfoKHR presentInfoKHR{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], - .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex }; - result = queue.presentKHR( presentInfoKHR ); - if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { - framebufferResized = false; - recreateSwapChain(); - } else if (result != vk::Result::eSuccess) { - throw std::runtime_error("failed to present swap chain image!"); - } - semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; - } - - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size(), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static vk::Format chooseSwapSurfaceFormat(const std::vector& availableFormats) { - const auto formatIt = std::ranges::find_if(availableFormats, - [](const auto& format) { - return format.format == vk::Format::eB8G8R8A8Srgb && - format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; - }); - return formatIt != availableFormats.end() ? formatIt->format : availableFormats[0].format; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : 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) - }; - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - 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; - } -}; - -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/23_descriptor_sets.cpp b/attachments/23_descriptor_sets.cpp index 5a734b75..adcbcbca 100644 --- a/attachments/23_descriptor_sets.cpp +++ b/attachments/23_descriptor_sets.cpp @@ -1,37 +1,36 @@ -#include -#include -#include -#include -#include -#include -#include #include -#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include #define GLM_FORCE_RADIANS #include #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -constexpr int MAX_FRAMES_IN_FLIGHT = 2; +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; +constexpr int MAX_FRAMES_IN_FLIGHT = 2; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -39,739 +38,779 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -struct Vertex { - glm::vec2 pos; - glm::vec3 color; - - static vk::VertexInputBindingDescription getBindingDescription() { - return { 0, sizeof(Vertex), vk::VertexInputRate::eVertex }; - } - - static std::array getAttributeDescriptions() { - return { - vk::VertexInputAttributeDescription( 0, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, pos) ), - vk::VertexInputAttributeDescription( 1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color) ) - }; - } +struct Vertex +{ + glm::vec2 pos; + glm::vec3 color; + + static vk::VertexInputBindingDescription getBindingDescription() + { + return {0, sizeof(Vertex), vk::VertexInputRate::eVertex}; + } + + static std::array getAttributeDescriptions() + { + return { + vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, pos)), + vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color))}; + } }; -struct UniformBufferObject { - alignas(16) glm::mat4 model; - alignas(16) glm::mat4 view; - alignas(16) glm::mat4 proj; +struct UniformBufferObject +{ + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; }; const std::vector vertices = { {{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}}, {{0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}}, {{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}, - {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}} -}; + {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}}}; const std::vector indices = { - 0, 1, 2, 2, 3, 0 + 0, 1, 2, 2, 3, 0}; + +class HelloTriangleApplication +{ + public: + void run() + { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + + private: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::Format swapChainImageFormat = vk::Format::eUndefined; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + + vk::raii::Buffer vertexBuffer = nullptr; + vk::raii::DeviceMemory vertexBufferMemory = nullptr; + vk::raii::Buffer indexBuffer = nullptr; + vk::raii::DeviceMemory indexBufferMemory = nullptr; + + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + std::vector uniformBuffersMapped; + + vk::raii::DescriptorPool descriptorPool = nullptr; + std::vector descriptorSets; + + vk::raii::CommandPool commandPool = nullptr; + std::vector commandBuffers; + + std::vector presentCompleteSemaphore; + std::vector renderFinishedSemaphore; + std::vector inFlightFences; + uint32_t semaphoreIndex = 0; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow *window, int width, int height) + { + auto app = static_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createDescriptorSetLayout(); + createGraphicsPipeline(); + createCommandPool(); + createVertexBuffer(); + createIndexBuffer(); + createUniformBuffers(); + createDescriptorPool(); + createDescriptorSets(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() + { + while (!glfwWindowShouldClose(window)) + { + glfwPollEvents(); + drawFrame(); + } + + device.waitIdle(); + } + + void cleanupSwapChain() + { + swapChainImageViews.clear(); + swapChain = nullptr; + } + + void cleanup() + { + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() + { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) + { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + device.waitIdle(); + + cleanupSwapChain(); + createSwapChain(); + createImageViews(); + } + + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain featureChain = { + {}, // vk::PhysicalDeviceFeatures2 + {.synchronization2 = true, .dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface); + swapChainImageFormat = 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{ + .surface = surface, .minImageCount = minImageCount, .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + void createImageViews() + { + 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(device, imageViewCreateInfo); + } + } + + void createDescriptorSetLayout() + { + vk::DescriptorSetLayoutBinding uboLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr); + vk::DescriptorSetLayoutCreateInfo layoutInfo{.bindingCount = 1, .pBindings = &uboLayoutBinding}; + descriptorSetLayout = vk::raii::DescriptorSetLayout(device, layoutInfo); + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; + + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{.vertexBindingDescriptionCount = 1, .pVertexBindingDescriptions = &bindingDescription, .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), .pVertexAttributeDescriptions = attributeDescriptions.data()}; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{.topology = vk::PrimitiveTopology::eTriangleList}; + vk::PipelineViewportStateCreateInfo viewportState{.viewportCount = 1, .scissorCount = 1}; + + vk::PipelineRasterizationStateCreateInfo rasterizer{.depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, .frontFace = vk::FrontFace::eCounterClockwise, .depthBiasEnable = vk::False, .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f}; + + vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; + + vk::PipelineColorBlendAttachmentState colorBlendAttachment{.blendEnable = vk::False, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA}; + + vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0}; + + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainImageFormat}; + vk::GraphicsPipelineCreateInfo pipelineInfo{.pNext = &pipelineRenderingCreateInfo, + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex}; + commandPool = vk::raii::CommandPool(device, poolInfo); + } + + void createVertexBuffer() + { + vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(dataStaging, vertices.data(), bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + } + + void createIndexBuffer() + { + vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(data, indices.data(), (size_t) bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); + + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + } + + void createUniformBuffers() + { + uniformBuffers.clear(); + uniformBuffersMemory.clear(); + uniformBuffersMapped.clear(); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DeviceSize bufferSize = sizeof(UniformBufferObject); + vk::raii::Buffer buffer({}); + vk::raii::DeviceMemory bufferMem({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); + uniformBuffers.emplace_back(std::move(buffer)); + uniformBuffersMemory.emplace_back(std::move(bufferMem)); + uniformBuffersMapped.emplace_back(uniformBuffersMemory[i].mapMemory(0, bufferSize)); + } + } + + void createDescriptorPool() + { + vk::DescriptorPoolSize poolSize(vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT); + vk::DescriptorPoolCreateInfo poolInfo{.flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, .maxSets = MAX_FRAMES_IN_FLIGHT, .poolSizeCount = 1, .pPoolSizes = &poolSize}; + descriptorPool = vk::raii::DescriptorPool(device, poolInfo); + } + + void createDescriptorSets() + { + std::vector layouts(MAX_FRAMES_IN_FLIGHT, *descriptorSetLayout); + vk::DescriptorSetAllocateInfo allocInfo{.descriptorPool = descriptorPool, .descriptorSetCount = static_cast(layouts.size()), .pSetLayouts = layouts.data()}; + + descriptorSets = device.allocateDescriptorSets(allocInfo); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DescriptorBufferInfo bufferInfo{.buffer = uniformBuffers[i], .offset = 0, .range = sizeof(UniformBufferObject)}; + vk::WriteDescriptorSet descriptorWrite{.dstSet = descriptorSets[i], .dstBinding = 0, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eUniformBuffer, .pBufferInfo = &bufferInfo}; + device.updateDescriptorSets(descriptorWrite, {}); + } + } + + void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer &buffer, vk::raii::DeviceMemory &bufferMemory) + { + vk::BufferCreateInfo bufferInfo{.size = size, .usage = usage, .sharingMode = vk::SharingMode::eExclusive}; + buffer = vk::raii::Buffer(device, bufferInfo); + vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{.allocationSize = memRequirements.size, .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + bufferMemory = vk::raii::DeviceMemory(device, allocInfo); + buffer.bindMemory(bufferMemory, 0); + } + + void copyBuffer(vk::raii::Buffer &srcBuffer, vk::raii::Buffer &dstBuffer, vk::DeviceSize size) + { + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1}; + vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); + commandCopyBuffer.begin(vk::CommandBufferBeginInfo{.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); + commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy(0, 0, size)); + commandCopyBuffer.end(); + queue.submit(vk::SubmitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer}, nullptr); + queue.waitIdle(); + } + + uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) + { + vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) + { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) + { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + void createCommandBuffers() + { + commandBuffers.clear(); + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = MAX_FRAMES_IN_FLIGHT}; + commandBuffers = vk::raii::CommandBuffers(device, allocInfo); + } + + void recordCommandBuffer(uint32_t imageIndex) + { + commandBuffers[currentFrame].begin({}); + // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL + transition_image_layout( + imageIndex, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage + ); + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::RenderingAttachmentInfo attachmentInfo = { + .imageView = swapChainImageViews[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor}; + vk::RenderingInfo renderingInfo = { + .renderArea = {.offset = {0, 0}, .extent = swapChainExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &attachmentInfo}; + commandBuffers[currentFrame].beginRendering(renderingInfo); + commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint16); + commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); + commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); + commandBuffers[currentFrame].endRendering(); + // After rendering, transition the swapchain image to PRESENT_SRC + transition_image_layout( + imageIndex, + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage + ); + commandBuffers[currentFrame].end(); + } + + void transition_image_layout( + uint32_t imageIndex, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout, + vk::AccessFlags2 src_access_mask, + vk::AccessFlags2 dst_access_mask, + vk::PipelineStageFlags2 src_stage_mask, + vk::PipelineStageFlags2 dst_stage_mask) + { + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = src_stage_mask, + .srcAccessMask = src_access_mask, + .dstStageMask = dst_stage_mask, + .dstAccessMask = dst_access_mask, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo dependency_info = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + } + + void createSyncObjects() + { + presentCompleteSemaphore.clear(); + renderFinishedSemaphore.clear(); + inFlightFences.clear(); + + for (size_t i = 0; i < swapChainImages.size(); i++) + { + presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); + } + } + + void updateUniformBuffer(uint32_t currentImage) + { + static auto startTime = std::chrono::high_resolution_clock::now(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + UniformBufferObject ubo{}; + ubo.model = rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.view = lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); + ubo.proj[1][1] *= -1; + + memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); + } + + void drawFrame() + { + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + ; + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr); + + if (result == vk::Result::eErrorOutOfDateKHR) + { + recreateSwapChain(); + return; + } + if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) + { + throw std::runtime_error("failed to acquire swap chain image!"); + } + updateUniformBuffer(currentFrame); + + device.resetFences(*inFlightFences[currentFrame]); + commandBuffers[currentFrame].reset(); + recordCommandBuffer(imageIndex); + + vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[currentFrame]); + + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + result = queue.presentKHR(presentInfoKHR); + if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) + { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != vk::Result::eSuccess) + { + throw std::runtime_error("failed to present swap chain image!"); + } + semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size(), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static vk::Format chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + const auto formatIt = std::ranges::find_if(availableFormats, + [](const auto &format) { + return format.format == vk::Format::eB8G8R8A8Srgb && + format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; + }); + return formatIt != availableFormats.end() ? formatIt->format : availableFormats[0].format; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + 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)}; + } + + std::vector getRequiredExtensions() + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + 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: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::Format swapChainImageFormat = vk::Format::eUndefined; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - - vk::raii::Buffer vertexBuffer = nullptr; - vk::raii::DeviceMemory vertexBufferMemory = nullptr; - vk::raii::Buffer indexBuffer = nullptr; - vk::raii::DeviceMemory indexBufferMemory = nullptr; - - std::vector uniformBuffers; - std::vector uniformBuffersMemory; - std::vector uniformBuffersMapped; - - vk::raii::DescriptorPool descriptorPool = nullptr; - std::vector descriptorSets; - - vk::raii::CommandPool commandPool = nullptr; - std::vector commandBuffers; - - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; - std::vector inFlightFences; - uint32_t semaphoreIndex = 0; - uint32_t currentFrame = 0; - - bool framebufferResized = false; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); - } - - static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { - auto app = static_cast(glfwGetWindowUserPointer(window)); - app->framebufferResized = true; - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - createSurface(); - pickPhysicalDevice(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createDescriptorSetLayout(); - createGraphicsPipeline(); - createCommandPool(); - createVertexBuffer(); - createIndexBuffer(); - createUniformBuffers(); - createDescriptorPool(); - createDescriptorSets(); - createCommandBuffers(); - createSyncObjects(); - } - - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - drawFrame(); - } - - device.waitIdle(); - } - - void cleanupSwapChain() { - swapChainImageViews.clear(); - swapChain = nullptr; - } - - void cleanup() { - glfwDestroyWindow(window); - - glfwTerminate(); - } - - void recreateSwapChain() { - int width = 0, height = 0; - glfwGetFramebufferSize(window, &width, &height); - while (width == 0 || height == 0) { - glfwGetFramebufferSize(window, &width, &height); - glfwWaitEvents(); - } - - device.waitIdle(); - - cleanupSwapChain(); - createSwapChain(); - createImageViews(); - } - - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - } ); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error( "failed to find a suitable GPU!" ); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain featureChain = { - {}, // vk::PhysicalDeviceFeatures2 - {.synchronization2 = true, .dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( surface ); - swapChainImageFormat = 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{ - .surface = surface, .minImageCount = minImageCount, - .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, - .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 }; - - swapChain = vk::raii::SwapchainKHR( device, swapChainCreateInfo ); - swapChainImages = swapChain.getImages(); - } - - void createImageViews() { - 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( device, imageViewCreateInfo ); - } - } - - void createDescriptorSetLayout() { - vk::DescriptorSetLayoutBinding uboLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr); - vk::DescriptorSetLayoutCreateInfo layoutInfo{ .bindingCount = 1, .pBindings = &uboLayoutBinding }; - descriptorSetLayout = vk::raii::DescriptorSetLayout( device, layoutInfo ); - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; - - auto bindingDescription = Vertex::getBindingDescription(); - auto attributeDescriptions = Vertex::getAttributeDescriptions(); - vk::PipelineVertexInputStateCreateInfo vertexInputInfo { .vertexBindingDescriptionCount =1, .pVertexBindingDescriptions = &bindingDescription, - .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), .pVertexAttributeDescriptions = attributeDescriptions.data() }; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ .topology = vk::PrimitiveTopology::eTriangleList }; - vk::PipelineViewportStateCreateInfo viewportState{ .viewportCount = 1, .scissorCount = 1 }; - - vk::PipelineRasterizationStateCreateInfo rasterizer{ .depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eCounterClockwise, .depthBiasEnable = vk::False, - .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f }; - - vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; - - vk::PipelineColorBlendAttachmentState colorBlendAttachment{ .blendEnable = vk::False, - .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA - }; - - vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0 }; - - pipelineLayout = vk::raii::PipelineLayout( device, pipelineLayoutInfo ); - - vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ .colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainImageFormat }; - vk::GraphicsPipelineCreateInfo pipelineInfo{ .pNext = &pipelineRenderingCreateInfo, - .stageCount = 2, .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, .layout = pipelineLayout, .renderPass = nullptr }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{ .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex }; - commandPool = vk::raii::CommandPool(device, poolInfo); - } - - void createVertexBuffer() { - vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(dataStaging, vertices.data(), bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); - - copyBuffer(stagingBuffer, vertexBuffer, bufferSize); - } - - void createIndexBuffer() { - vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); - - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(data, indices.data(), (size_t) bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); - - copyBuffer(stagingBuffer, indexBuffer, bufferSize); - } - - void createUniformBuffers() { - uniformBuffers.clear(); - uniformBuffersMemory.clear(); - uniformBuffersMapped.clear(); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DeviceSize bufferSize = sizeof(UniformBufferObject); - vk::raii::Buffer buffer({}); - vk::raii::DeviceMemory bufferMem({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); - uniformBuffers.emplace_back(std::move(buffer)); - uniformBuffersMemory.emplace_back(std::move(bufferMem)); - uniformBuffersMapped.emplace_back( uniformBuffersMemory[i].mapMemory(0, bufferSize)); - } - } - - void createDescriptorPool() { - vk::DescriptorPoolSize poolSize(vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT); - vk::DescriptorPoolCreateInfo poolInfo{ .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, .maxSets = MAX_FRAMES_IN_FLIGHT, .poolSizeCount = 1, .pPoolSizes = &poolSize }; - descriptorPool = vk::raii::DescriptorPool(device, poolInfo); - } - - void createDescriptorSets() { - std::vector layouts(MAX_FRAMES_IN_FLIGHT, *descriptorSetLayout); - vk::DescriptorSetAllocateInfo allocInfo{ .descriptorPool = descriptorPool, .descriptorSetCount = static_cast(layouts.size()), .pSetLayouts = layouts.data() }; - - descriptorSets = device.allocateDescriptorSets(allocInfo); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DescriptorBufferInfo bufferInfo{ .buffer = uniformBuffers[i], .offset = 0, .range = sizeof(UniformBufferObject) }; - vk::WriteDescriptorSet descriptorWrite{ .dstSet = descriptorSets[i], .dstBinding = 0, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eUniformBuffer, .pBufferInfo = &bufferInfo }; - device.updateDescriptorSets(descriptorWrite, {}); - } - } - - void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer& buffer, vk::raii::DeviceMemory& bufferMemory) { - vk::BufferCreateInfo bufferInfo{ .size = size, .usage = usage, .sharingMode = vk::SharingMode::eExclusive }; - buffer = vk::raii::Buffer(device, bufferInfo); - vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ .allocationSize =memRequirements.size, .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) }; - bufferMemory = vk::raii::DeviceMemory(device, allocInfo); - buffer.bindMemory(bufferMemory, 0); - } - - void copyBuffer(vk::raii::Buffer & srcBuffer, vk::raii::Buffer & dstBuffer, vk::DeviceSize size) { - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1 }; - vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); - commandCopyBuffer.begin(vk::CommandBufferBeginInfo { .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit }); - commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy(0, 0, size)); - commandCopyBuffer.end(); - queue.submit(vk::SubmitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer }, nullptr); - queue.waitIdle(); - } - - uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) { - vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); - - for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { - if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { - return i; - } - } - - throw std::runtime_error("failed to find suitable memory type!"); - } - - void createCommandBuffers() { - commandBuffers.clear(); - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = MAX_FRAMES_IN_FLIGHT }; - commandBuffers = vk::raii::CommandBuffers( device, allocInfo ); - } - - void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin( {} ); - // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL - transition_image_layout( - imageIndex, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage - ); - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::RenderingAttachmentInfo attachmentInfo = { - .imageView = swapChainImageViews[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor - }; - vk::RenderingInfo renderingInfo = { - .renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &attachmentInfo - }; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor( 0, vk::Rect2D( vk::Offset2D( 0, 0 ), swapChainExtent ) ); - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].bindIndexBuffer( *indexBuffer, 0, vk::IndexType::eUint16 ); - commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); - commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); - commandBuffers[currentFrame].endRendering(); - // After rendering, transition the swapchain image to PRESENT_SRC - transition_image_layout( - imageIndex, - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage - ); - commandBuffers[currentFrame].end(); - } - - void transition_image_layout( - uint32_t imageIndex, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::AccessFlags2 src_access_mask, - vk::AccessFlags2 dst_access_mask, - vk::PipelineStageFlags2 src_stage_mask, - vk::PipelineStageFlags2 dst_stage_mask - ) { - vk::ImageMemoryBarrier2 barrier = { - .srcStageMask = src_stage_mask, - .srcAccessMask = src_access_mask, - .dstStageMask = dst_stage_mask, - .dstAccessMask = dst_access_mask, - .oldLayout = old_layout, - .newLayout = new_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); - } - - void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); - - for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - } - - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - inFlightFences.emplace_back(device, vk::FenceCreateInfo { .flags = vk::FenceCreateFlagBits::eSignaled }); - } - } - - void updateUniformBuffer(uint32_t currentImage) { - static auto startTime = std::chrono::high_resolution_clock::now(); - - auto currentTime = std::chrono::high_resolution_clock::now(); - float time = std::chrono::duration(currentTime - startTime).count(); - - UniformBufferObject ubo{}; - ubo.model = rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.view = lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); - ubo.proj[1][1] *= -1; - - memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); - } - - void drawFrame() { - while ( vk::Result::eTimeout == device.waitForFences( *inFlightFences[currentFrame], vk::True, UINT64_MAX ) ) - ; - auto [result, imageIndex] = swapChain.acquireNextImage( UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr ); - - if (result == vk::Result::eErrorOutOfDateKHR) { - recreateSwapChain(); - return; - } - if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) { - throw std::runtime_error("failed to acquire swap chain image!"); - } - updateUniformBuffer(currentFrame); - - device.resetFences( *inFlightFences[currentFrame] ); - commandBuffers[currentFrame].reset(); - recordCommandBuffer(imageIndex); - - vk::PipelineStageFlags waitDestinationStageMask( vk::PipelineStageFlagBits::eColorAttachmentOutput ); - const vk::SubmitInfo submitInfo{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], - .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], - .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex] }; - queue.submit(submitInfo, *inFlightFences[currentFrame]); - - - const vk::PresentInfoKHR presentInfoKHR{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], - .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex }; - result = queue.presentKHR( presentInfoKHR ); - if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { - framebufferResized = false; - recreateSwapChain(); - } else if (result != vk::Result::eSuccess) { - throw std::runtime_error("failed to present swap chain image!"); - } - semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; - } - - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size(), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static vk::Format chooseSwapSurfaceFormat(const std::vector& availableFormats) { - const auto formatIt = std::ranges::find_if(availableFormats, - [](const auto& format) { - return format.format == vk::Format::eB8G8R8A8Srgb && - format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; - }); - return formatIt != availableFormats.end() ? formatIt->format : availableFormats[0].format; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : 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) - }; - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - 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; - } -}; - -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/24_texture_image.cpp b/attachments/24_texture_image.cpp index 0c516081..b7a256db 100644 --- a/attachments/24_texture_image.cpp +++ b/attachments/24_texture_image.cpp @@ -1,24 +1,24 @@ -#include -#include -#include -#include -#include -#include -#include #include -#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include #define GLM_FORCE_RADIANS @@ -28,13 +28,12 @@ import vulkan_hpp; #define STB_IMAGE_IMPLEMENTATION #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -constexpr int MAX_FRAMES_IN_FLIGHT = 2; +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; +constexpr int MAX_FRAMES_IN_FLIGHT = 2; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -42,840 +41,885 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -struct Vertex { - glm::vec2 pos; - glm::vec3 color; - - static vk::VertexInputBindingDescription getBindingDescription() { - return { 0, sizeof(Vertex), vk::VertexInputRate::eVertex }; - } - - static std::array getAttributeDescriptions() { - return { - vk::VertexInputAttributeDescription( 0, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, pos) ), - vk::VertexInputAttributeDescription( 1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color) ) - }; - } +struct Vertex +{ + glm::vec2 pos; + glm::vec3 color; + + static vk::VertexInputBindingDescription getBindingDescription() + { + return {0, sizeof(Vertex), vk::VertexInputRate::eVertex}; + } + + static std::array getAttributeDescriptions() + { + return { + vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, pos)), + vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color))}; + } }; -struct UniformBufferObject { - alignas(16) glm::mat4 model; - alignas(16) glm::mat4 view; - alignas(16) glm::mat4 proj; +struct UniformBufferObject +{ + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; }; const std::vector vertices = { {{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}}, {{0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}}, {{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}, - {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}} -}; + {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}}}; const std::vector indices = { - 0, 1, 2, 2, 3, 0 + 0, 1, 2, 2, 3, 0}; + +class HelloTriangleApplication +{ + public: + void run() + { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + + private: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::Format swapChainImageFormat = vk::Format::eUndefined; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + + vk::raii::Image textureImage = nullptr; + vk::raii::DeviceMemory textureImageMemory = nullptr; + + vk::raii::Buffer vertexBuffer = nullptr; + vk::raii::DeviceMemory vertexBufferMemory = nullptr; + vk::raii::Buffer indexBuffer = nullptr; + vk::raii::DeviceMemory indexBufferMemory = nullptr; + + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + std::vector uniformBuffersMapped; + + vk::raii::DescriptorPool descriptorPool = nullptr; + std::vector descriptorSets; + + vk::raii::CommandPool commandPool = nullptr; + std::vector commandBuffers; + + std::vector presentCompleteSemaphore; + std::vector renderFinishedSemaphore; + std::vector inFlightFences; + uint32_t semaphoreIndex = 0; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow *window, int width, int height) + { + auto app = static_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createDescriptorSetLayout(); + createGraphicsPipeline(); + createCommandPool(); + createTextureImage(); + createVertexBuffer(); + createIndexBuffer(); + createUniformBuffers(); + createDescriptorPool(); + createDescriptorSets(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() + { + while (!glfwWindowShouldClose(window)) + { + glfwPollEvents(); + drawFrame(); + } + + device.waitIdle(); + } + + void cleanupSwapChain() + { + swapChainImageViews.clear(); + swapChain = nullptr; + } + + void cleanup() + { + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() + { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) + { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + device.waitIdle(); + + cleanupSwapChain(); + createSwapChain(); + createImageViews(); + } + + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain featureChain = { + {}, // vk::PhysicalDeviceFeatures2 + {.synchronization2 = true, .dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface); + swapChainImageFormat = 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{ + .surface = surface, .minImageCount = minImageCount, .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + void createImageViews() + { + 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(device, imageViewCreateInfo); + } + } + + void createDescriptorSetLayout() + { + vk::DescriptorSetLayoutBinding uboLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr); + vk::DescriptorSetLayoutCreateInfo layoutInfo{.bindingCount = 1, .pBindings = &uboLayoutBinding}; + descriptorSetLayout = vk::raii::DescriptorSetLayout(device, layoutInfo); + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; + + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{.vertexBindingDescriptionCount = 1, .pVertexBindingDescriptions = &bindingDescription, .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), .pVertexAttributeDescriptions = attributeDescriptions.data()}; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{.topology = vk::PrimitiveTopology::eTriangleList}; + vk::PipelineViewportStateCreateInfo viewportState{.viewportCount = 1, .scissorCount = 1}; + + vk::PipelineRasterizationStateCreateInfo rasterizer{.depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, .frontFace = vk::FrontFace::eCounterClockwise, .depthBiasEnable = vk::False, .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f}; + + vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; + + vk::PipelineColorBlendAttachmentState colorBlendAttachment{.blendEnable = vk::False, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA}; + + vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0}; + + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainImageFormat}; + vk::GraphicsPipelineCreateInfo pipelineInfo{.pNext = &pipelineRenderingCreateInfo, + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex}; + commandPool = vk::raii::CommandPool(device, poolInfo); + } + + void createTextureImage() + { + int texWidth, texHeight, texChannels; + stbi_uc *pixels = stbi_load("textures/texture.jpg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + vk::DeviceSize imageSize = texWidth * texHeight * 4; + + if (!pixels) + { + throw std::runtime_error("failed to load texture image!"); + } + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, imageSize); + memcpy(data, pixels, imageSize); + stagingBufferMemory.unmapMemory(); + + stbi_image_free(pixels); + + createImage(texWidth, texHeight, vk::Format::eR8G8B8A8Srgb, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory); + + transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); + transitionImageLayout(textureImage, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal); + } + + void createImage(uint32_t width, uint32_t height, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image &image, vk::raii::DeviceMemory &imageMemory) + { + vk::ImageCreateInfo imageInfo{.imageType = vk::ImageType::e2D, .format = format, .extent = {width, height, 1}, .mipLevels = 1, .arrayLayers = 1, .samples = vk::SampleCountFlagBits::e1, .tiling = tiling, .usage = usage, .sharingMode = vk::SharingMode::eExclusive}; + + image = vk::raii::Image(device, imageInfo); + + vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{.allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + imageMemory = vk::raii::DeviceMemory(device, allocInfo); + image.bindMemory(imageMemory, 0); + } + + void transitionImageLayout(const vk::raii::Image &image, vk::ImageLayout oldLayout, vk::ImageLayout newLayout) + { + auto commandBuffer = beginSingleTimeCommands(); + + vk::ImageMemoryBarrier barrier{.oldLayout = oldLayout, .newLayout = newLayout, .image = image, .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + + vk::PipelineStageFlags sourceStage; + vk::PipelineStageFlags destinationStage; + + if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) + { + barrier.srcAccessMask = {}; + barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; + + sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; + destinationStage = vk::PipelineStageFlagBits::eTransfer; + } + else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) + { + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + + sourceStage = vk::PipelineStageFlagBits::eTransfer; + destinationStage = vk::PipelineStageFlagBits::eFragmentShader; + } + else + { + throw std::invalid_argument("unsupported layout transition!"); + } + commandBuffer->pipelineBarrier(sourceStage, destinationStage, {}, {}, nullptr, barrier); + endSingleTimeCommands(*commandBuffer); + } + + void copyBufferToImage(const vk::raii::Buffer &buffer, vk::raii::Image &image, uint32_t width, uint32_t height) + { + std::unique_ptr commandBuffer = beginSingleTimeCommands(); + vk::BufferImageCopy region{.bufferOffset = 0, .bufferRowLength = 0, .bufferImageHeight = 0, .imageSubresource = {vk::ImageAspectFlagBits::eColor, 0, 0, 1}, .imageOffset = {0, 0, 0}, .imageExtent = {width, height, 1}}; + commandBuffer->copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, {region}); + endSingleTimeCommands(*commandBuffer); + } + + void createVertexBuffer() + { + vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(dataStaging, vertices.data(), bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + } + + void createIndexBuffer() + { + vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(data, indices.data(), (size_t) bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); + + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + } + + void createUniformBuffers() + { + uniformBuffers.clear(); + uniformBuffersMemory.clear(); + uniformBuffersMapped.clear(); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DeviceSize bufferSize = sizeof(UniformBufferObject); + vk::raii::Buffer buffer({}); + vk::raii::DeviceMemory bufferMem({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); + uniformBuffers.emplace_back(std::move(buffer)); + uniformBuffersMemory.emplace_back(std::move(bufferMem)); + uniformBuffersMapped.emplace_back(uniformBuffersMemory[i].mapMemory(0, bufferSize)); + } + } + + void createDescriptorPool() + { + vk::DescriptorPoolSize poolSize(vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT); + vk::DescriptorPoolCreateInfo poolInfo{.flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, .maxSets = MAX_FRAMES_IN_FLIGHT, .poolSizeCount = 1, .pPoolSizes = &poolSize}; + descriptorPool = vk::raii::DescriptorPool(device, poolInfo); + } + + void createDescriptorSets() + { + std::vector layouts(MAX_FRAMES_IN_FLIGHT, *descriptorSetLayout); + vk::DescriptorSetAllocateInfo allocInfo{.descriptorPool = descriptorPool, .descriptorSetCount = static_cast(layouts.size()), .pSetLayouts = layouts.data()}; + + descriptorSets = device.allocateDescriptorSets(allocInfo); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DescriptorBufferInfo bufferInfo{.buffer = uniformBuffers[i], .offset = 0, .range = sizeof(UniformBufferObject)}; + vk::WriteDescriptorSet descriptorWrite{.dstSet = descriptorSets[i], .dstBinding = 0, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eUniformBuffer, .pBufferInfo = &bufferInfo}; + device.updateDescriptorSets(descriptorWrite, {}); + } + } + + void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer &buffer, vk::raii::DeviceMemory &bufferMemory) + { + vk::BufferCreateInfo bufferInfo{.size = size, .usage = usage, .sharingMode = vk::SharingMode::eExclusive}; + buffer = vk::raii::Buffer(device, bufferInfo); + vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{.allocationSize = memRequirements.size, .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + bufferMemory = vk::raii::DeviceMemory(device, allocInfo); + buffer.bindMemory(bufferMemory, 0); + } + + std::unique_ptr beginSingleTimeCommands() + { + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1}; + std::unique_ptr commandBuffer = std::make_unique(std::move(vk::raii::CommandBuffers(device, allocInfo).front())); + + vk::CommandBufferBeginInfo beginInfo{.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; + commandBuffer->begin(beginInfo); + + return commandBuffer; + } + + void endSingleTimeCommands(vk::raii::CommandBuffer &commandBuffer) + { + commandBuffer.end(); + + vk::SubmitInfo submitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandBuffer}; + queue.submit(submitInfo, nullptr); + queue.waitIdle(); + } + + void copyBuffer(vk::raii::Buffer &srcBuffer, vk::raii::Buffer &dstBuffer, vk::DeviceSize size) + { + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1}; + vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); + commandCopyBuffer.begin(vk::CommandBufferBeginInfo{.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); + commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy{.size = size}); + commandCopyBuffer.end(); + queue.submit(vk::SubmitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer}, nullptr); + queue.waitIdle(); + } + + uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) + { + vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) + { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) + { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + void createCommandBuffers() + { + commandBuffers.clear(); + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = MAX_FRAMES_IN_FLIGHT}; + commandBuffers = vk::raii::CommandBuffers(device, allocInfo); + } + + void recordCommandBuffer(uint32_t imageIndex) + { + commandBuffers[currentFrame].begin({}); + // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL + transition_image_layout( + imageIndex, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage + ); + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::RenderingAttachmentInfo attachmentInfo = { + .imageView = swapChainImageViews[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor}; + vk::RenderingInfo renderingInfo = { + .renderArea = {.offset = {0, 0}, .extent = swapChainExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &attachmentInfo}; + commandBuffers[currentFrame].beginRendering(renderingInfo); + commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint16); + commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); + commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); + commandBuffers[currentFrame].endRendering(); + // After rendering, transition the swapchain image to PRESENT_SRC + transition_image_layout( + imageIndex, + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage + ); + commandBuffers[currentFrame].end(); + } + + void transition_image_layout( + uint32_t imageIndex, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout, + vk::AccessFlags2 src_access_mask, + vk::AccessFlags2 dst_access_mask, + vk::PipelineStageFlags2 src_stage_mask, + vk::PipelineStageFlags2 dst_stage_mask) + { + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = src_stage_mask, + .srcAccessMask = src_access_mask, + .dstStageMask = dst_stage_mask, + .dstAccessMask = dst_access_mask, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo dependency_info = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + } + + void createSyncObjects() + { + presentCompleteSemaphore.clear(); + renderFinishedSemaphore.clear(); + inFlightFences.clear(); + + for (size_t i = 0; i < swapChainImages.size(); i++) + { + presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); + } + } + + void updateUniformBuffer(uint32_t currentImage) + { + static auto startTime = std::chrono::high_resolution_clock::now(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + UniformBufferObject ubo{}; + ubo.model = rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.view = lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); + ubo.proj[1][1] *= -1; + + memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); + } + + void drawFrame() + { + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + ; + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr); + + if (result == vk::Result::eErrorOutOfDateKHR) + { + recreateSwapChain(); + return; + } + if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) + { + throw std::runtime_error("failed to acquire swap chain image!"); + } + updateUniformBuffer(currentFrame); + + device.resetFences(*inFlightFences[currentFrame]); + commandBuffers[currentFrame].reset(); + recordCommandBuffer(imageIndex); + + vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[currentFrame]); + + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + result = queue.presentKHR(presentInfoKHR); + if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) + { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != vk::Result::eSuccess) + { + throw std::runtime_error("failed to present swap chain image!"); + } + semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size(), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static vk::Format chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + const auto formatIt = std::ranges::find_if(availableFormats, + [](const auto &format) { + return format.format == vk::Format::eB8G8R8A8Srgb && + format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; + }); + return formatIt != availableFormats.end() ? formatIt->format : availableFormats[0].format; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + 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)}; + } + + std::vector getRequiredExtensions() + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + 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: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::Format swapChainImageFormat = vk::Format::eUndefined; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - - vk::raii::Image textureImage = nullptr; - vk::raii::DeviceMemory textureImageMemory = nullptr; - - vk::raii::Buffer vertexBuffer = nullptr; - vk::raii::DeviceMemory vertexBufferMemory = nullptr; - vk::raii::Buffer indexBuffer = nullptr; - vk::raii::DeviceMemory indexBufferMemory = nullptr; - - std::vector uniformBuffers; - std::vector uniformBuffersMemory; - std::vector uniformBuffersMapped; - - vk::raii::DescriptorPool descriptorPool = nullptr; - std::vector descriptorSets; - - vk::raii::CommandPool commandPool = nullptr; - std::vector commandBuffers; - - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; - std::vector inFlightFences; - uint32_t semaphoreIndex = 0; - uint32_t currentFrame = 0; - - bool framebufferResized = false; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); - } - - static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { - auto app = static_cast(glfwGetWindowUserPointer(window)); - app->framebufferResized = true; - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - createSurface(); - pickPhysicalDevice(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createDescriptorSetLayout(); - createGraphicsPipeline(); - createCommandPool(); - createTextureImage(); - createVertexBuffer(); - createIndexBuffer(); - createUniformBuffers(); - createDescriptorPool(); - createDescriptorSets(); - createCommandBuffers(); - createSyncObjects(); - } - - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - drawFrame(); - } - - device.waitIdle(); - } - - void cleanupSwapChain() { - swapChainImageViews.clear(); - swapChain = nullptr; - } - - void cleanup() { - glfwDestroyWindow(window); - - glfwTerminate(); - } - - void recreateSwapChain() { - int width = 0, height = 0; - glfwGetFramebufferSize(window, &width, &height); - while (width == 0 || height == 0) { - glfwGetFramebufferSize(window, &width, &height); - glfwWaitEvents(); - } - - device.waitIdle(); - - cleanupSwapChain(); - createSwapChain(); - createImageViews(); - } - - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - } ); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error( "failed to find a suitable GPU!" ); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain featureChain = { - {}, // vk::PhysicalDeviceFeatures2 - {.synchronization2 = true, .dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( surface ); - swapChainImageFormat = 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{ - .surface = surface, .minImageCount = minImageCount, - .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, - .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 }; - - swapChain = vk::raii::SwapchainKHR( device, swapChainCreateInfo ); - swapChainImages = swapChain.getImages(); - } - - void createImageViews() { - 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( device, imageViewCreateInfo ); - } - } - - void createDescriptorSetLayout() { - vk::DescriptorSetLayoutBinding uboLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr); - vk::DescriptorSetLayoutCreateInfo layoutInfo{ .bindingCount = 1, .pBindings = &uboLayoutBinding }; - descriptorSetLayout = vk::raii::DescriptorSetLayout( device, layoutInfo ); - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; - - auto bindingDescription = Vertex::getBindingDescription(); - auto attributeDescriptions = Vertex::getAttributeDescriptions(); - vk::PipelineVertexInputStateCreateInfo vertexInputInfo { .vertexBindingDescriptionCount =1, .pVertexBindingDescriptions = &bindingDescription, - .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), .pVertexAttributeDescriptions = attributeDescriptions.data() }; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ .topology = vk::PrimitiveTopology::eTriangleList }; - vk::PipelineViewportStateCreateInfo viewportState{ .viewportCount = 1, .scissorCount = 1 }; - - vk::PipelineRasterizationStateCreateInfo rasterizer{ .depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eCounterClockwise, .depthBiasEnable = vk::False, - .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f }; - - vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; - - vk::PipelineColorBlendAttachmentState colorBlendAttachment{ .blendEnable = vk::False, - .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA - }; - - vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0 }; - - pipelineLayout = vk::raii::PipelineLayout( device, pipelineLayoutInfo ); - - vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ .colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainImageFormat }; - vk::GraphicsPipelineCreateInfo pipelineInfo{ .pNext = &pipelineRenderingCreateInfo, - .stageCount = 2, .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, .layout = pipelineLayout, .renderPass = nullptr }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{ .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex }; - commandPool = vk::raii::CommandPool(device, poolInfo); - } - - void createTextureImage() { - int texWidth, texHeight, texChannels; - stbi_uc* pixels = stbi_load("textures/texture.jpg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); - vk::DeviceSize imageSize = texWidth * texHeight * 4; - - if (!pixels) { - throw std::runtime_error("failed to load texture image!"); - } - - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, imageSize); - memcpy(data, pixels, imageSize); - stagingBufferMemory.unmapMemory(); - - stbi_image_free(pixels); - - createImage(texWidth, texHeight, vk::Format::eR8G8B8A8Srgb, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory); - - transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal); - copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); - transitionImageLayout(textureImage, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal); - } - - void createImage(uint32_t width, uint32_t height, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image& image, vk::raii::DeviceMemory& imageMemory) { - vk::ImageCreateInfo imageInfo{ .imageType = vk::ImageType::e2D, .format = format, - .extent = {width, height, 1}, .mipLevels = 1, .arrayLayers = 1, - .samples = vk::SampleCountFlagBits::e1, .tiling = tiling, - .usage = usage, .sharingMode = vk::SharingMode::eExclusive }; - - image = vk::raii::Image( device, imageInfo ); - - vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) }; - imageMemory = vk::raii::DeviceMemory( device, allocInfo ); - image.bindMemory(imageMemory, 0); - } - - void transitionImageLayout(const vk::raii::Image& image, vk::ImageLayout oldLayout, vk::ImageLayout newLayout) { - auto commandBuffer = beginSingleTimeCommands(); - - vk::ImageMemoryBarrier barrier{ .oldLayout = oldLayout, .newLayout = newLayout, - .image = image, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } }; - - vk::PipelineStageFlags sourceStage; - vk::PipelineStageFlags destinationStage; - - if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) { - barrier.srcAccessMask = {}; - barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; - - sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; - destinationStage = vk::PipelineStageFlagBits::eTransfer; - } else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) { - barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; - barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - - sourceStage = vk::PipelineStageFlagBits::eTransfer; - destinationStage = vk::PipelineStageFlagBits::eFragmentShader; - } else { - throw std::invalid_argument("unsupported layout transition!"); - } - commandBuffer->pipelineBarrier(sourceStage, destinationStage, {}, {}, nullptr, barrier); - endSingleTimeCommands(*commandBuffer); - } - - void copyBufferToImage(const vk::raii::Buffer& buffer, vk::raii::Image& image, uint32_t width, uint32_t height) { - std::unique_ptr commandBuffer = beginSingleTimeCommands(); - vk::BufferImageCopy region{ .bufferOffset = 0, .bufferRowLength = 0, .bufferImageHeight = 0, - .imageSubresource = { vk::ImageAspectFlagBits::eColor, 0, 0, 1 }, - .imageOffset = {0, 0, 0}, .imageExtent = {width, height, 1} }; - commandBuffer->copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, {region}); - endSingleTimeCommands(*commandBuffer); - } - - void createVertexBuffer() { - vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(dataStaging, vertices.data(), bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); - - copyBuffer(stagingBuffer, vertexBuffer, bufferSize); - } - - void createIndexBuffer() { - vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); - - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(data, indices.data(), (size_t) bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); - - copyBuffer(stagingBuffer, indexBuffer, bufferSize); - } - - void createUniformBuffers() { - uniformBuffers.clear(); - uniformBuffersMemory.clear(); - uniformBuffersMapped.clear(); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DeviceSize bufferSize = sizeof(UniformBufferObject); - vk::raii::Buffer buffer({}); - vk::raii::DeviceMemory bufferMem({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); - uniformBuffers.emplace_back(std::move(buffer)); - uniformBuffersMemory.emplace_back(std::move(bufferMem)); - uniformBuffersMapped.emplace_back( uniformBuffersMemory[i].mapMemory(0, bufferSize)); - } - } - - void createDescriptorPool() { - vk::DescriptorPoolSize poolSize(vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT); - vk::DescriptorPoolCreateInfo poolInfo{ .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, .maxSets = MAX_FRAMES_IN_FLIGHT, .poolSizeCount = 1, .pPoolSizes = &poolSize }; - descriptorPool = vk::raii::DescriptorPool(device, poolInfo); - } - - void createDescriptorSets() { - std::vector layouts(MAX_FRAMES_IN_FLIGHT, *descriptorSetLayout); - vk::DescriptorSetAllocateInfo allocInfo{ .descriptorPool = descriptorPool, .descriptorSetCount = static_cast(layouts.size()), .pSetLayouts = layouts.data() }; - - descriptorSets = device.allocateDescriptorSets(allocInfo); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DescriptorBufferInfo bufferInfo{ .buffer = uniformBuffers[i], .offset = 0, .range = sizeof(UniformBufferObject) }; - vk::WriteDescriptorSet descriptorWrite{ .dstSet = descriptorSets[i], .dstBinding = 0, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eUniformBuffer, .pBufferInfo = &bufferInfo }; - device.updateDescriptorSets(descriptorWrite, {}); - } - } - - void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer& buffer, vk::raii::DeviceMemory& bufferMemory) { - vk::BufferCreateInfo bufferInfo{ .size = size, .usage = usage, .sharingMode = vk::SharingMode::eExclusive }; - buffer = vk::raii::Buffer(device, bufferInfo); - vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ .allocationSize =memRequirements.size, .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) }; - bufferMemory = vk::raii::DeviceMemory(device, allocInfo); - buffer.bindMemory(bufferMemory, 0); - } - - std::unique_ptr beginSingleTimeCommands() { - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1 }; - std::unique_ptr commandBuffer = std::make_unique(std::move(vk::raii::CommandBuffers(device, allocInfo).front())); - - vk::CommandBufferBeginInfo beginInfo{ .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit }; - commandBuffer->begin(beginInfo); - - return commandBuffer; - } - - void endSingleTimeCommands(vk::raii::CommandBuffer& commandBuffer) { - commandBuffer.end(); - - vk::SubmitInfo submitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandBuffer }; - queue.submit(submitInfo, nullptr); - queue.waitIdle(); - } - - void copyBuffer(vk::raii::Buffer & srcBuffer, vk::raii::Buffer & dstBuffer, vk::DeviceSize size) { - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1 }; - vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); - commandCopyBuffer.begin(vk::CommandBufferBeginInfo{ .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit }); - commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy{ .size = size }); - commandCopyBuffer.end(); - queue.submit(vk::SubmitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer }, nullptr); - queue.waitIdle(); - } - - uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) { - vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); - - for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { - if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { - return i; - } - } - - throw std::runtime_error("failed to find suitable memory type!"); - } - - void createCommandBuffers() { - commandBuffers.clear(); - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = MAX_FRAMES_IN_FLIGHT }; - commandBuffers = vk::raii::CommandBuffers( device, allocInfo ); - } - - void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin( {} ); - // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL - transition_image_layout( - imageIndex, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage - ); - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::RenderingAttachmentInfo attachmentInfo = { - .imageView = swapChainImageViews[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor - }; - vk::RenderingInfo renderingInfo = { - .renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &attachmentInfo - }; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].bindIndexBuffer( *indexBuffer, 0, vk::IndexType::eUint16 ); - commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); - commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); - commandBuffers[currentFrame].endRendering(); - // After rendering, transition the swapchain image to PRESENT_SRC - transition_image_layout( - imageIndex, - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage - ); - commandBuffers[currentFrame].end(); - } - - void transition_image_layout( - uint32_t imageIndex, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::AccessFlags2 src_access_mask, - vk::AccessFlags2 dst_access_mask, - vk::PipelineStageFlags2 src_stage_mask, - vk::PipelineStageFlags2 dst_stage_mask - ) { - vk::ImageMemoryBarrier2 barrier = { - .srcStageMask = src_stage_mask, - .srcAccessMask = src_access_mask, - .dstStageMask = dst_stage_mask, - .dstAccessMask = dst_access_mask, - .oldLayout = old_layout, - .newLayout = new_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); - } - - void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); - - for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - } - - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - inFlightFences.emplace_back(device, vk::FenceCreateInfo { .flags = vk::FenceCreateFlagBits::eSignaled }); - } - } - - void updateUniformBuffer(uint32_t currentImage) { - static auto startTime = std::chrono::high_resolution_clock::now(); - - auto currentTime = std::chrono::high_resolution_clock::now(); - float time = std::chrono::duration(currentTime - startTime).count(); - - UniformBufferObject ubo{}; - ubo.model = rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.view = lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); - ubo.proj[1][1] *= -1; - - memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); - } - - void drawFrame() { - while ( vk::Result::eTimeout == device.waitForFences( *inFlightFences[currentFrame], vk::True, UINT64_MAX ) ) - ; - auto [result, imageIndex] = swapChain.acquireNextImage( UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr ); - - if (result == vk::Result::eErrorOutOfDateKHR) { - recreateSwapChain(); - return; - } - if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) { - throw std::runtime_error("failed to acquire swap chain image!"); - } - updateUniformBuffer(currentFrame); - - device.resetFences( *inFlightFences[currentFrame] ); - commandBuffers[currentFrame].reset(); - recordCommandBuffer(imageIndex); - - vk::PipelineStageFlags waitDestinationStageMask( vk::PipelineStageFlagBits::eColorAttachmentOutput ); - const vk::SubmitInfo submitInfo{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], - .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], - .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex] }; - queue.submit(submitInfo, *inFlightFences[currentFrame]); - - - const vk::PresentInfoKHR presentInfoKHR{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], - .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex }; - result = queue.presentKHR( presentInfoKHR ); - if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { - framebufferResized = false; - recreateSwapChain(); - } else if (result != vk::Result::eSuccess) { - throw std::runtime_error("failed to present swap chain image!"); - } - semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; - } - - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size(), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static vk::Format chooseSwapSurfaceFormat(const std::vector& availableFormats) { - const auto formatIt = std::ranges::find_if(availableFormats, - [](const auto& format) { - return format.format == vk::Format::eB8G8R8A8Srgb && - format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; - }); - return formatIt != availableFormats.end() ? formatIt->format : availableFormats[0].format; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : 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) - }; - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - 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; - } -}; - -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/25_sampler.cpp b/attachments/25_sampler.cpp index 3bcd184a..b4d46e99 100644 --- a/attachments/25_sampler.cpp +++ b/attachments/25_sampler.cpp @@ -1,24 +1,24 @@ -#include -#include -#include -#include -#include -#include -#include #include -#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include #define GLM_FORCE_RADIANS @@ -28,13 +28,12 @@ import vulkan_hpp; #define STB_IMAGE_IMPLEMENTATION #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -constexpr int MAX_FRAMES_IN_FLIGHT = 2; +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; +constexpr int MAX_FRAMES_IN_FLIGHT = 2; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -42,875 +41,923 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -struct Vertex { - glm::vec2 pos; - glm::vec3 color; - - static vk::VertexInputBindingDescription getBindingDescription() { - return { 0, sizeof(Vertex), vk::VertexInputRate::eVertex }; - } - - static std::array getAttributeDescriptions() { - return { - vk::VertexInputAttributeDescription( 0, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, pos) ), - vk::VertexInputAttributeDescription( 1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color) ) - }; - } +struct Vertex +{ + glm::vec2 pos; + glm::vec3 color; + + static vk::VertexInputBindingDescription getBindingDescription() + { + return {0, sizeof(Vertex), vk::VertexInputRate::eVertex}; + } + + static std::array getAttributeDescriptions() + { + return { + vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, pos)), + vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color))}; + } }; -struct UniformBufferObject { - alignas(16) glm::mat4 model; - alignas(16) glm::mat4 view; - alignas(16) glm::mat4 proj; +struct UniformBufferObject +{ + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; }; const std::vector vertices = { {{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}}, {{0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}}, {{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}, - {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}} -}; + {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}}}; const std::vector indices = { - 0, 1, 2, 2, 3, 0 + 0, 1, 2, 2, 3, 0}; + +class HelloTriangleApplication +{ + public: + void run() + { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + + private: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::Format swapChainImageFormat = vk::Format::eUndefined; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + + vk::raii::Image textureImage = nullptr; + vk::raii::DeviceMemory textureImageMemory = nullptr; + vk::raii::ImageView textureImageView = nullptr; + vk::raii::Sampler textureSampler = nullptr; + + vk::raii::Buffer vertexBuffer = nullptr; + vk::raii::DeviceMemory vertexBufferMemory = nullptr; + vk::raii::Buffer indexBuffer = nullptr; + vk::raii::DeviceMemory indexBufferMemory = nullptr; + + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + std::vector uniformBuffersMapped; + + vk::raii::DescriptorPool descriptorPool = nullptr; + std::vector descriptorSets; + + vk::raii::CommandPool commandPool = nullptr; + std::vector commandBuffers; + + std::vector presentCompleteSemaphore; + std::vector renderFinishedSemaphore; + std::vector inFlightFences; + uint32_t semaphoreIndex = 0; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow *window, int width, int height) + { + auto app = static_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createDescriptorSetLayout(); + createGraphicsPipeline(); + createCommandPool(); + createTextureImage(); + createTextureImageView(); + createTextureSampler(); + createVertexBuffer(); + createIndexBuffer(); + createUniformBuffers(); + createDescriptorPool(); + createDescriptorSets(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() + { + while (!glfwWindowShouldClose(window)) + { + glfwPollEvents(); + drawFrame(); + } + + device.waitIdle(); + } + + void cleanupSwapChain() + { + swapChainImageViews.clear(); + swapChain = nullptr; + } + + void cleanup() + { + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() + { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) + { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + device.waitIdle(); + + cleanupSwapChain(); + createSwapChain(); + createImageViews(); + } + + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().features.samplerAnisotropy && + features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain featureChain = { + {.features = {.samplerAnisotropy = true}}, // vk::PhysicalDeviceFeatures2 + {.synchronization2 = true, .dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface); + swapChainImageFormat = 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{ + .surface = surface, .minImageCount = minImageCount, .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + void createImageViews() + { + 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(device, imageViewCreateInfo); + } + } + + void createDescriptorSetLayout() + { + vk::DescriptorSetLayoutBinding uboLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr); + vk::DescriptorSetLayoutCreateInfo layoutInfo{.bindingCount = 1, .pBindings = &uboLayoutBinding}; + descriptorSetLayout = vk::raii::DescriptorSetLayout(device, layoutInfo); + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; + + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{.vertexBindingDescriptionCount = 1, .pVertexBindingDescriptions = &bindingDescription, .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), .pVertexAttributeDescriptions = attributeDescriptions.data()}; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{.topology = vk::PrimitiveTopology::eTriangleList}; + vk::PipelineViewportStateCreateInfo viewportState{.viewportCount = 1, .scissorCount = 1}; + + vk::PipelineRasterizationStateCreateInfo rasterizer{.depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, .frontFace = vk::FrontFace::eCounterClockwise, .depthBiasEnable = vk::False, .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f}; + + vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; + + vk::PipelineColorBlendAttachmentState colorBlendAttachment{.blendEnable = vk::False, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA}; + + vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0}; + + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainImageFormat}; + vk::GraphicsPipelineCreateInfo pipelineInfo{.pNext = &pipelineRenderingCreateInfo, + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex}; + commandPool = vk::raii::CommandPool(device, poolInfo); + } + + void createTextureImage() + { + int texWidth, texHeight, texChannels; + stbi_uc *pixels = stbi_load("textures/texture.jpg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + vk::DeviceSize imageSize = texWidth * texHeight * 4; + + if (!pixels) + { + throw std::runtime_error("failed to load texture image!"); + } + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, imageSize); + memcpy(data, pixels, imageSize); + stagingBufferMemory.unmapMemory(); + + stbi_image_free(pixels); + + createImage(texWidth, texHeight, vk::Format::eR8G8B8A8Srgb, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory); + + transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); + transitionImageLayout(textureImage, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal); + } + + void createTextureImageView() + { + textureImageView = createImageView(textureImage, vk::Format::eR8G8B8A8Srgb); + } + + void createTextureSampler() + { + vk::PhysicalDeviceProperties properties = physicalDevice.getProperties(); + vk::SamplerCreateInfo samplerInfo{ + .magFilter = vk::Filter::eLinear, + .minFilter = vk::Filter::eLinear, + .mipmapMode = vk::SamplerMipmapMode::eLinear, + .addressModeU = vk::SamplerAddressMode::eRepeat, + .addressModeV = vk::SamplerAddressMode::eRepeat, + .addressModeW = vk::SamplerAddressMode::eRepeat, + .mipLodBias = 0.0f, + .anisotropyEnable = vk::True, + .maxAnisotropy = properties.limits.maxSamplerAnisotropy, + .compareEnable = vk::False, + .compareOp = vk::CompareOp::eAlways}; + textureSampler = vk::raii::Sampler(device, samplerInfo); + } + + vk::raii::ImageView createImageView(vk::raii::Image &image, vk::Format format) + { + vk::ImageViewCreateInfo viewInfo{ + .image = image, + .viewType = vk::ImageViewType::e2D, + .format = format, + .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + return vk::raii::ImageView(device, viewInfo); + } + + void createImage(uint32_t width, uint32_t height, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image &image, vk::raii::DeviceMemory &imageMemory) + { + vk::ImageCreateInfo imageInfo{.imageType = vk::ImageType::e2D, .format = format, .extent = {width, height, 1}, .mipLevels = 1, .arrayLayers = 1, .samples = vk::SampleCountFlagBits::e1, .tiling = tiling, .usage = usage, .sharingMode = vk::SharingMode::eExclusive}; + + image = vk::raii::Image(device, imageInfo); + + vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{.allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + imageMemory = vk::raii::DeviceMemory(device, allocInfo); + image.bindMemory(imageMemory, 0); + } + + void transitionImageLayout(const vk::raii::Image &image, vk::ImageLayout oldLayout, vk::ImageLayout newLayout) + { + auto commandBuffer = beginSingleTimeCommands(); + + vk::ImageMemoryBarrier barrier{.oldLayout = oldLayout, .newLayout = newLayout, .image = image, .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + + vk::PipelineStageFlags sourceStage; + vk::PipelineStageFlags destinationStage; + + if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) + { + barrier.srcAccessMask = {}; + barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; + + sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; + destinationStage = vk::PipelineStageFlagBits::eTransfer; + } + else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) + { + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + + sourceStage = vk::PipelineStageFlagBits::eTransfer; + destinationStage = vk::PipelineStageFlagBits::eFragmentShader; + } + else + { + throw std::invalid_argument("unsupported layout transition!"); + } + commandBuffer->pipelineBarrier(sourceStage, destinationStage, {}, {}, nullptr, barrier); + endSingleTimeCommands(*commandBuffer); + } + + void copyBufferToImage(const vk::raii::Buffer &buffer, vk::raii::Image &image, uint32_t width, uint32_t height) + { + std::unique_ptr commandBuffer = beginSingleTimeCommands(); + vk::BufferImageCopy region{.bufferOffset = 0, .bufferRowLength = 0, .bufferImageHeight = 0, .imageSubresource = {vk::ImageAspectFlagBits::eColor, 0, 0, 1}, .imageOffset = {0, 0, 0}, .imageExtent = {width, height, 1}}; + commandBuffer->copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, {region}); + endSingleTimeCommands(*commandBuffer); + } + + void createVertexBuffer() + { + vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(dataStaging, vertices.data(), bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + } + + void createIndexBuffer() + { + vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(data, indices.data(), bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); + + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + } + + void createUniformBuffers() + { + uniformBuffers.clear(); + uniformBuffersMemory.clear(); + uniformBuffersMapped.clear(); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DeviceSize bufferSize = sizeof(UniformBufferObject); + vk::raii::Buffer buffer({}); + vk::raii::DeviceMemory bufferMem({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); + uniformBuffers.emplace_back(std::move(buffer)); + uniformBuffersMemory.emplace_back(std::move(bufferMem)); + uniformBuffersMapped.emplace_back(uniformBuffersMemory[i].mapMemory(0, bufferSize)); + } + } + + void createDescriptorPool() + { + vk::DescriptorPoolSize poolSize(vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT); + vk::DescriptorPoolCreateInfo poolInfo{.flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, .maxSets = MAX_FRAMES_IN_FLIGHT, .poolSizeCount = 1, .pPoolSizes = &poolSize}; + descriptorPool = vk::raii::DescriptorPool(device, poolInfo); + } + + void createDescriptorSets() + { + std::vector layouts(MAX_FRAMES_IN_FLIGHT, *descriptorSetLayout); + vk::DescriptorSetAllocateInfo allocInfo{.descriptorPool = descriptorPool, .descriptorSetCount = static_cast(layouts.size()), .pSetLayouts = layouts.data()}; + + descriptorSets = device.allocateDescriptorSets(allocInfo); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DescriptorBufferInfo bufferInfo{.buffer = uniformBuffers[i], .offset = 0, .range = sizeof(UniformBufferObject)}; + vk::WriteDescriptorSet descriptorWrite{.dstSet = descriptorSets[i], .dstBinding = 0, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eUniformBuffer, .pBufferInfo = &bufferInfo}; + device.updateDescriptorSets(descriptorWrite, {}); + } + } + + void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer &buffer, vk::raii::DeviceMemory &bufferMemory) + { + vk::BufferCreateInfo bufferInfo{.size = size, .usage = usage, .sharingMode = vk::SharingMode::eExclusive}; + buffer = vk::raii::Buffer(device, bufferInfo); + vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{.allocationSize = memRequirements.size, .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + bufferMemory = vk::raii::DeviceMemory(device, allocInfo); + buffer.bindMemory(bufferMemory, 0); + } + + std::unique_ptr beginSingleTimeCommands() + { + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1}; + std::unique_ptr commandBuffer = std::make_unique(std::move(vk::raii::CommandBuffers(device, allocInfo).front())); + + vk::CommandBufferBeginInfo beginInfo{.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; + commandBuffer->begin(beginInfo); + + return commandBuffer; + } + + void endSingleTimeCommands(vk::raii::CommandBuffer &commandBuffer) + { + commandBuffer.end(); + + vk::SubmitInfo submitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandBuffer}; + queue.submit(submitInfo, nullptr); + queue.waitIdle(); + } + + void copyBuffer(vk::raii::Buffer &srcBuffer, vk::raii::Buffer &dstBuffer, vk::DeviceSize size) + { + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1}; + vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); + commandCopyBuffer.begin(vk::CommandBufferBeginInfo{.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); + commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy{.size = size}); + commandCopyBuffer.end(); + queue.submit(vk::SubmitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer}, nullptr); + queue.waitIdle(); + } + + uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) + { + vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) + { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) + { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + void createCommandBuffers() + { + commandBuffers.clear(); + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = MAX_FRAMES_IN_FLIGHT}; + commandBuffers = vk::raii::CommandBuffers(device, allocInfo); + } + + void recordCommandBuffer(uint32_t imageIndex) + { + commandBuffers[currentFrame].begin({}); + // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL + transition_image_layout( + imageIndex, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage + ); + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::RenderingAttachmentInfo attachmentInfo = { + .imageView = swapChainImageViews[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor}; + vk::RenderingInfo renderingInfo = { + .renderArea = {.offset = {0, 0}, .extent = swapChainExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &attachmentInfo}; + commandBuffers[currentFrame].beginRendering(renderingInfo); + commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint16); + commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); + commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); + commandBuffers[currentFrame].endRendering(); + // After rendering, transition the swapchain image to PRESENT_SRC + transition_image_layout( + imageIndex, + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage + ); + commandBuffers[currentFrame].end(); + } + + void transition_image_layout( + uint32_t imageIndex, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout, + vk::AccessFlags2 src_access_mask, + vk::AccessFlags2 dst_access_mask, + vk::PipelineStageFlags2 src_stage_mask, + vk::PipelineStageFlags2 dst_stage_mask) + { + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = src_stage_mask, + .srcAccessMask = src_access_mask, + .dstStageMask = dst_stage_mask, + .dstAccessMask = dst_access_mask, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo dependency_info = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + } + + void createSyncObjects() + { + presentCompleteSemaphore.clear(); + renderFinishedSemaphore.clear(); + inFlightFences.clear(); + + for (size_t i = 0; i < swapChainImages.size(); i++) + { + presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); + } + } + + void updateUniformBuffer(uint32_t currentImage) + { + static auto startTime = std::chrono::high_resolution_clock::now(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + UniformBufferObject ubo{}; + ubo.model = rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.view = lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); + ubo.proj[1][1] *= -1; + + memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); + } + + void drawFrame() + { + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + ; + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr); + + if (result == vk::Result::eErrorOutOfDateKHR) + { + recreateSwapChain(); + return; + } + if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) + { + throw std::runtime_error("failed to acquire swap chain image!"); + } + updateUniformBuffer(currentFrame); + + device.resetFences(*inFlightFences[currentFrame]); + commandBuffers[currentFrame].reset(); + recordCommandBuffer(imageIndex); + + vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[currentFrame]); + + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + result = queue.presentKHR(presentInfoKHR); + if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) + { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != vk::Result::eSuccess) + { + throw std::runtime_error("failed to present swap chain image!"); + } + semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size(), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static vk::Format chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + const auto formatIt = std::ranges::find_if(availableFormats, + [](const auto &format) { + return format.format == vk::Format::eB8G8R8A8Srgb && + format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; + }); + return formatIt != availableFormats.end() ? formatIt->format : availableFormats[0].format; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + 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)}; + } + + std::vector getRequiredExtensions() + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + 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: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::Format swapChainImageFormat = vk::Format::eUndefined; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - - vk::raii::Image textureImage = nullptr; - vk::raii::DeviceMemory textureImageMemory = nullptr; - vk::raii::ImageView textureImageView = nullptr; - vk::raii::Sampler textureSampler = nullptr; - - vk::raii::Buffer vertexBuffer = nullptr; - vk::raii::DeviceMemory vertexBufferMemory = nullptr; - vk::raii::Buffer indexBuffer = nullptr; - vk::raii::DeviceMemory indexBufferMemory = nullptr; - - std::vector uniformBuffers; - std::vector uniformBuffersMemory; - std::vector uniformBuffersMapped; - - vk::raii::DescriptorPool descriptorPool = nullptr; - std::vector descriptorSets; - - vk::raii::CommandPool commandPool = nullptr; - std::vector commandBuffers; - - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; - std::vector inFlightFences; - uint32_t semaphoreIndex = 0; - uint32_t currentFrame = 0; - - bool framebufferResized = false; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); - } - - static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { - auto app = static_cast(glfwGetWindowUserPointer(window)); - app->framebufferResized = true; - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - createSurface(); - pickPhysicalDevice(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createDescriptorSetLayout(); - createGraphicsPipeline(); - createCommandPool(); - createTextureImage(); - createTextureImageView(); - createTextureSampler(); - createVertexBuffer(); - createIndexBuffer(); - createUniformBuffers(); - createDescriptorPool(); - createDescriptorSets(); - createCommandBuffers(); - createSyncObjects(); - } - - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - drawFrame(); - } - - device.waitIdle(); - } - - void cleanupSwapChain() { - swapChainImageViews.clear(); - swapChain = nullptr; - } - - void cleanup() { - glfwDestroyWindow(window); - - glfwTerminate(); - } - - void recreateSwapChain() { - int width = 0, height = 0; - glfwGetFramebufferSize(window, &width, &height); - while (width == 0 || height == 0) { - glfwGetFramebufferSize(window, &width, &height); - glfwWaitEvents(); - } - - device.waitIdle(); - - cleanupSwapChain(); - createSwapChain(); - createImageViews(); - } - - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().features.samplerAnisotropy && - features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - } ); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error( "failed to find a suitable GPU!" ); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain featureChain = { - {.features = {.samplerAnisotropy = true } }, // vk::PhysicalDeviceFeatures2 - {.synchronization2 = true, .dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( surface ); - swapChainImageFormat = 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{ - .surface = surface, .minImageCount = minImageCount, - .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, - .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 }; - - swapChain = vk::raii::SwapchainKHR( device, swapChainCreateInfo ); - swapChainImages = swapChain.getImages(); - } - - void createImageViews() { - 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( device, imageViewCreateInfo ); - } - } - - void createDescriptorSetLayout() { - vk::DescriptorSetLayoutBinding uboLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr); - vk::DescriptorSetLayoutCreateInfo layoutInfo{ .bindingCount = 1, .pBindings = &uboLayoutBinding }; - descriptorSetLayout = vk::raii::DescriptorSetLayout( device, layoutInfo ); - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; - - auto bindingDescription = Vertex::getBindingDescription(); - auto attributeDescriptions = Vertex::getAttributeDescriptions(); - vk::PipelineVertexInputStateCreateInfo vertexInputInfo { .vertexBindingDescriptionCount =1, .pVertexBindingDescriptions = &bindingDescription, - .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), .pVertexAttributeDescriptions = attributeDescriptions.data() }; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ .topology = vk::PrimitiveTopology::eTriangleList }; - vk::PipelineViewportStateCreateInfo viewportState{ .viewportCount = 1, .scissorCount = 1 }; - - vk::PipelineRasterizationStateCreateInfo rasterizer{ .depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eCounterClockwise, .depthBiasEnable = vk::False, - .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f }; - - vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; - - vk::PipelineColorBlendAttachmentState colorBlendAttachment{ .blendEnable = vk::False, - .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA - }; - - vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0 }; - - pipelineLayout = vk::raii::PipelineLayout( device, pipelineLayoutInfo ); - - vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ .colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainImageFormat }; - vk::GraphicsPipelineCreateInfo pipelineInfo{ .pNext = &pipelineRenderingCreateInfo, - .stageCount = 2, .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, .layout = pipelineLayout, .renderPass = nullptr }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{ .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex }; - commandPool = vk::raii::CommandPool(device, poolInfo); - } - - void createTextureImage() { - int texWidth, texHeight, texChannels; - stbi_uc* pixels = stbi_load("textures/texture.jpg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); - vk::DeviceSize imageSize = texWidth * texHeight * 4; - - if (!pixels) { - throw std::runtime_error("failed to load texture image!"); - } - - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, imageSize); - memcpy(data, pixels, imageSize); - stagingBufferMemory.unmapMemory(); - - stbi_image_free(pixels); - - createImage(texWidth, texHeight, vk::Format::eR8G8B8A8Srgb, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory); - - transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal); - copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); - transitionImageLayout(textureImage, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal); - } - - void createTextureImageView() { - textureImageView = createImageView(textureImage, vk::Format::eR8G8B8A8Srgb); - } - - void createTextureSampler() { - vk::PhysicalDeviceProperties properties = physicalDevice.getProperties(); - vk::SamplerCreateInfo samplerInfo{ - .magFilter = vk::Filter::eLinear, - .minFilter = vk::Filter::eLinear, - .mipmapMode = vk::SamplerMipmapMode::eLinear, - .addressModeU = vk::SamplerAddressMode::eRepeat, - .addressModeV = vk::SamplerAddressMode::eRepeat, - .addressModeW = vk::SamplerAddressMode::eRepeat, - .mipLodBias = 0.0f, - .anisotropyEnable = vk::True, - .maxAnisotropy = properties.limits.maxSamplerAnisotropy, - .compareEnable = vk::False, - .compareOp = vk::CompareOp::eAlways }; - textureSampler = vk::raii::Sampler(device, samplerInfo); - } - - vk::raii::ImageView createImageView(vk::raii::Image& image, vk::Format format) { - vk::ImageViewCreateInfo viewInfo{ - .image = image, - .viewType = vk::ImageViewType::e2D, - .format = format, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } }; - return vk::raii::ImageView(device, viewInfo); - } - - void createImage(uint32_t width, uint32_t height, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image& image, vk::raii::DeviceMemory& imageMemory) { - vk::ImageCreateInfo imageInfo{ .imageType = vk::ImageType::e2D, .format = format, - .extent = {width, height, 1}, .mipLevels = 1, .arrayLayers = 1, - .samples = vk::SampleCountFlagBits::e1, .tiling = tiling, - .usage = usage, .sharingMode = vk::SharingMode::eExclusive }; - - image = vk::raii::Image( device, imageInfo ); - - vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) }; - imageMemory = vk::raii::DeviceMemory( device, allocInfo ); - image.bindMemory(imageMemory, 0); - } - - void transitionImageLayout(const vk::raii::Image& image, vk::ImageLayout oldLayout, vk::ImageLayout newLayout) { - auto commandBuffer = beginSingleTimeCommands(); - - vk::ImageMemoryBarrier barrier{ .oldLayout = oldLayout, .newLayout = newLayout, - .image = image, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } }; - - vk::PipelineStageFlags sourceStage; - vk::PipelineStageFlags destinationStage; - - if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) { - barrier.srcAccessMask = {}; - barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; - - sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; - destinationStage = vk::PipelineStageFlagBits::eTransfer; - } else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) { - barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; - barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - - sourceStage = vk::PipelineStageFlagBits::eTransfer; - destinationStage = vk::PipelineStageFlagBits::eFragmentShader; - } else { - throw std::invalid_argument("unsupported layout transition!"); - } - commandBuffer->pipelineBarrier(sourceStage, destinationStage, {}, {}, nullptr, barrier); - endSingleTimeCommands(*commandBuffer); - } - - void copyBufferToImage(const vk::raii::Buffer& buffer, vk::raii::Image& image, uint32_t width, uint32_t height) { - std::unique_ptr commandBuffer = beginSingleTimeCommands(); - vk::BufferImageCopy region{ .bufferOffset = 0, .bufferRowLength = 0, .bufferImageHeight = 0, - .imageSubresource = { vk::ImageAspectFlagBits::eColor, 0, 0, 1 }, - .imageOffset = {0, 0, 0}, .imageExtent = {width, height, 1} }; - commandBuffer->copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, {region}); - endSingleTimeCommands(*commandBuffer); - } - - void createVertexBuffer() { - vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(dataStaging, vertices.data(), bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); - - copyBuffer(stagingBuffer, vertexBuffer, bufferSize); - } - - void createIndexBuffer() { - vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); - - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(data, indices.data(), bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); - - copyBuffer(stagingBuffer, indexBuffer, bufferSize); - } - - void createUniformBuffers() { - uniformBuffers.clear(); - uniformBuffersMemory.clear(); - uniformBuffersMapped.clear(); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DeviceSize bufferSize = sizeof(UniformBufferObject); - vk::raii::Buffer buffer({}); - vk::raii::DeviceMemory bufferMem({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); - uniformBuffers.emplace_back(std::move(buffer)); - uniformBuffersMemory.emplace_back(std::move(bufferMem)); - uniformBuffersMapped.emplace_back( uniformBuffersMemory[i].mapMemory(0, bufferSize)); - } - } - - void createDescriptorPool() { - vk::DescriptorPoolSize poolSize(vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT); - vk::DescriptorPoolCreateInfo poolInfo{ .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, .maxSets = MAX_FRAMES_IN_FLIGHT, .poolSizeCount = 1, .pPoolSizes = &poolSize }; - descriptorPool = vk::raii::DescriptorPool(device, poolInfo); - } - - void createDescriptorSets() { - std::vector layouts(MAX_FRAMES_IN_FLIGHT, *descriptorSetLayout); - vk::DescriptorSetAllocateInfo allocInfo{ .descriptorPool = descriptorPool, .descriptorSetCount = static_cast(layouts.size()), .pSetLayouts = layouts.data() }; - - descriptorSets = device.allocateDescriptorSets(allocInfo); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DescriptorBufferInfo bufferInfo{ .buffer = uniformBuffers[i], .offset = 0, .range = sizeof(UniformBufferObject) }; - vk::WriteDescriptorSet descriptorWrite{ .dstSet = descriptorSets[i], .dstBinding = 0, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eUniformBuffer, .pBufferInfo = &bufferInfo }; - device.updateDescriptorSets(descriptorWrite, {}); - } - } - - void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer& buffer, vk::raii::DeviceMemory& bufferMemory) { - vk::BufferCreateInfo bufferInfo{ .size = size, .usage = usage, .sharingMode = vk::SharingMode::eExclusive }; - buffer = vk::raii::Buffer(device, bufferInfo); - vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ .allocationSize =memRequirements.size, .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) }; - bufferMemory = vk::raii::DeviceMemory(device, allocInfo); - buffer.bindMemory(bufferMemory, 0); - } - - std::unique_ptr beginSingleTimeCommands() { - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1 }; - std::unique_ptr commandBuffer = std::make_unique(std::move(vk::raii::CommandBuffers(device, allocInfo).front())); - - vk::CommandBufferBeginInfo beginInfo{ .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit }; - commandBuffer->begin(beginInfo); - - return commandBuffer; - } - - void endSingleTimeCommands(vk::raii::CommandBuffer& commandBuffer) { - commandBuffer.end(); - - vk::SubmitInfo submitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandBuffer }; - queue.submit(submitInfo, nullptr); - queue.waitIdle(); - } - - void copyBuffer(vk::raii::Buffer & srcBuffer, vk::raii::Buffer & dstBuffer, vk::DeviceSize size) { - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1 }; - vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); - commandCopyBuffer.begin(vk::CommandBufferBeginInfo{ .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit }); - commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy{ .size = size }); - commandCopyBuffer.end(); - queue.submit(vk::SubmitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer }, nullptr); - queue.waitIdle(); - } - - uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) { - vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); - - for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { - if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { - return i; - } - } - - throw std::runtime_error("failed to find suitable memory type!"); - } - - void createCommandBuffers() { - commandBuffers.clear(); - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = MAX_FRAMES_IN_FLIGHT }; - commandBuffers = vk::raii::CommandBuffers( device, allocInfo ); - } - - void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin( {} ); - // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL - transition_image_layout( - imageIndex, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage - ); - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::RenderingAttachmentInfo attachmentInfo = { - .imageView = swapChainImageViews[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor - }; - vk::RenderingInfo renderingInfo = { - .renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &attachmentInfo - }; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].bindIndexBuffer( *indexBuffer, 0, vk::IndexType::eUint16 ); - commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); - commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); - commandBuffers[currentFrame].endRendering(); - // After rendering, transition the swapchain image to PRESENT_SRC - transition_image_layout( - imageIndex, - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage - ); - commandBuffers[currentFrame].end(); - } - - void transition_image_layout( - uint32_t imageIndex, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::AccessFlags2 src_access_mask, - vk::AccessFlags2 dst_access_mask, - vk::PipelineStageFlags2 src_stage_mask, - vk::PipelineStageFlags2 dst_stage_mask - ) { - vk::ImageMemoryBarrier2 barrier = { - .srcStageMask = src_stage_mask, - .srcAccessMask = src_access_mask, - .dstStageMask = dst_stage_mask, - .dstAccessMask = dst_access_mask, - .oldLayout = old_layout, - .newLayout = new_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); - } - - void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); - - for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - } - - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - inFlightFences.emplace_back(device, vk::FenceCreateInfo { .flags = vk::FenceCreateFlagBits::eSignaled }); - } - } - - void updateUniformBuffer(uint32_t currentImage) { - static auto startTime = std::chrono::high_resolution_clock::now(); - - auto currentTime = std::chrono::high_resolution_clock::now(); - float time = std::chrono::duration(currentTime - startTime).count(); - - UniformBufferObject ubo{}; - ubo.model = rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.view = lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); - ubo.proj[1][1] *= -1; - - memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); - } - - void drawFrame() { - while ( vk::Result::eTimeout == device.waitForFences( *inFlightFences[currentFrame], vk::True, UINT64_MAX ) ) - ; - auto [result, imageIndex] = swapChain.acquireNextImage( UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr ); - - if (result == vk::Result::eErrorOutOfDateKHR) { - recreateSwapChain(); - return; - } - if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) { - throw std::runtime_error("failed to acquire swap chain image!"); - } - updateUniformBuffer(currentFrame); - - device.resetFences( *inFlightFences[currentFrame] ); - commandBuffers[currentFrame].reset(); - recordCommandBuffer(imageIndex); - - vk::PipelineStageFlags waitDestinationStageMask( vk::PipelineStageFlagBits::eColorAttachmentOutput ); - const vk::SubmitInfo submitInfo{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], - .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], - .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex] }; - queue.submit(submitInfo, *inFlightFences[currentFrame]); - - - const vk::PresentInfoKHR presentInfoKHR{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], - .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex }; - result = queue.presentKHR( presentInfoKHR ); - if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { - framebufferResized = false; - recreateSwapChain(); - } else if (result != vk::Result::eSuccess) { - throw std::runtime_error("failed to present swap chain image!"); - } - semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; - } - - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size(), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static vk::Format chooseSwapSurfaceFormat(const std::vector& availableFormats) { - const auto formatIt = std::ranges::find_if(availableFormats, - [](const auto& format) { - return format.format == vk::Format::eB8G8R8A8Srgb && - format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; - }); - return formatIt != availableFormats.end() ? formatIt->format : availableFormats[0].format; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : 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) - }; - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - 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; - } -}; - -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/26_texture_mapping.cpp b/attachments/26_texture_mapping.cpp index 9f50edd2..8849338d 100644 --- a/attachments/26_texture_mapping.cpp +++ b/attachments/26_texture_mapping.cpp @@ -1,24 +1,24 @@ -#include -#include -#include -#include -#include -#include -#include #include -#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include #define GLM_FORCE_RADIANS @@ -28,13 +28,12 @@ import vulkan_hpp; #define STB_IMAGE_IMPLEMENTATION #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -constexpr int MAX_FRAMES_IN_FLIGHT = 2; +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; +constexpr int MAX_FRAMES_IN_FLIGHT = 2; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -42,952 +41,988 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -struct Vertex { - glm::vec2 pos; - glm::vec3 color; - glm::vec2 texCoord; - - static vk::VertexInputBindingDescription getBindingDescription() { - return { 0, sizeof(Vertex), vk::VertexInputRate::eVertex }; - } - - static std::array getAttributeDescriptions() { - return { - vk::VertexInputAttributeDescription( 0, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, pos) ), - vk::VertexInputAttributeDescription( 1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color) ), - vk::VertexInputAttributeDescription( 2, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, texCoord) ) - }; - } +struct Vertex +{ + glm::vec2 pos; + glm::vec3 color; + glm::vec2 texCoord; + + static vk::VertexInputBindingDescription getBindingDescription() + { + return {0, sizeof(Vertex), vk::VertexInputRate::eVertex}; + } + + static std::array getAttributeDescriptions() + { + return { + vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, pos)), + vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color)), + vk::VertexInputAttributeDescription(2, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, texCoord))}; + } }; -struct UniformBufferObject { - alignas(16) glm::mat4 model; - alignas(16) glm::mat4 view; - alignas(16) glm::mat4 proj; +struct UniformBufferObject +{ + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; }; const std::vector vertices = { {{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}, {1.0f, 0.0f}}, {{0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f}}, {{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}, {0.0f, 1.0f}}, - {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}, {1.0f, 1.0f}} -}; + {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}, {1.0f, 1.0f}}}; const std::vector indices = { - 0, 1, 2, 2, 3, 0 + 0, 1, 2, 2, 3, 0}; + +class HelloTriangleApplication +{ + public: + void run() + { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + + private: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::Format swapChainImageFormat = vk::Format::eUndefined; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + + vk::raii::Image textureImage = nullptr; + vk::raii::DeviceMemory textureImageMemory = nullptr; + vk::raii::ImageView textureImageView = nullptr; + vk::raii::Sampler textureSampler = nullptr; + + vk::raii::Buffer vertexBuffer = nullptr; + vk::raii::DeviceMemory vertexBufferMemory = nullptr; + vk::raii::Buffer indexBuffer = nullptr; + vk::raii::DeviceMemory indexBufferMemory = nullptr; + + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + std::vector uniformBuffersMapped; + + vk::raii::DescriptorPool descriptorPool = nullptr; + std::vector descriptorSets; + + vk::raii::CommandPool commandPool = nullptr; + std::vector commandBuffers; + + std::vector presentCompleteSemaphore; + std::vector renderFinishedSemaphore; + std::vector inFlightFences; + uint32_t semaphoreIndex = 0; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow *window, int width, int height) + { + auto app = static_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createDescriptorSetLayout(); + createGraphicsPipeline(); + createCommandPool(); + createTextureImage(); + createTextureImageView(); + createTextureSampler(); + createVertexBuffer(); + createIndexBuffer(); + createUniformBuffers(); + createDescriptorPool(); + createDescriptorSets(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() + { + while (!glfwWindowShouldClose(window)) + { + glfwPollEvents(); + drawFrame(); + } + + device.waitIdle(); + } + + void cleanupSwapChain() + { + swapChainImageViews.clear(); + swapChain = nullptr; + } + + void cleanup() + { + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() + { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) + { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + device.waitIdle(); + + cleanupSwapChain(); + createSwapChain(); + createImageViews(); + } + + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().features.samplerAnisotropy && + features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain featureChain = { + {.features = {.samplerAnisotropy = true}}, // vk::PhysicalDeviceFeatures2 + {.synchronization2 = true, .dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface); + swapChainImageFormat = 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{ + .surface = surface, .minImageCount = minImageCount, .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + void createImageViews() + { + 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(device, imageViewCreateInfo); + } + } + + void createDescriptorSetLayout() + { + std::array bindings = { + vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr), + vk::DescriptorSetLayoutBinding(1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment, nullptr)}; + + vk::DescriptorSetLayoutCreateInfo layoutInfo{.bindingCount = static_cast(bindings.size()), .pBindings = bindings.data()}; + descriptorSetLayout = vk::raii::DescriptorSetLayout(device, layoutInfo); + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; + + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{.vertexBindingDescriptionCount = 1, .pVertexBindingDescriptions = &bindingDescription, .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), .pVertexAttributeDescriptions = attributeDescriptions.data()}; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{.topology = vk::PrimitiveTopology::eTriangleList}; + vk::PipelineViewportStateCreateInfo viewportState{.viewportCount = 1, .scissorCount = 1}; + + vk::PipelineRasterizationStateCreateInfo rasterizer{.depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, .frontFace = vk::FrontFace::eCounterClockwise, .depthBiasEnable = vk::False, .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f}; + + vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; + + vk::PipelineColorBlendAttachmentState colorBlendAttachment{.blendEnable = vk::False, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA}; + + vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0}; + + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainImageFormat}; + vk::GraphicsPipelineCreateInfo pipelineInfo{.pNext = &pipelineRenderingCreateInfo, + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex}; + commandPool = vk::raii::CommandPool(device, poolInfo); + } + + void createTextureImage() + { + int texWidth, texHeight, texChannels; + stbi_uc *pixels = stbi_load("textures/texture.jpg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + vk::DeviceSize imageSize = texWidth * texHeight * 4; + + if (!pixels) + { + throw std::runtime_error("failed to load texture image!"); + } + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, imageSize); + memcpy(data, pixels, imageSize); + stagingBufferMemory.unmapMemory(); + + stbi_image_free(pixels); + + createImage(texWidth, texHeight, vk::Format::eR8G8B8A8Srgb, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory); + + transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); + transitionImageLayout(textureImage, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal); + } + + void createTextureImageView() + { + textureImageView = createImageView(textureImage, vk::Format::eR8G8B8A8Srgb, vk::ImageAspectFlagBits::eColor); + } + + void createTextureSampler() + { + vk::PhysicalDeviceProperties properties = physicalDevice.getProperties(); + vk::SamplerCreateInfo samplerInfo{ + .magFilter = vk::Filter::eLinear, + .minFilter = vk::Filter::eLinear, + .mipmapMode = vk::SamplerMipmapMode::eLinear, + .addressModeU = vk::SamplerAddressMode::eRepeat, + .addressModeV = vk::SamplerAddressMode::eRepeat, + .addressModeW = vk::SamplerAddressMode::eRepeat, + .mipLodBias = 0.0f, + .anisotropyEnable = vk::True, + .maxAnisotropy = properties.limits.maxSamplerAnisotropy, + .compareEnable = vk::False, + .compareOp = vk::CompareOp::eAlways}; + textureSampler = vk::raii::Sampler(device, samplerInfo); + } + + vk::raii::ImageView createImageView(vk::raii::Image &image, vk::Format format, vk::ImageAspectFlags aspectFlags) + { + vk::ImageViewCreateInfo viewInfo{ + .image = image, + .viewType = vk::ImageViewType::e2D, + .format = format, + .subresourceRange = {aspectFlags, 0, 1, 0, 1}}; + return vk::raii::ImageView(device, viewInfo); + } + + void createImage(uint32_t width, uint32_t height, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image &image, vk::raii::DeviceMemory &imageMemory) + { + vk::ImageCreateInfo imageInfo{ + .imageType = vk::ImageType::e2D, + .format = format, + .extent = {width, height, 1}, + .mipLevels = 1, + .arrayLayers = 1, + .samples = vk::SampleCountFlagBits::e1, + .tiling = tiling, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive}; + + image = vk::raii::Image(device, imageInfo); + + vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{ + .allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + imageMemory = vk::raii::DeviceMemory(device, allocInfo); + image.bindMemory(imageMemory, 0); + } + + void transitionImageLayout(const vk::raii::Image &image, vk::ImageLayout oldLayout, vk::ImageLayout newLayout) + { + auto commandBuffer = beginSingleTimeCommands(); + + vk::ImageMemoryBarrier barrier{ + .oldLayout = oldLayout, + .newLayout = newLayout, + .image = image, + .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + + vk::PipelineStageFlags sourceStage; + vk::PipelineStageFlags destinationStage; + + if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) + { + barrier.srcAccessMask = {}; + barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; + + sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; + destinationStage = vk::PipelineStageFlagBits::eTransfer; + } + else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) + { + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + + sourceStage = vk::PipelineStageFlagBits::eTransfer; + destinationStage = vk::PipelineStageFlagBits::eFragmentShader; + } + else + { + throw std::invalid_argument("unsupported layout transition!"); + } + commandBuffer->pipelineBarrier(sourceStage, destinationStage, {}, {}, nullptr, barrier); + endSingleTimeCommands(*commandBuffer); + } + + void copyBufferToImage(const vk::raii::Buffer &buffer, vk::raii::Image &image, uint32_t width, uint32_t height) + { + std::unique_ptr commandBuffer = beginSingleTimeCommands(); + vk::BufferImageCopy region{ + .bufferOffset = 0, + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource = {vk::ImageAspectFlagBits::eColor, 0, 0, 1}, + .imageOffset = {0, 0, 0}, + .imageExtent = {width, height, 1}}; + commandBuffer->copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, {region}); + endSingleTimeCommands(*commandBuffer); + } + + void createVertexBuffer() + { + vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(dataStaging, vertices.data(), bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + } + + void createIndexBuffer() + { + vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(data, indices.data(), bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); + + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + } + + void createUniformBuffers() + { + uniformBuffers.clear(); + uniformBuffersMemory.clear(); + uniformBuffersMapped.clear(); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DeviceSize bufferSize = sizeof(UniformBufferObject); + vk::raii::Buffer buffer({}); + vk::raii::DeviceMemory bufferMem({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); + uniformBuffers.emplace_back(std::move(buffer)); + uniformBuffersMemory.emplace_back(std::move(bufferMem)); + uniformBuffersMapped.emplace_back(uniformBuffersMemory[i].mapMemory(0, bufferSize)); + } + } + + void createDescriptorPool() + { + std::array poolSize{ + vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT), + vk::DescriptorPoolSize(vk::DescriptorType::eCombinedImageSampler, MAX_FRAMES_IN_FLIGHT)}; + vk::DescriptorPoolCreateInfo poolInfo{ + .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, + .maxSets = MAX_FRAMES_IN_FLIGHT, + .poolSizeCount = static_cast(poolSize.size()), + .pPoolSizes = poolSize.data()}; + descriptorPool = vk::raii::DescriptorPool(device, poolInfo); + } + + void createDescriptorSets() + { + std::vector layouts(MAX_FRAMES_IN_FLIGHT, descriptorSetLayout); + vk::DescriptorSetAllocateInfo allocInfo{ + .descriptorPool = descriptorPool, + .descriptorSetCount = static_cast(layouts.size()), + .pSetLayouts = layouts.data()}; + + descriptorSets.clear(); + descriptorSets = device.allocateDescriptorSets(allocInfo); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DescriptorBufferInfo bufferInfo{ + .buffer = uniformBuffers[i], + .offset = 0, + .range = sizeof(UniformBufferObject)}; + vk::DescriptorImageInfo imageInfo{ + .sampler = textureSampler, + .imageView = textureImageView, + .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal}; + std::array descriptorWrites{ + vk::WriteDescriptorSet{ + .dstSet = descriptorSets[i], + .dstBinding = 0, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eUniformBuffer, + .pBufferInfo = &bufferInfo}, + vk::WriteDescriptorSet{ + .dstSet = descriptorSets[i], + .dstBinding = 1, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .pImageInfo = &imageInfo}}; + device.updateDescriptorSets(descriptorWrites, {}); + } + } + + void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer &buffer, vk::raii::DeviceMemory &bufferMemory) + { + vk::BufferCreateInfo bufferInfo{ + .size = size, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive}; + buffer = vk::raii::Buffer(device, bufferInfo); + vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{ + .allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + bufferMemory = vk::raii::DeviceMemory(device, allocInfo); + buffer.bindMemory(bufferMemory, 0); + } + + std::unique_ptr beginSingleTimeCommands() + { + vk::CommandBufferAllocateInfo allocInfo{ + .commandPool = commandPool, + .level = vk::CommandBufferLevel::ePrimary, + .commandBufferCount = 1}; + std::unique_ptr commandBuffer = std::make_unique(std::move(vk::raii::CommandBuffers(device, allocInfo).front())); + + vk::CommandBufferBeginInfo beginInfo{ + .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; + commandBuffer->begin(beginInfo); + + return commandBuffer; + } + + void endSingleTimeCommands(vk::raii::CommandBuffer &commandBuffer) + { + commandBuffer.end(); + + vk::SubmitInfo submitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandBuffer}; + queue.submit(submitInfo, nullptr); + queue.waitIdle(); + } + + void copyBuffer(vk::raii::Buffer &srcBuffer, vk::raii::Buffer &dstBuffer, vk::DeviceSize size) + { + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1}; + vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); + commandCopyBuffer.begin(vk::CommandBufferBeginInfo{.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); + commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy{.size = size}); + commandCopyBuffer.end(); + queue.submit(vk::SubmitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer}, nullptr); + queue.waitIdle(); + } + + uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) + { + vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) + { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) + { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + void createCommandBuffers() + { + commandBuffers.clear(); + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = MAX_FRAMES_IN_FLIGHT}; + commandBuffers = vk::raii::CommandBuffers(device, allocInfo); + } + + void recordCommandBuffer(uint32_t imageIndex) + { + commandBuffers[currentFrame].begin({}); + // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL + transition_image_layout( + imageIndex, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage + ); + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::RenderingAttachmentInfo attachmentInfo = { + .imageView = swapChainImageViews[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor}; + vk::RenderingInfo renderingInfo = { + .renderArea = {.offset = {0, 0}, .extent = swapChainExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &attachmentInfo}; + commandBuffers[currentFrame].beginRendering(renderingInfo); + commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint16); + commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); + commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); + commandBuffers[currentFrame].endRendering(); + // After rendering, transition the swapchain image to PRESENT_SRC + transition_image_layout( + imageIndex, + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage + ); + commandBuffers[currentFrame].end(); + } + + void transition_image_layout( + uint32_t imageIndex, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout, + vk::AccessFlags2 src_access_mask, + vk::AccessFlags2 dst_access_mask, + vk::PipelineStageFlags2 src_stage_mask, + vk::PipelineStageFlags2 dst_stage_mask) + { + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = src_stage_mask, + .srcAccessMask = src_access_mask, + .dstStageMask = dst_stage_mask, + .dstAccessMask = dst_access_mask, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo dependency_info = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + } + + void createSyncObjects() + { + presentCompleteSemaphore.clear(); + renderFinishedSemaphore.clear(); + inFlightFences.clear(); + + for (size_t i = 0; i < swapChainImages.size(); i++) + { + presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); + } + } + + void updateUniformBuffer(uint32_t currentImage) + { + static auto startTime = std::chrono::high_resolution_clock::now(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + UniformBufferObject ubo{}; + ubo.model = rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.view = lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); + ubo.proj[1][1] *= -1; + + memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); + } + + void drawFrame() + { + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + ; + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr); + + if (result == vk::Result::eErrorOutOfDateKHR) + { + recreateSwapChain(); + return; + } + if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) + { + throw std::runtime_error("failed to acquire swap chain image!"); + } + updateUniformBuffer(currentFrame); + + device.resetFences(*inFlightFences[currentFrame]); + commandBuffers[currentFrame].reset(); + recordCommandBuffer(imageIndex); + + vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[currentFrame]); + + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + result = queue.presentKHR(presentInfoKHR); + if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) + { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != vk::Result::eSuccess) + { + throw std::runtime_error("failed to present swap chain image!"); + } + semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size(), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static vk::Format chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + const auto formatIt = std::ranges::find_if(availableFormats, + [](const auto &format) { + return format.format == vk::Format::eB8G8R8A8Srgb && + format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; + }); + return formatIt != availableFormats.end() ? formatIt->format : availableFormats[0].format; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + 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)}; + } + + std::vector getRequiredExtensions() + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + 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: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::Format swapChainImageFormat = vk::Format::eUndefined; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - - vk::raii::Image textureImage = nullptr; - vk::raii::DeviceMemory textureImageMemory = nullptr; - vk::raii::ImageView textureImageView = nullptr; - vk::raii::Sampler textureSampler = nullptr; - - vk::raii::Buffer vertexBuffer = nullptr; - vk::raii::DeviceMemory vertexBufferMemory = nullptr; - vk::raii::Buffer indexBuffer = nullptr; - vk::raii::DeviceMemory indexBufferMemory = nullptr; - - std::vector uniformBuffers; - std::vector uniformBuffersMemory; - std::vector uniformBuffersMapped; - - vk::raii::DescriptorPool descriptorPool = nullptr; - std::vector descriptorSets; - - vk::raii::CommandPool commandPool = nullptr; - std::vector commandBuffers; - - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; - std::vector inFlightFences; - uint32_t semaphoreIndex = 0; - uint32_t currentFrame = 0; - - bool framebufferResized = false; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); - } - - static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { - auto app = static_cast(glfwGetWindowUserPointer(window)); - app->framebufferResized = true; - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - createSurface(); - pickPhysicalDevice(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createDescriptorSetLayout(); - createGraphicsPipeline(); - createCommandPool(); - createTextureImage(); - createTextureImageView(); - createTextureSampler(); - createVertexBuffer(); - createIndexBuffer(); - createUniformBuffers(); - createDescriptorPool(); - createDescriptorSets(); - createCommandBuffers(); - createSyncObjects(); - } - - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - drawFrame(); - } - - device.waitIdle(); - } - - void cleanupSwapChain() { - swapChainImageViews.clear(); - swapChain = nullptr; - } - - void cleanup() { - glfwDestroyWindow(window); - - glfwTerminate(); - } - - void recreateSwapChain() { - int width = 0, height = 0; - glfwGetFramebufferSize(window, &width, &height); - while (width == 0 || height == 0) { - glfwGetFramebufferSize(window, &width, &height); - glfwWaitEvents(); - } - - device.waitIdle(); - - cleanupSwapChain(); - createSwapChain(); - createImageViews(); - } - - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().features.samplerAnisotropy && - features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - } ); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error( "failed to find a suitable GPU!" ); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain featureChain = { - {.features = {.samplerAnisotropy = true } }, // vk::PhysicalDeviceFeatures2 - {.synchronization2 = true, .dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( surface ); - swapChainImageFormat = 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{ - .surface = surface, .minImageCount = minImageCount, - .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, - .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 }; - - swapChain = vk::raii::SwapchainKHR( device, swapChainCreateInfo ); - swapChainImages = swapChain.getImages(); - } - - void createImageViews() { - 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( device, imageViewCreateInfo ); - } - } - - void createDescriptorSetLayout() { - std::array bindings = { - vk::DescriptorSetLayoutBinding( 0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr), - vk::DescriptorSetLayoutBinding( 1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment, nullptr) - }; - - vk::DescriptorSetLayoutCreateInfo layoutInfo{ .bindingCount = static_cast(bindings.size()), .pBindings = bindings.data() }; - descriptorSetLayout = vk::raii::DescriptorSetLayout( device, layoutInfo ); - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; - - auto bindingDescription = Vertex::getBindingDescription(); - auto attributeDescriptions = Vertex::getAttributeDescriptions(); - vk::PipelineVertexInputStateCreateInfo vertexInputInfo { .vertexBindingDescriptionCount =1, .pVertexBindingDescriptions = &bindingDescription, - .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), .pVertexAttributeDescriptions = attributeDescriptions.data() }; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ .topology = vk::PrimitiveTopology::eTriangleList }; - vk::PipelineViewportStateCreateInfo viewportState{ .viewportCount = 1, .scissorCount = 1 }; - - vk::PipelineRasterizationStateCreateInfo rasterizer{ .depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eCounterClockwise, .depthBiasEnable = vk::False, - .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f }; - - vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; - - vk::PipelineColorBlendAttachmentState colorBlendAttachment{ .blendEnable = vk::False, - .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA - }; - - vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0 }; - - pipelineLayout = vk::raii::PipelineLayout( device, pipelineLayoutInfo ); - - vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ .colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainImageFormat }; - vk::GraphicsPipelineCreateInfo pipelineInfo{ .pNext = &pipelineRenderingCreateInfo, - .stageCount = 2, .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, .layout = pipelineLayout, .renderPass = nullptr }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{ .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex }; - commandPool = vk::raii::CommandPool(device, poolInfo); - } - - void createTextureImage() { - int texWidth, texHeight, texChannels; - stbi_uc* pixels = stbi_load("textures/texture.jpg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); - vk::DeviceSize imageSize = texWidth * texHeight * 4; - - if (!pixels) { - throw std::runtime_error("failed to load texture image!"); - } - - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, imageSize); - memcpy(data, pixels, imageSize); - stagingBufferMemory.unmapMemory(); - - stbi_image_free(pixels); - - createImage(texWidth, texHeight, vk::Format::eR8G8B8A8Srgb, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory); - - transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal); - copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); - transitionImageLayout(textureImage, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal); - } - - void createTextureImageView() { - textureImageView = createImageView(textureImage, vk::Format::eR8G8B8A8Srgb, vk::ImageAspectFlagBits::eColor); - } - - void createTextureSampler() { - vk::PhysicalDeviceProperties properties = physicalDevice.getProperties(); - vk::SamplerCreateInfo samplerInfo{ - .magFilter = vk::Filter::eLinear, - .minFilter = vk::Filter::eLinear, - .mipmapMode = vk::SamplerMipmapMode::eLinear, - .addressModeU = vk::SamplerAddressMode::eRepeat, - .addressModeV = vk::SamplerAddressMode::eRepeat, - .addressModeW = vk::SamplerAddressMode::eRepeat, - .mipLodBias = 0.0f, - .anisotropyEnable = vk::True, - .maxAnisotropy = properties.limits.maxSamplerAnisotropy, - .compareEnable = vk::False, - .compareOp = vk::CompareOp::eAlways - }; - textureSampler = vk::raii::Sampler(device, samplerInfo); - } - - vk::raii::ImageView createImageView(vk::raii::Image& image, vk::Format format, vk::ImageAspectFlags aspectFlags) { - vk::ImageViewCreateInfo viewInfo{ - .image = image, - .viewType = vk::ImageViewType::e2D, - .format = format, - .subresourceRange = { aspectFlags, 0, 1, 0, 1 } - }; - return vk::raii::ImageView(device, viewInfo); - } - - void createImage(uint32_t width, uint32_t height, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image& image, vk::raii::DeviceMemory& imageMemory) { - vk::ImageCreateInfo imageInfo{ - .imageType = vk::ImageType::e2D, - .format = format, - .extent = {width, height, 1}, - .mipLevels = 1, - .arrayLayers = 1, - .samples = vk::SampleCountFlagBits::e1, - .tiling = tiling, - .usage = usage, - .sharingMode = vk::SharingMode::eExclusive - }; - - image = vk::raii::Image(device, imageInfo); - - vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ - .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) - }; - imageMemory = vk::raii::DeviceMemory(device, allocInfo); - image.bindMemory(imageMemory, 0); - } - - void transitionImageLayout(const vk::raii::Image& image, vk::ImageLayout oldLayout, vk::ImageLayout newLayout) { - auto commandBuffer = beginSingleTimeCommands(); - - vk::ImageMemoryBarrier barrier{ - .oldLayout = oldLayout, - .newLayout = newLayout, - .image = image, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } - }; - - vk::PipelineStageFlags sourceStage; - vk::PipelineStageFlags destinationStage; - - if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) { - barrier.srcAccessMask = {}; - barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; - - sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; - destinationStage = vk::PipelineStageFlagBits::eTransfer; - } else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) { - barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; - barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - - sourceStage = vk::PipelineStageFlagBits::eTransfer; - destinationStage = vk::PipelineStageFlagBits::eFragmentShader; - } else { - throw std::invalid_argument("unsupported layout transition!"); - } - commandBuffer->pipelineBarrier(sourceStage, destinationStage, {}, {}, nullptr, barrier); - endSingleTimeCommands(*commandBuffer); - } - - void copyBufferToImage(const vk::raii::Buffer& buffer, vk::raii::Image& image, uint32_t width, uint32_t height) { - std::unique_ptr commandBuffer = beginSingleTimeCommands(); - vk::BufferImageCopy region{ - .bufferOffset = 0, - .bufferRowLength = 0, - .bufferImageHeight = 0, - .imageSubresource = { vk::ImageAspectFlagBits::eColor, 0, 0, 1 }, - .imageOffset = {0, 0, 0}, - .imageExtent = {width, height, 1} - }; - commandBuffer->copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, {region}); - endSingleTimeCommands(*commandBuffer); - } - - void createVertexBuffer() { - vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(dataStaging, vertices.data(), bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); - - copyBuffer(stagingBuffer, vertexBuffer, bufferSize); - } - - void createIndexBuffer() { - vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); - - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(data, indices.data(), bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); - - copyBuffer(stagingBuffer, indexBuffer, bufferSize); - } - - void createUniformBuffers() { - uniformBuffers.clear(); - uniformBuffersMemory.clear(); - uniformBuffersMapped.clear(); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DeviceSize bufferSize = sizeof(UniformBufferObject); - vk::raii::Buffer buffer({}); - vk::raii::DeviceMemory bufferMem({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); - uniformBuffers.emplace_back(std::move(buffer)); - uniformBuffersMemory.emplace_back(std::move(bufferMem)); - uniformBuffersMapped.emplace_back( uniformBuffersMemory[i].mapMemory(0, bufferSize)); - } - } - - void createDescriptorPool() { - std::array poolSize { - vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT), - vk::DescriptorPoolSize(vk::DescriptorType::eCombinedImageSampler, MAX_FRAMES_IN_FLIGHT) - }; - vk::DescriptorPoolCreateInfo poolInfo{ - .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, - .maxSets = MAX_FRAMES_IN_FLIGHT, - .poolSizeCount = static_cast(poolSize.size()), - .pPoolSizes = poolSize.data() - }; - descriptorPool = vk::raii::DescriptorPool(device, poolInfo); - } - - void createDescriptorSets() { - std::vector layouts(MAX_FRAMES_IN_FLIGHT, descriptorSetLayout); - vk::DescriptorSetAllocateInfo allocInfo{ - .descriptorPool = descriptorPool, - .descriptorSetCount = static_cast(layouts.size()), - .pSetLayouts = layouts.data() - }; - - descriptorSets.clear(); - descriptorSets = device.allocateDescriptorSets(allocInfo); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DescriptorBufferInfo bufferInfo{ - .buffer = uniformBuffers[i], - .offset = 0, - .range = sizeof(UniformBufferObject) - }; - vk::DescriptorImageInfo imageInfo{ - .sampler = textureSampler, - .imageView = textureImageView, - .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal - }; - std::array descriptorWrites{ - vk::WriteDescriptorSet{ - .dstSet = descriptorSets[i], - .dstBinding = 0, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eUniformBuffer, - .pBufferInfo = &bufferInfo - }, - vk::WriteDescriptorSet{ - .dstSet = descriptorSets[i], - .dstBinding = 1, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eCombinedImageSampler, - .pImageInfo = &imageInfo - } - }; - device.updateDescriptorSets(descriptorWrites, {}); - } - } - - void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer& buffer, vk::raii::DeviceMemory& bufferMemory) { - vk::BufferCreateInfo bufferInfo{ - .size = size, - .usage = usage, - .sharingMode = vk::SharingMode::eExclusive - }; - buffer = vk::raii::Buffer(device, bufferInfo); - vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ - .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) - }; - bufferMemory = vk::raii::DeviceMemory(device, allocInfo); - buffer.bindMemory(bufferMemory, 0); - } - - std::unique_ptr beginSingleTimeCommands() { - vk::CommandBufferAllocateInfo allocInfo{ - .commandPool = commandPool, - .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = 1 - }; - std::unique_ptr commandBuffer = std::make_unique(std::move(vk::raii::CommandBuffers(device, allocInfo).front())); - - vk::CommandBufferBeginInfo beginInfo{ - .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit - }; - commandBuffer->begin(beginInfo); - - return commandBuffer; - } - - void endSingleTimeCommands(vk::raii::CommandBuffer& commandBuffer) { - commandBuffer.end(); - - vk::SubmitInfo submitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandBuffer }; - queue.submit(submitInfo, nullptr); - queue.waitIdle(); - } - - void copyBuffer(vk::raii::Buffer & srcBuffer, vk::raii::Buffer & dstBuffer, vk::DeviceSize size) { - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1 }; - vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); - commandCopyBuffer.begin(vk::CommandBufferBeginInfo{ .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit }); - commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy{ .size = size }); - commandCopyBuffer.end(); - queue.submit(vk::SubmitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer }, nullptr); - queue.waitIdle(); - } - - uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) { - vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); - - for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { - if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { - return i; - } - } - - throw std::runtime_error("failed to find suitable memory type!"); - } - - void createCommandBuffers() { - commandBuffers.clear(); - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = MAX_FRAMES_IN_FLIGHT }; - commandBuffers = vk::raii::CommandBuffers(device, allocInfo); - } - - void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin({}); - // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL - transition_image_layout( - imageIndex, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage - ); - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::RenderingAttachmentInfo attachmentInfo = { - .imageView = swapChainImageViews[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor - }; - vk::RenderingInfo renderingInfo = { - .renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &attachmentInfo - }; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].bindIndexBuffer( *indexBuffer, 0, vk::IndexType::eUint16 ); - commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); - commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); - commandBuffers[currentFrame].endRendering(); - // After rendering, transition the swapchain image to PRESENT_SRC - transition_image_layout( - imageIndex, - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage - ); - commandBuffers[currentFrame].end(); - } - - void transition_image_layout( - uint32_t imageIndex, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::AccessFlags2 src_access_mask, - vk::AccessFlags2 dst_access_mask, - vk::PipelineStageFlags2 src_stage_mask, - vk::PipelineStageFlags2 dst_stage_mask - ) { - vk::ImageMemoryBarrier2 barrier = { - .srcStageMask = src_stage_mask, - .srcAccessMask = src_access_mask, - .dstStageMask = dst_stage_mask, - .dstAccessMask = dst_access_mask, - .oldLayout = old_layout, - .newLayout = new_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); - } - - void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); - - for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - } - - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - inFlightFences.emplace_back(device, vk::FenceCreateInfo{ .flags = vk::FenceCreateFlagBits::eSignaled }); - } - } - - void updateUniformBuffer(uint32_t currentImage) { - static auto startTime = std::chrono::high_resolution_clock::now(); - - auto currentTime = std::chrono::high_resolution_clock::now(); - float time = std::chrono::duration(currentTime - startTime).count(); - - UniformBufferObject ubo{}; - ubo.model = rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.view = lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); - ubo.proj[1][1] *= -1; - - memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); - } - - void drawFrame() { - while ( vk::Result::eTimeout == device.waitForFences( *inFlightFences[currentFrame], vk::True, UINT64_MAX ) ) - ; - auto [result, imageIndex] = swapChain.acquireNextImage( UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr ); - - if (result == vk::Result::eErrorOutOfDateKHR) { - recreateSwapChain(); - return; - } - if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) { - throw std::runtime_error("failed to acquire swap chain image!"); - } - updateUniformBuffer(currentFrame); - - device.resetFences( *inFlightFences[currentFrame] ); - commandBuffers[currentFrame].reset(); - recordCommandBuffer(imageIndex); - - vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); - const vk::SubmitInfo submitInfo{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], - .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], - .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex] }; - queue.submit(submitInfo, *inFlightFences[currentFrame]); - - - const vk::PresentInfoKHR presentInfoKHR{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], - .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex }; - result = queue.presentKHR(presentInfoKHR); - if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { - framebufferResized = false; - recreateSwapChain(); - } else if (result != vk::Result::eSuccess) { - throw std::runtime_error("failed to present swap chain image!"); - } - semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; - } - - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size(), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static vk::Format chooseSwapSurfaceFormat(const std::vector& availableFormats) { - const auto formatIt = std::ranges::find_if(availableFormats, - [](const auto& format) { - return format.format == vk::Format::eB8G8R8A8Srgb && - format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; - }); - return formatIt != availableFormats.end() ? formatIt->format : availableFormats[0].format; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : 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) - }; - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - 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; - } -}; - -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/27_depth_buffering.cpp b/attachments/27_depth_buffering.cpp index 97f2d987..ddae065f 100644 --- a/attachments/27_depth_buffering.cpp +++ b/attachments/27_depth_buffering.cpp @@ -1,24 +1,24 @@ -#include -#include -#include -#include -#include -#include -#include #include -#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include #define GLM_FORCE_RADIANS @@ -29,13 +29,12 @@ import vulkan_hpp; #define STB_IMAGE_IMPLEMENTATION #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -constexpr int MAX_FRAMES_IN_FLIGHT = 2; +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; +constexpr int MAX_FRAMES_IN_FLIGHT = 2; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -43,28 +42,31 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -struct Vertex { - glm::vec3 pos; - glm::vec3 color; - glm::vec2 texCoord; - - static vk::VertexInputBindingDescription getBindingDescription() { - return { 0, sizeof(Vertex), vk::VertexInputRate::eVertex }; - } - - static std::array getAttributeDescriptions() { - return { - vk::VertexInputAttributeDescription( 0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, pos) ), - vk::VertexInputAttributeDescription( 1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color) ), - vk::VertexInputAttributeDescription( 2, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, texCoord) ) - }; - } +struct Vertex +{ + glm::vec3 pos; + glm::vec3 color; + glm::vec2 texCoord; + + static vk::VertexInputBindingDescription getBindingDescription() + { + return {0, sizeof(Vertex), vk::VertexInputRate::eVertex}; + } + + static std::array getAttributeDescriptions() + { + return { + vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, pos)), + vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color)), + vk::VertexInputAttributeDescription(2, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, texCoord))}; + } }; -struct UniformBufferObject { - alignas(16) glm::mat4 model; - alignas(16) glm::mat4 view; - alignas(16) glm::mat4 proj; +struct UniformBufferObject +{ + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; }; const std::vector vertices = { @@ -76,1046 +78,1067 @@ const std::vector vertices = { {{-0.5f, -0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}, {1.0f, 0.0f}}, {{0.5f, -0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f}}, {{0.5f, 0.5f, -0.5f}, {0.0f, 0.0f, 1.0f}, {0.0f, 1.0f}}, - {{-0.5f, 0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}, {1.0f, 1.0f}} -}; + {{-0.5f, 0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}, {1.0f, 1.0f}}}; const std::vector indices = { 0, 1, 2, 2, 3, 0, - 4, 5, 6, 6, 7, 4 + 4, 5, 6, 6, 7, 4}; + +class HelloTriangleApplication +{ + public: + void run() + { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + + private: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::Format swapChainImageFormat = vk::Format::eUndefined; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + + vk::raii::Image depthImage = nullptr; + vk::raii::DeviceMemory depthImageMemory = nullptr; + vk::raii::ImageView depthImageView = nullptr; + + vk::raii::Image textureImage = nullptr; + vk::raii::DeviceMemory textureImageMemory = nullptr; + vk::raii::ImageView textureImageView = nullptr; + vk::raii::Sampler textureSampler = nullptr; + + vk::raii::Buffer vertexBuffer = nullptr; + vk::raii::DeviceMemory vertexBufferMemory = nullptr; + vk::raii::Buffer indexBuffer = nullptr; + vk::raii::DeviceMemory indexBufferMemory = nullptr; + + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + std::vector uniformBuffersMapped; + + vk::raii::DescriptorPool descriptorPool = nullptr; + std::vector descriptorSets; + + vk::raii::CommandPool commandPool = nullptr; + std::vector commandBuffers; + + std::vector presentCompleteSemaphore; + std::vector renderFinishedSemaphore; + std::vector inFlightFences; + uint32_t semaphoreIndex = 0; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow *window, int width, int height) + { + auto app = static_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createDescriptorSetLayout(); + createGraphicsPipeline(); + createCommandPool(); + createDepthResources(); + createTextureImage(); + createTextureImageView(); + createTextureSampler(); + createVertexBuffer(); + createIndexBuffer(); + createUniformBuffers(); + createDescriptorPool(); + createDescriptorSets(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() + { + while (!glfwWindowShouldClose(window)) + { + glfwPollEvents(); + drawFrame(); + } + + device.waitIdle(); + } + + void cleanupSwapChain() + { + swapChainImageViews.clear(); + swapChain = nullptr; + } + + void cleanup() const + { + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() + { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) + { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + device.waitIdle(); + + cleanupSwapChain(); + createSwapChain(); + createImageViews(); + createDepthResources(); + } + + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().features.samplerAnisotropy && + features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain featureChain = { + {.features = {.samplerAnisotropy = true}}, // vk::PhysicalDeviceFeatures2 + {.synchronization2 = true, .dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface); + swapChainImageFormat = 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{ + .surface = surface, .minImageCount = minImageCount, .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + void createImageViews() + { + 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(device, imageViewCreateInfo); + } + } + + void createDescriptorSetLayout() + { + std::array bindings = { + vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr), + vk::DescriptorSetLayoutBinding(1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment, nullptr)}; + + vk::DescriptorSetLayoutCreateInfo layoutInfo{.bindingCount = static_cast(bindings.size()), .pBindings = bindings.data()}; + descriptorSetLayout = vk::raii::DescriptorSetLayout(device, layoutInfo); + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; + + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{ + .vertexBindingDescriptionCount = 1, + .pVertexBindingDescriptions = &bindingDescription, + .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), + .pVertexAttributeDescriptions = attributeDescriptions.data()}; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ + .topology = vk::PrimitiveTopology::eTriangleList, + .primitiveRestartEnable = vk::False}; + vk::PipelineViewportStateCreateInfo viewportState{ + .viewportCount = 1, + .scissorCount = 1}; + vk::PipelineRasterizationStateCreateInfo rasterizer{ + .depthClampEnable = vk::False, + .rasterizerDiscardEnable = vk::False, + .polygonMode = vk::PolygonMode::eFill, + .cullMode = vk::CullModeFlagBits::eBack, + .frontFace = vk::FrontFace::eCounterClockwise, + .depthBiasEnable = vk::False}; + rasterizer.lineWidth = 1.0f; + vk::PipelineMultisampleStateCreateInfo multisampling{ + .rasterizationSamples = vk::SampleCountFlagBits::e1, + .sampleShadingEnable = vk::False}; + vk::PipelineDepthStencilStateCreateInfo depthStencil{ + .depthTestEnable = vk::True, + .depthWriteEnable = vk::True, + .depthCompareOp = vk::CompareOp::eLess, + .depthBoundsTestEnable = vk::False, + .stencilTestEnable = vk::False}; + vk::PipelineColorBlendAttachmentState colorBlendAttachment; + colorBlendAttachment.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA; + colorBlendAttachment.blendEnable = vk::False; + + vk::PipelineColorBlendStateCreateInfo colorBlending{ + .logicOpEnable = vk::False, + .logicOp = vk::LogicOp::eCopy, + .attachmentCount = 1, + .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0}; + + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::Format depthFormat = findDepthFormat(); + vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ + .colorAttachmentCount = 1, + .pColorAttachmentFormats = &swapChainImageFormat, + .depthAttachmentFormat = depthFormat}; + vk::GraphicsPipelineCreateInfo pipelineInfo{.pNext = &pipelineRenderingCreateInfo, + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pDepthStencilState = &depthStencil, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{ + .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex}; + commandPool = vk::raii::CommandPool(device, poolInfo); + } + + void createDepthResources() + { + vk::Format depthFormat = findDepthFormat(); + + createImage(swapChainExtent.width, swapChainExtent.height, depthFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eDepthStencilAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, depthImage, depthImageMemory); + depthImageView = createImageView(depthImage, depthFormat, vk::ImageAspectFlagBits::eDepth); + } + + vk::Format findSupportedFormat(const std::vector &candidates, vk::ImageTiling tiling, vk::FormatFeatureFlags features) + { + auto formatIt = std::ranges::find_if(candidates, [&](auto const format) { + vk::FormatProperties props = physicalDevice.getFormatProperties(format); + return (((tiling == vk::ImageTiling::eLinear) && ((props.linearTilingFeatures & features) == features)) || + ((tiling == vk::ImageTiling::eOptimal) && ((props.optimalTilingFeatures & features) == features))); + }); + if (formatIt == candidates.end()) + { + throw std::runtime_error("failed to find supported format!"); + } + return *formatIt; + } + + vk::Format findDepthFormat() + { + return findSupportedFormat( + {vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, vk::Format::eD24UnormS8Uint}, + vk::ImageTiling::eOptimal, + vk::FormatFeatureFlagBits::eDepthStencilAttachment); + } + + bool hasStencilComponent(vk::Format format) + { + return format == vk::Format::eD32SfloatS8Uint || format == vk::Format::eD24UnormS8Uint; + } + + void createTextureImage() + { + int texWidth, texHeight, texChannels; + stbi_uc *pixels = stbi_load("textures/texture.jpg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + vk::DeviceSize imageSize = texWidth * texHeight * 4; + + if (!pixels) + { + throw std::runtime_error("failed to load texture image!"); + } + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, imageSize); + memcpy(data, pixels, imageSize); + stagingBufferMemory.unmapMemory(); + + stbi_image_free(pixels); + + createImage(texWidth, texHeight, vk::Format::eR8G8B8A8Srgb, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory); + + transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); + transitionImageLayout(textureImage, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal); + } + + void createTextureImageView() + { + textureImageView = createImageView(textureImage, vk::Format::eR8G8B8A8Srgb, vk::ImageAspectFlagBits::eColor); + } + + void createTextureSampler() + { + vk::PhysicalDeviceProperties properties = physicalDevice.getProperties(); + vk::SamplerCreateInfo samplerInfo{ + .magFilter = vk::Filter::eLinear, + .minFilter = vk::Filter::eLinear, + .mipmapMode = vk::SamplerMipmapMode::eLinear, + .addressModeU = vk::SamplerAddressMode::eRepeat, + .addressModeV = vk::SamplerAddressMode::eRepeat, + .addressModeW = vk::SamplerAddressMode::eRepeat, + .mipLodBias = 0.0f, + .anisotropyEnable = vk::True, + .maxAnisotropy = properties.limits.maxSamplerAnisotropy, + .compareEnable = vk::False, + .compareOp = vk::CompareOp::eAlways}; + textureSampler = vk::raii::Sampler(device, samplerInfo); + } + + vk::raii::ImageView createImageView(vk::raii::Image &image, vk::Format format, vk::ImageAspectFlags aspectFlags) + { + vk::ImageViewCreateInfo viewInfo{ + .image = image, + .viewType = vk::ImageViewType::e2D, + .format = format, + .subresourceRange = {aspectFlags, 0, 1, 0, 1}}; + return vk::raii::ImageView(device, viewInfo); + } + + void createImage(uint32_t width, uint32_t height, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image &image, vk::raii::DeviceMemory &imageMemory) + { + vk::ImageCreateInfo imageInfo{ + .imageType = vk::ImageType::e2D, + .format = format, + .extent = {width, height, 1}, + .mipLevels = 1, + .arrayLayers = 1, + .samples = vk::SampleCountFlagBits::e1, + .tiling = tiling, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive, + .initialLayout = vk::ImageLayout::eUndefined}; + image = vk::raii::Image(device, imageInfo); + + vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{ + .allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + imageMemory = vk::raii::DeviceMemory(device, allocInfo); + image.bindMemory(imageMemory, 0); + } + + void transitionImageLayout(const vk::raii::Image &image, vk::ImageLayout oldLayout, vk::ImageLayout newLayout) + { + auto commandBuffer = beginSingleTimeCommands(); + + vk::ImageMemoryBarrier barrier{ + .oldLayout = oldLayout, + .newLayout = newLayout, + .image = image, + .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + + vk::PipelineStageFlags sourceStage; + vk::PipelineStageFlags destinationStage; + + if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) + { + barrier.srcAccessMask = {}; + barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; + + sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; + destinationStage = vk::PipelineStageFlagBits::eTransfer; + } + else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) + { + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + + sourceStage = vk::PipelineStageFlagBits::eTransfer; + destinationStage = vk::PipelineStageFlagBits::eFragmentShader; + } + else + { + throw std::invalid_argument("unsupported layout transition!"); + } + commandBuffer->pipelineBarrier(sourceStage, destinationStage, {}, {}, nullptr, barrier); + endSingleTimeCommands(*commandBuffer); + } + + void copyBufferToImage(const vk::raii::Buffer &buffer, vk::raii::Image &image, uint32_t width, uint32_t height) + { + std::unique_ptr commandBuffer = beginSingleTimeCommands(); + vk::BufferImageCopy region{ + .bufferOffset = 0, + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource = {vk::ImageAspectFlagBits::eColor, 0, 0, 1}, + .imageOffset = {0, 0, 0}, + .imageExtent = {width, height, 1}}; + commandBuffer->copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, {region}); + endSingleTimeCommands(*commandBuffer); + } + + void createVertexBuffer() + { + vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(dataStaging, vertices.data(), bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + } + + void createIndexBuffer() + { + vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(data, indices.data(), bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); + + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + } + + void createUniformBuffers() + { + uniformBuffers.clear(); + uniformBuffersMemory.clear(); + uniformBuffersMapped.clear(); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DeviceSize bufferSize = sizeof(UniformBufferObject); + vk::raii::Buffer buffer({}); + vk::raii::DeviceMemory bufferMem({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); + uniformBuffers.emplace_back(std::move(buffer)); + uniformBuffersMemory.emplace_back(std::move(bufferMem)); + uniformBuffersMapped.emplace_back(uniformBuffersMemory[i].mapMemory(0, bufferSize)); + } + } + + void createDescriptorPool() + { + std::array poolSize{ + vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT), + vk::DescriptorPoolSize(vk::DescriptorType::eCombinedImageSampler, MAX_FRAMES_IN_FLIGHT)}; + vk::DescriptorPoolCreateInfo poolInfo{ + .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, + .maxSets = MAX_FRAMES_IN_FLIGHT, + .poolSizeCount = static_cast(poolSize.size()), + .pPoolSizes = poolSize.data()}; + descriptorPool = vk::raii::DescriptorPool(device, poolInfo); + } + + void createDescriptorSets() + { + std::vector layouts(MAX_FRAMES_IN_FLIGHT, descriptorSetLayout); + vk::DescriptorSetAllocateInfo allocInfo{ + .descriptorPool = descriptorPool, + .descriptorSetCount = static_cast(layouts.size()), + .pSetLayouts = layouts.data()}; + + descriptorSets.clear(); + descriptorSets = device.allocateDescriptorSets(allocInfo); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DescriptorBufferInfo bufferInfo{ + .buffer = uniformBuffers[i], + .offset = 0, + .range = sizeof(UniformBufferObject)}; + vk::DescriptorImageInfo imageInfo{ + .sampler = textureSampler, + .imageView = textureImageView, + .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal}; + std::array descriptorWrites{ + vk::WriteDescriptorSet{ + .dstSet = descriptorSets[i], + .dstBinding = 0, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eUniformBuffer, + .pBufferInfo = &bufferInfo}, + vk::WriteDescriptorSet{ + .dstSet = descriptorSets[i], + .dstBinding = 1, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .pImageInfo = &imageInfo}}; + device.updateDescriptorSets(descriptorWrites, {}); + } + } + + void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer &buffer, vk::raii::DeviceMemory &bufferMemory) + { + vk::BufferCreateInfo bufferInfo{ + .size = size, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive}; + buffer = vk::raii::Buffer(device, bufferInfo); + vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{ + .allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + bufferMemory = vk::raii::DeviceMemory(device, allocInfo); + buffer.bindMemory(bufferMemory, 0); + } + + std::unique_ptr beginSingleTimeCommands() + { + vk::CommandBufferAllocateInfo allocInfo{ + .commandPool = commandPool, + .level = vk::CommandBufferLevel::ePrimary, + .commandBufferCount = 1}; + std::unique_ptr commandBuffer = std::make_unique(std::move(vk::raii::CommandBuffers(device, allocInfo).front())); + + vk::CommandBufferBeginInfo beginInfo{ + .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; + commandBuffer->begin(beginInfo); + + return commandBuffer; + } + + void endSingleTimeCommands(vk::raii::CommandBuffer &commandBuffer) + { + commandBuffer.end(); + + vk::SubmitInfo submitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandBuffer}; + queue.submit(submitInfo, nullptr); + queue.waitIdle(); + } + + void copyBuffer(vk::raii::Buffer &srcBuffer, vk::raii::Buffer &dstBuffer, vk::DeviceSize size) + { + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1}; + vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); + commandCopyBuffer.begin(vk::CommandBufferBeginInfo{.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); + commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy{.size = size}); + commandCopyBuffer.end(); + queue.submit(vk::SubmitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer}, nullptr); + queue.waitIdle(); + } + + uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) + { + vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) + { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) + { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + void createCommandBuffers() + { + commandBuffers.clear(); + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = MAX_FRAMES_IN_FLIGHT}; + commandBuffers = vk::raii::CommandBuffers(device, allocInfo); + } + + void recordCommandBuffer(uint32_t imageIndex) + { + commandBuffers[currentFrame].begin({}); + // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL + transition_image_layout( + imageIndex, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage + ); + // Transition depth image to depth attachment optimal layout + vk::ImageMemoryBarrier2 depthBarrier = { + .srcStageMask = vk::PipelineStageFlagBits2::eTopOfPipe, + .srcAccessMask = {}, + .dstStageMask = vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, + .dstAccessMask = vk::AccessFlagBits2::eDepthStencilAttachmentRead | vk::AccessFlagBits2::eDepthStencilAttachmentWrite, + .oldLayout = vk::ImageLayout::eUndefined, + .newLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = depthImage, + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eDepth, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo depthDependencyInfo = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &depthBarrier}; + commandBuffers[currentFrame].pipelineBarrier2(depthDependencyInfo); + + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::ClearValue clearDepth = vk::ClearDepthStencilValue(1.0f, 0); + + vk::RenderingAttachmentInfo colorAttachmentInfo = { + .imageView = swapChainImageViews[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor}; + + vk::RenderingAttachmentInfo depthAttachmentInfo = { + .imageView = depthImageView, + .imageLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eDontCare, + .clearValue = clearDepth}; + + vk::RenderingInfo renderingInfo = { + .renderArea = {.offset = {0, 0}, .extent = swapChainExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &colorAttachmentInfo, + .pDepthAttachment = &depthAttachmentInfo}; + commandBuffers[currentFrame].beginRendering(renderingInfo); + commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint16); + commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); + commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); + commandBuffers[currentFrame].endRendering(); + // After rendering, transition the swapchain image to PRESENT_SRC + transition_image_layout( + imageIndex, + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage + ); + commandBuffers[currentFrame].end(); + } + + void transition_image_layout( + uint32_t imageIndex, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout, + vk::AccessFlags2 src_access_mask, + vk::AccessFlags2 dst_access_mask, + vk::PipelineStageFlags2 src_stage_mask, + vk::PipelineStageFlags2 dst_stage_mask) + { + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = src_stage_mask, + .srcAccessMask = src_access_mask, + .dstStageMask = dst_stage_mask, + .dstAccessMask = dst_access_mask, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo dependency_info = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + } + + void createSyncObjects() + { + presentCompleteSemaphore.clear(); + renderFinishedSemaphore.clear(); + inFlightFences.clear(); + + for (size_t i = 0; i < swapChainImages.size(); i++) + { + presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); + } + } + + void updateUniformBuffer(uint32_t currentImage) + { + static auto startTime = std::chrono::high_resolution_clock::now(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + UniformBufferObject ubo{}; + ubo.model = rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.view = lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); + ubo.proj[1][1] *= -1; + + memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); + } + + void drawFrame() + { + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + ; + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr); + + if (result == vk::Result::eErrorOutOfDateKHR) + { + recreateSwapChain(); + return; + } + if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) + { + throw std::runtime_error("failed to acquire swap chain image!"); + } + updateUniformBuffer(currentFrame); + + device.resetFences(*inFlightFences[currentFrame]); + commandBuffers[currentFrame].reset(); + recordCommandBuffer(imageIndex); + + vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[currentFrame]); + + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + result = queue.presentKHR(presentInfoKHR); + if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) + { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != vk::Result::eSuccess) + { + throw std::runtime_error("failed to present swap chain image!"); + } + semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size(), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static vk::Format chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + const auto formatIt = std::ranges::find_if(availableFormats, + [](const auto &format) { + return format.format == vk::Format::eB8G8R8A8Srgb && + format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; + }); + return formatIt != availableFormats.end() ? formatIt->format : availableFormats[0].format; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + 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)}; + } + + std::vector getRequiredExtensions() + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + 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: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::Format swapChainImageFormat = vk::Format::eUndefined; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - - vk::raii::Image depthImage = nullptr; - vk::raii::DeviceMemory depthImageMemory = nullptr; - vk::raii::ImageView depthImageView = nullptr; - - vk::raii::Image textureImage = nullptr; - vk::raii::DeviceMemory textureImageMemory = nullptr; - vk::raii::ImageView textureImageView = nullptr; - vk::raii::Sampler textureSampler = nullptr; - - vk::raii::Buffer vertexBuffer = nullptr; - vk::raii::DeviceMemory vertexBufferMemory = nullptr; - vk::raii::Buffer indexBuffer = nullptr; - vk::raii::DeviceMemory indexBufferMemory = nullptr; - - std::vector uniformBuffers; - std::vector uniformBuffersMemory; - std::vector uniformBuffersMapped; - - vk::raii::DescriptorPool descriptorPool = nullptr; - std::vector descriptorSets; - - vk::raii::CommandPool commandPool = nullptr; - std::vector commandBuffers; - - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; - std::vector inFlightFences; - uint32_t semaphoreIndex = 0; - uint32_t currentFrame = 0; - - bool framebufferResized = false; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); - } - - static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { - auto app = static_cast(glfwGetWindowUserPointer(window)); - app->framebufferResized = true; - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - createSurface(); - pickPhysicalDevice(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createDescriptorSetLayout(); - createGraphicsPipeline(); - createCommandPool(); - createDepthResources(); - createTextureImage(); - createTextureImageView(); - createTextureSampler(); - createVertexBuffer(); - createIndexBuffer(); - createUniformBuffers(); - createDescriptorPool(); - createDescriptorSets(); - createCommandBuffers(); - createSyncObjects(); - } - - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - drawFrame(); - } - - device.waitIdle(); - } - - void cleanupSwapChain() { - swapChainImageViews.clear(); - swapChain = nullptr; - } - - void cleanup() const { - glfwDestroyWindow(window); - - glfwTerminate(); - } - - void recreateSwapChain() { - int width = 0, height = 0; - glfwGetFramebufferSize(window, &width, &height); - while (width == 0 || height == 0) { - glfwGetFramebufferSize(window, &width, &height); - glfwWaitEvents(); - } - - device.waitIdle(); - - cleanupSwapChain(); - createSwapChain(); - createImageViews(); - createDepthResources(); - } - - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().features.samplerAnisotropy && - features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - } ); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error( "failed to find a suitable GPU!" ); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain featureChain = { - {.features = {.samplerAnisotropy = true } }, // vk::PhysicalDeviceFeatures2 - {.synchronization2 = true, .dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( surface ); - swapChainImageFormat = 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{ - .surface = surface, .minImageCount = minImageCount, - .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, - .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 }; - - swapChain = vk::raii::SwapchainKHR( device, swapChainCreateInfo ); - swapChainImages = swapChain.getImages(); - } - - void createImageViews() { - 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( device, imageViewCreateInfo ); - } - } - - void createDescriptorSetLayout() { - std::array bindings = { - vk::DescriptorSetLayoutBinding( 0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr), - vk::DescriptorSetLayoutBinding( 1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment, nullptr) - }; - - vk::DescriptorSetLayoutCreateInfo layoutInfo{ .bindingCount = static_cast(bindings.size()), .pBindings = bindings.data() }; - descriptorSetLayout = vk::raii::DescriptorSetLayout( device, layoutInfo ); - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; - - auto bindingDescription = Vertex::getBindingDescription(); - auto attributeDescriptions = Vertex::getAttributeDescriptions(); - vk::PipelineVertexInputStateCreateInfo vertexInputInfo{ - .vertexBindingDescriptionCount = 1, - .pVertexBindingDescriptions = &bindingDescription, - .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), - .pVertexAttributeDescriptions = attributeDescriptions.data() - }; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ - .topology = vk::PrimitiveTopology::eTriangleList, - .primitiveRestartEnable = vk::False - }; - vk::PipelineViewportStateCreateInfo viewportState{ - .viewportCount = 1, - .scissorCount = 1 - }; - vk::PipelineRasterizationStateCreateInfo rasterizer{ - .depthClampEnable = vk::False, - .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, - .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eCounterClockwise, - .depthBiasEnable = vk::False - }; - rasterizer.lineWidth = 1.0f; - vk::PipelineMultisampleStateCreateInfo multisampling{ - .rasterizationSamples = vk::SampleCountFlagBits::e1, - .sampleShadingEnable = vk::False - }; - vk::PipelineDepthStencilStateCreateInfo depthStencil{ - .depthTestEnable = vk::True, - .depthWriteEnable = vk::True, - .depthCompareOp = vk::CompareOp::eLess, - .depthBoundsTestEnable = vk::False, - .stencilTestEnable = vk::False - }; - vk::PipelineColorBlendAttachmentState colorBlendAttachment; - colorBlendAttachment.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA; - colorBlendAttachment.blendEnable = vk::False; - - vk::PipelineColorBlendStateCreateInfo colorBlending{ - .logicOpEnable = vk::False, - .logicOp = vk::LogicOp::eCopy, - .attachmentCount = 1, - .pAttachments = &colorBlendAttachment - }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0 }; - - pipelineLayout = vk::raii::PipelineLayout( device, pipelineLayoutInfo ); - - vk::Format depthFormat = findDepthFormat(); - vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ - .colorAttachmentCount = 1, - .pColorAttachmentFormats = &swapChainImageFormat, - .depthAttachmentFormat = depthFormat - }; - vk::GraphicsPipelineCreateInfo pipelineInfo{ .pNext = &pipelineRenderingCreateInfo, - .stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pDepthStencilState = &depthStencil, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = pipelineLayout, - .renderPass = nullptr - }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{ - .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex - }; - commandPool = vk::raii::CommandPool(device, poolInfo); - } - - void createDepthResources() { - vk::Format depthFormat = findDepthFormat(); - - createImage(swapChainExtent.width, swapChainExtent.height, depthFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eDepthStencilAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, depthImage, depthImageMemory); - depthImageView = createImageView(depthImage, depthFormat, vk::ImageAspectFlagBits::eDepth); - } - - vk::Format findSupportedFormat(const std::vector& candidates, vk::ImageTiling tiling, vk::FormatFeatureFlags features) { - auto formatIt = std::ranges::find_if(candidates, [&](auto const format){ - vk::FormatProperties props = physicalDevice.getFormatProperties(format); - return (((tiling == vk::ImageTiling::eLinear) && ((props.linearTilingFeatures & features) == features)) || - ((tiling == vk::ImageTiling::eOptimal) && ((props.optimalTilingFeatures & features) == features))); - }); - if ( formatIt == candidates.end()) - { - throw std::runtime_error("failed to find supported format!"); - } - return *formatIt; - } - - vk::Format findDepthFormat() { - return findSupportedFormat( - {vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, vk::Format::eD24UnormS8Uint}, - vk::ImageTiling::eOptimal, - vk::FormatFeatureFlagBits::eDepthStencilAttachment - ); - } - - bool hasStencilComponent(vk::Format format) { - return format == vk::Format::eD32SfloatS8Uint || format == vk::Format::eD24UnormS8Uint; - } - - void createTextureImage() { - int texWidth, texHeight, texChannels; - stbi_uc* pixels = stbi_load("textures/texture.jpg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); - vk::DeviceSize imageSize = texWidth * texHeight * 4; - - if (!pixels) { - throw std::runtime_error("failed to load texture image!"); - } - - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, imageSize); - memcpy(data, pixels, imageSize); - stagingBufferMemory.unmapMemory(); - - stbi_image_free(pixels); - - createImage(texWidth, texHeight, vk::Format::eR8G8B8A8Srgb, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory); - - transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal); - copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); - transitionImageLayout(textureImage, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal); - } - - void createTextureImageView() { - textureImageView = createImageView(textureImage, vk::Format::eR8G8B8A8Srgb, vk::ImageAspectFlagBits::eColor); - } - - void createTextureSampler() { - vk::PhysicalDeviceProperties properties = physicalDevice.getProperties(); - vk::SamplerCreateInfo samplerInfo{ - .magFilter = vk::Filter::eLinear, - .minFilter = vk::Filter::eLinear, - .mipmapMode = vk::SamplerMipmapMode::eLinear, - .addressModeU = vk::SamplerAddressMode::eRepeat, - .addressModeV = vk::SamplerAddressMode::eRepeat, - .addressModeW = vk::SamplerAddressMode::eRepeat, - .mipLodBias = 0.0f, - .anisotropyEnable = vk::True, - .maxAnisotropy = properties.limits.maxSamplerAnisotropy, - .compareEnable = vk::False, - .compareOp = vk::CompareOp::eAlways - }; - textureSampler = vk::raii::Sampler(device, samplerInfo); - } - - vk::raii::ImageView createImageView(vk::raii::Image& image, vk::Format format, vk::ImageAspectFlags aspectFlags) { - vk::ImageViewCreateInfo viewInfo{ - .image = image, - .viewType = vk::ImageViewType::e2D, - .format = format, - .subresourceRange = { aspectFlags, 0, 1, 0, 1 } - }; - return vk::raii::ImageView(device, viewInfo); - } - - void createImage(uint32_t width, uint32_t height, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image& image, vk::raii::DeviceMemory& imageMemory) { - vk::ImageCreateInfo imageInfo{ - .imageType = vk::ImageType::e2D, - .format = format, - .extent = {width, height, 1}, - .mipLevels = 1, - .arrayLayers = 1, - .samples = vk::SampleCountFlagBits::e1, - .tiling = tiling, - .usage = usage, - .sharingMode = vk::SharingMode::eExclusive, - .initialLayout = vk::ImageLayout::eUndefined - }; - image = vk::raii::Image(device, imageInfo); - - vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ - .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) - }; - imageMemory = vk::raii::DeviceMemory(device, allocInfo); - image.bindMemory(imageMemory, 0); - } - - void transitionImageLayout(const vk::raii::Image& image, vk::ImageLayout oldLayout, vk::ImageLayout newLayout) { - auto commandBuffer = beginSingleTimeCommands(); - - vk::ImageMemoryBarrier barrier{ - .oldLayout = oldLayout, - .newLayout = newLayout, - .image = image, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } - }; - - vk::PipelineStageFlags sourceStage; - vk::PipelineStageFlags destinationStage; - - if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) { - barrier.srcAccessMask = {}; - barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; - - sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; - destinationStage = vk::PipelineStageFlagBits::eTransfer; - } else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) { - barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; - barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - - sourceStage = vk::PipelineStageFlagBits::eTransfer; - destinationStage = vk::PipelineStageFlagBits::eFragmentShader; - } else { - throw std::invalid_argument("unsupported layout transition!"); - } - commandBuffer->pipelineBarrier( sourceStage, destinationStage, {}, {}, nullptr, barrier ); - endSingleTimeCommands(*commandBuffer); - } - - void copyBufferToImage(const vk::raii::Buffer& buffer, vk::raii::Image& image, uint32_t width, uint32_t height) { - std::unique_ptr commandBuffer = beginSingleTimeCommands(); - vk::BufferImageCopy region{ - .bufferOffset = 0, - .bufferRowLength = 0, - .bufferImageHeight = 0, - .imageSubresource = { vk::ImageAspectFlagBits::eColor, 0, 0, 1 }, - .imageOffset = {0, 0, 0}, - .imageExtent = {width, height, 1} - }; - commandBuffer->copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, {region}); - endSingleTimeCommands(*commandBuffer); - } - - void createVertexBuffer() { - vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(dataStaging, vertices.data(), bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); - - copyBuffer(stagingBuffer, vertexBuffer, bufferSize); - } - - void createIndexBuffer() { - vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); - - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(data, indices.data(), bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); - - copyBuffer(stagingBuffer, indexBuffer, bufferSize); - } - - void createUniformBuffers() { - uniformBuffers.clear(); - uniformBuffersMemory.clear(); - uniformBuffersMapped.clear(); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DeviceSize bufferSize = sizeof(UniformBufferObject); - vk::raii::Buffer buffer({}); - vk::raii::DeviceMemory bufferMem({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); - uniformBuffers.emplace_back(std::move(buffer)); - uniformBuffersMemory.emplace_back(std::move(bufferMem)); - uniformBuffersMapped.emplace_back( uniformBuffersMemory[i].mapMemory(0, bufferSize)); - } - } - - void createDescriptorPool() { - std::array poolSize { - vk::DescriptorPoolSize( vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT), - vk::DescriptorPoolSize( vk::DescriptorType::eCombinedImageSampler, MAX_FRAMES_IN_FLIGHT) - }; - vk::DescriptorPoolCreateInfo poolInfo{ - .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, - .maxSets = MAX_FRAMES_IN_FLIGHT, - .poolSizeCount = static_cast(poolSize.size()), - .pPoolSizes = poolSize.data() - }; - descriptorPool = vk::raii::DescriptorPool(device, poolInfo); - } - - void createDescriptorSets() { - std::vector layouts(MAX_FRAMES_IN_FLIGHT, descriptorSetLayout); - vk::DescriptorSetAllocateInfo allocInfo{ - .descriptorPool = descriptorPool, - .descriptorSetCount = static_cast(layouts.size()), - .pSetLayouts = layouts.data() - }; - - descriptorSets.clear(); - descriptorSets = device.allocateDescriptorSets(allocInfo); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DescriptorBufferInfo bufferInfo{ - .buffer = uniformBuffers[i], - .offset = 0, - .range = sizeof(UniformBufferObject) - }; - vk::DescriptorImageInfo imageInfo{ - .sampler = textureSampler, - .imageView = textureImageView, - .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal - }; - std::array descriptorWrites{ - vk::WriteDescriptorSet{ - .dstSet = descriptorSets[i], - .dstBinding = 0, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eUniformBuffer, - .pBufferInfo = &bufferInfo - }, - vk::WriteDescriptorSet{ - .dstSet = descriptorSets[i], - .dstBinding = 1, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eCombinedImageSampler, - .pImageInfo = &imageInfo - } - }; - device.updateDescriptorSets(descriptorWrites, {}); - } - } - - void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer& buffer, vk::raii::DeviceMemory& bufferMemory) { - vk::BufferCreateInfo bufferInfo{ - .size = size, - .usage = usage, - .sharingMode = vk::SharingMode::eExclusive - }; - buffer = vk::raii::Buffer(device, bufferInfo); - vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ - .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) - }; - bufferMemory = vk::raii::DeviceMemory(device, allocInfo); - buffer.bindMemory(bufferMemory, 0); - } - - std::unique_ptr beginSingleTimeCommands() { - vk::CommandBufferAllocateInfo allocInfo{ - .commandPool = commandPool, - .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = 1 - }; - std::unique_ptr commandBuffer = std::make_unique(std::move(vk::raii::CommandBuffers(device, allocInfo).front())); - - vk::CommandBufferBeginInfo beginInfo{ - .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit - }; - commandBuffer->begin(beginInfo); - - return commandBuffer; - } - - void endSingleTimeCommands(vk::raii::CommandBuffer& commandBuffer) { - commandBuffer.end(); - - vk::SubmitInfo submitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandBuffer }; - queue.submit(submitInfo, nullptr); - queue.waitIdle(); - } - - void copyBuffer(vk::raii::Buffer & srcBuffer, vk::raii::Buffer & dstBuffer, vk::DeviceSize size) { - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1 }; - vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); - commandCopyBuffer.begin(vk::CommandBufferBeginInfo{ .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit }); - commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy{ .size = size }); - commandCopyBuffer.end(); - queue.submit(vk::SubmitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer }, nullptr); - queue.waitIdle(); - } - - uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) { - vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); - - for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { - if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { - return i; - } - } - - throw std::runtime_error("failed to find suitable memory type!"); - } - - void createCommandBuffers() { - commandBuffers.clear(); - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = MAX_FRAMES_IN_FLIGHT }; - commandBuffers = vk::raii::CommandBuffers(device, allocInfo); - } - - void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin({}); - // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL - transition_image_layout( - imageIndex, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage - ); - // Transition depth image to depth attachment optimal layout - vk::ImageMemoryBarrier2 depthBarrier = { - .srcStageMask = vk::PipelineStageFlagBits2::eTopOfPipe, - .srcAccessMask = {}, - .dstStageMask = vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, - .dstAccessMask = vk::AccessFlagBits2::eDepthStencilAttachmentRead | vk::AccessFlagBits2::eDepthStencilAttachmentWrite, - .oldLayout = vk::ImageLayout::eUndefined, - .newLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = depthImage, - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eDepth, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo depthDependencyInfo = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &depthBarrier - }; - commandBuffers[currentFrame].pipelineBarrier2(depthDependencyInfo); - - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::ClearValue clearDepth = vk::ClearDepthStencilValue(1.0f, 0); - - vk::RenderingAttachmentInfo colorAttachmentInfo = { - .imageView = swapChainImageViews[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor - }; - - vk::RenderingAttachmentInfo depthAttachmentInfo = { - .imageView = depthImageView, - .imageLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eDontCare, - .clearValue = clearDepth - }; - - vk::RenderingInfo renderingInfo = { - .renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &colorAttachmentInfo, - .pDepthAttachment = &depthAttachmentInfo - }; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].bindIndexBuffer( *indexBuffer, 0, vk::IndexType::eUint16 ); - commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); - commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); - commandBuffers[currentFrame].endRendering(); - // After rendering, transition the swapchain image to PRESENT_SRC - transition_image_layout( - imageIndex, - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage - ); - commandBuffers[currentFrame].end(); - } - - void transition_image_layout( - uint32_t imageIndex, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::AccessFlags2 src_access_mask, - vk::AccessFlags2 dst_access_mask, - vk::PipelineStageFlags2 src_stage_mask, - vk::PipelineStageFlags2 dst_stage_mask - ) { - vk::ImageMemoryBarrier2 barrier = { - .srcStageMask = src_stage_mask, - .srcAccessMask = src_access_mask, - .dstStageMask = dst_stage_mask, - .dstAccessMask = dst_access_mask, - .oldLayout = old_layout, - .newLayout = new_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); - } - - void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); - - for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - } - - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - inFlightFences.emplace_back(device, vk::FenceCreateInfo{ .flags = vk::FenceCreateFlagBits::eSignaled }); - } - } - - void updateUniformBuffer(uint32_t currentImage) { - static auto startTime = std::chrono::high_resolution_clock::now(); - - auto currentTime = std::chrono::high_resolution_clock::now(); - float time = std::chrono::duration(currentTime - startTime).count(); - - UniformBufferObject ubo{}; - ubo.model = rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.view = lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); - ubo.proj[1][1] *= -1; - - memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); - } - - void drawFrame() { - while ( vk::Result::eTimeout == device.waitForFences( *inFlightFences[currentFrame], vk::True, UINT64_MAX ) ) - ; - auto [result, imageIndex] = swapChain.acquireNextImage( UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr ); - - if (result == vk::Result::eErrorOutOfDateKHR) { - recreateSwapChain(); - return; - } - if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) { - throw std::runtime_error("failed to acquire swap chain image!"); - } - updateUniformBuffer(currentFrame); - - device.resetFences( *inFlightFences[currentFrame] ); - commandBuffers[currentFrame].reset(); - recordCommandBuffer(imageIndex); - - vk::PipelineStageFlags waitDestinationStageMask( vk::PipelineStageFlagBits::eColorAttachmentOutput ); - const vk::SubmitInfo submitInfo{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], - .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], - .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex] }; - queue.submit(submitInfo, *inFlightFences[currentFrame]); - - - const vk::PresentInfoKHR presentInfoKHR{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], - .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex }; - result = queue.presentKHR(presentInfoKHR); - if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { - framebufferResized = false; - recreateSwapChain(); - } else if (result != vk::Result::eSuccess) { - throw std::runtime_error("failed to present swap chain image!"); - } - semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; - } - - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size(), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static vk::Format chooseSwapSurfaceFormat(const std::vector& availableFormats) { - const auto formatIt = std::ranges::find_if(availableFormats, - [](const auto& format) { - return format.format == vk::Format::eB8G8R8A8Srgb && - format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; - }); - return formatIt != availableFormats.end() ? formatIt->format : availableFormats[0].format; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : 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) - }; - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - 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; - } -}; - -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/28_model_loading.cpp b/attachments/28_model_loading.cpp index 76e9e177..9bb1005d 100644 --- a/attachments/28_model_loading.cpp +++ b/attachments/28_model_loading.cpp @@ -1,24 +1,24 @@ -#include -#include -#include -#include -#include -#include -#include #include -#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include #define GLM_FORCE_RADIANS @@ -34,16 +34,15 @@ import vulkan_hpp; #define TINYOBJLOADER_IMPLEMENTATION #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -constexpr uint64_t FenceTimeout = 100000000; -const std::string MODEL_PATH = "models/viking_room.obj"; -const std::string TEXTURE_PATH = "textures/viking_room.png"; -constexpr int MAX_FRAMES_IN_FLIGHT = 2; +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; +constexpr uint64_t FenceTimeout = 100000000; +const std::string MODEL_PATH = "models/viking_room.obj"; +const std::string TEXTURE_PATH = "textures/viking_room.png"; +constexpr int MAX_FRAMES_IN_FLIGHT = 2; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -51,1116 +50,1152 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -struct Vertex { - glm::vec3 pos; - glm::vec3 color; - glm::vec2 texCoord; - - static vk::VertexInputBindingDescription getBindingDescription() { - return { 0, sizeof(Vertex), vk::VertexInputRate::eVertex }; - } - - static std::array getAttributeDescriptions() { - return { - vk::VertexInputAttributeDescription( 0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, pos) ), - vk::VertexInputAttributeDescription( 1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color) ), - vk::VertexInputAttributeDescription( 2, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, texCoord) ) - }; - } - - bool operator==(const Vertex& other) const { - return pos == other.pos && color == other.color && texCoord == other.texCoord; - } +struct Vertex +{ + glm::vec3 pos; + glm::vec3 color; + glm::vec2 texCoord; + + static vk::VertexInputBindingDescription getBindingDescription() + { + return {0, sizeof(Vertex), vk::VertexInputRate::eVertex}; + } + + static std::array getAttributeDescriptions() + { + return { + vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, pos)), + vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color)), + vk::VertexInputAttributeDescription(2, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, texCoord))}; + } + + bool operator==(const Vertex &other) const + { + return pos == other.pos && color == other.color && texCoord == other.texCoord; + } }; -template<> struct std::hash { - size_t operator()(Vertex const& vertex) const noexcept { - return ((hash()(vertex.pos) ^ (hash()(vertex.color) << 1)) >> 1) ^ (hash()(vertex.texCoord) << 1); - } +template <> +struct std::hash +{ + size_t operator()(Vertex const &vertex) const noexcept + { + return ((hash()(vertex.pos) ^ (hash()(vertex.color) << 1)) >> 1) ^ (hash()(vertex.texCoord) << 1); + } }; -struct UniformBufferObject { - alignas(16) glm::mat4 model; - alignas(16) glm::mat4 view; - alignas(16) glm::mat4 proj; +struct UniformBufferObject +{ + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; }; -class HelloTriangleApplication { -public: - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } - -private: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::Format swapChainImageFormat = vk::Format::eUndefined; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - - vk::raii::Image depthImage = nullptr; - vk::raii::DeviceMemory depthImageMemory = nullptr; - vk::raii::ImageView depthImageView = nullptr; - - vk::raii::Image textureImage = nullptr; - vk::raii::DeviceMemory textureImageMemory = nullptr; - vk::raii::ImageView textureImageView = nullptr; - vk::raii::Sampler textureSampler = nullptr; - - std::vector vertices; - std::vector indices; - vk::raii::Buffer vertexBuffer = nullptr; - vk::raii::DeviceMemory vertexBufferMemory = nullptr; - vk::raii::Buffer indexBuffer = nullptr; - vk::raii::DeviceMemory indexBufferMemory = nullptr; - - std::vector uniformBuffers; - std::vector uniformBuffersMemory; - std::vector uniformBuffersMapped; - - vk::raii::DescriptorPool descriptorPool = nullptr; - std::vector descriptorSets; - - vk::raii::CommandPool commandPool = nullptr; - std::vector commandBuffers; - - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; - std::vector inFlightFences; - uint32_t semaphoreIndex = 0; - uint32_t currentFrame = 0; - - bool framebufferResized = false; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); - } - - static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { - auto app = static_cast(glfwGetWindowUserPointer(window)); - app->framebufferResized = true; - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - createSurface(); - pickPhysicalDevice(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createDescriptorSetLayout(); - createGraphicsPipeline(); - createCommandPool(); - createDepthResources(); - createTextureImage(); - createTextureImageView(); - createTextureSampler(); - loadModel(); - createVertexBuffer(); - createIndexBuffer(); - createUniformBuffers(); - createDescriptorPool(); - createDescriptorSets(); - createCommandBuffers(); - createSyncObjects(); - } - - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - drawFrame(); - } - - device.waitIdle(); - } - - void cleanupSwapChain() { - swapChainImageViews.clear(); - swapChain = nullptr; - } - - void cleanup() const { - glfwDestroyWindow(window); - - glfwTerminate(); - } - - void recreateSwapChain() { - int width = 0, height = 0; - glfwGetFramebufferSize(window, &width, &height); - while (width == 0 || height == 0) { - glfwGetFramebufferSize(window, &width, &height); - glfwWaitEvents(); - } - - device.waitIdle(); - - cleanupSwapChain(); - createSwapChain(); - createImageViews(); - createDepthResources(); - } - - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().features.samplerAnisotropy && - features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - } ); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error( "failed to find a suitable GPU!" ); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain featureChain = { - {.features = {.samplerAnisotropy = true } }, // vk::PhysicalDeviceFeatures2 - {.synchronization2 = true, .dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface); - swapChainImageFormat = 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{ - .surface = surface, .minImageCount = minImageCount, - .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, - .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 }; - - swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); - swapChainImages = swapChain.getImages(); - } - - void createImageViews() { - 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( device, imageViewCreateInfo ); - } - } - - void createDescriptorSetLayout() { - std::array bindings = { - vk::DescriptorSetLayoutBinding( 0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr), - vk::DescriptorSetLayoutBinding( 1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment, nullptr) - }; - - vk::DescriptorSetLayoutCreateInfo layoutInfo{ .bindingCount = static_cast(bindings.size()), .pBindings = bindings.data() }; - descriptorSetLayout = vk::raii::DescriptorSetLayout(device, layoutInfo); - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; - - auto bindingDescription = Vertex::getBindingDescription(); - auto attributeDescriptions = Vertex::getAttributeDescriptions(); - vk::PipelineVertexInputStateCreateInfo vertexInputInfo{ - .vertexBindingDescriptionCount = 1, - .pVertexBindingDescriptions = &bindingDescription, - .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), - .pVertexAttributeDescriptions = attributeDescriptions.data() - }; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ - .topology = vk::PrimitiveTopology::eTriangleList, - .primitiveRestartEnable = vk::False - }; - vk::PipelineViewportStateCreateInfo viewportState{ - .viewportCount = 1, - .scissorCount = 1 - }; - vk::PipelineRasterizationStateCreateInfo rasterizer{ - .depthClampEnable = vk::False, - .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, - .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eCounterClockwise, - .depthBiasEnable = vk::False - }; - rasterizer.lineWidth = 1.0f; - vk::PipelineMultisampleStateCreateInfo multisampling{ - .rasterizationSamples = vk::SampleCountFlagBits::e1, - .sampleShadingEnable = vk::False - }; - vk::PipelineDepthStencilStateCreateInfo depthStencil{ - .depthTestEnable = vk::True, - .depthWriteEnable = vk::True, - .depthCompareOp = vk::CompareOp::eLess, - .depthBoundsTestEnable = vk::False, - .stencilTestEnable = vk::False - }; - vk::PipelineColorBlendAttachmentState colorBlendAttachment; - colorBlendAttachment.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA; - colorBlendAttachment.blendEnable = vk::False; - - vk::PipelineColorBlendStateCreateInfo colorBlending{ - .logicOpEnable = vk::False, - .logicOp = vk::LogicOp::eCopy, - .attachmentCount = 1, - .pAttachments = &colorBlendAttachment - }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0 }; - - pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); - - vk::Format depthFormat = findDepthFormat(); - vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ - .colorAttachmentCount = 1, - .pColorAttachmentFormats = &swapChainImageFormat, - .depthAttachmentFormat = depthFormat - }; - vk::GraphicsPipelineCreateInfo pipelineInfo{ .pNext = &pipelineRenderingCreateInfo, - .stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pDepthStencilState = &depthStencil, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = pipelineLayout, - .renderPass = nullptr - }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{ - .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex - }; - commandPool = vk::raii::CommandPool(device, poolInfo); - } - - void createDepthResources() { - vk::Format depthFormat = findDepthFormat(); - - createImage(swapChainExtent.width, swapChainExtent.height, depthFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eDepthStencilAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, depthImage, depthImageMemory); - depthImageView = createImageView(depthImage, depthFormat, vk::ImageAspectFlagBits::eDepth); - } - - vk::Format findSupportedFormat(const std::vector& candidates, vk::ImageTiling tiling, vk::FormatFeatureFlags features) const { - for (const auto format : candidates) { - vk::FormatProperties props = physicalDevice.getFormatProperties(format); - - if (tiling == vk::ImageTiling::eLinear && (props.linearTilingFeatures & features) == features) { - return format; - } - if (tiling == vk::ImageTiling::eOptimal && (props.optimalTilingFeatures & features) == features) { - return format; - } - } - - throw std::runtime_error("failed to find supported format!"); - } - - [[nodiscard]] vk::Format findDepthFormat() const { - return findSupportedFormat( - {vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, vk::Format::eD24UnormS8Uint}, - vk::ImageTiling::eOptimal, - vk::FormatFeatureFlagBits::eDepthStencilAttachment - ); - } - - static bool hasStencilComponent(vk::Format format) { - return format == vk::Format::eD32SfloatS8Uint || format == vk::Format::eD24UnormS8Uint; - } - - void createTextureImage() { - int texWidth, texHeight, texChannels; - stbi_uc* pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); - vk::DeviceSize imageSize = texWidth * texHeight * 4; - - if (!pixels) { - throw std::runtime_error("failed to load texture image!"); - } - - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, imageSize); - memcpy(data, pixels, imageSize); - stagingBufferMemory.unmapMemory(); - - stbi_image_free(pixels); - - createImage(texWidth, texHeight, vk::Format::eR8G8B8A8Srgb, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory); - - transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal); - copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); - transitionImageLayout(textureImage, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal); - } - - void createTextureImageView() { - textureImageView = createImageView(textureImage, vk::Format::eR8G8B8A8Srgb, vk::ImageAspectFlagBits::eColor); - } - - void createTextureSampler() { - vk::PhysicalDeviceProperties properties = physicalDevice.getProperties(); - vk::SamplerCreateInfo samplerInfo{ - .magFilter = vk::Filter::eLinear, - .minFilter = vk::Filter::eLinear, - .mipmapMode = vk::SamplerMipmapMode::eLinear, - .addressModeU = vk::SamplerAddressMode::eRepeat, - .addressModeV = vk::SamplerAddressMode::eRepeat, - .addressModeW = vk::SamplerAddressMode::eRepeat, - .mipLodBias = 0.0f, - .anisotropyEnable = vk::True, - .maxAnisotropy = properties.limits.maxSamplerAnisotropy, - .compareEnable = vk::False, - .compareOp = vk::CompareOp::eAlways - }; - textureSampler = vk::raii::Sampler(device, samplerInfo); - } - - vk::raii::ImageView createImageView(vk::raii::Image& image, vk::Format format, vk::ImageAspectFlags aspectFlags) { - vk::ImageViewCreateInfo viewInfo{ - .image = image, - .viewType = vk::ImageViewType::e2D, - .format = format, - .subresourceRange = { aspectFlags, 0, 1, 0, 1 } - }; - return vk::raii::ImageView(device, viewInfo); - } - - void createImage(uint32_t width, uint32_t height, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image& image, vk::raii::DeviceMemory& imageMemory) { - vk::ImageCreateInfo imageInfo{ - .imageType = vk::ImageType::e2D, - .format = format, - .extent = {width, height, 1}, - .mipLevels = 1, - .arrayLayers = 1, - .samples = vk::SampleCountFlagBits::e1, - .tiling = tiling, - .usage = usage, - .sharingMode = vk::SharingMode::eExclusive, - .initialLayout = vk::ImageLayout::eUndefined - }; - image = vk::raii::Image(device, imageInfo); - - vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ - .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) - }; - imageMemory = vk::raii::DeviceMemory(device, allocInfo); - image.bindMemory(imageMemory, 0); - } - - void transitionImageLayout(const vk::raii::Image& image, vk::ImageLayout oldLayout, vk::ImageLayout newLayout) { - auto commandBuffer = beginSingleTimeCommands(); - - vk::ImageMemoryBarrier barrier{ - .oldLayout = oldLayout, - .newLayout = newLayout, - .image = image, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } - }; - - vk::PipelineStageFlags sourceStage; - vk::PipelineStageFlags destinationStage; - - if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) { - barrier.srcAccessMask = {}; - barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; - - sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; - destinationStage = vk::PipelineStageFlagBits::eTransfer; - } else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) { - barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; - barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - - sourceStage = vk::PipelineStageFlagBits::eTransfer; - destinationStage = vk::PipelineStageFlagBits::eFragmentShader; - } else { - throw std::invalid_argument("unsupported layout transition!"); - } - commandBuffer->pipelineBarrier( sourceStage, destinationStage, {}, {}, nullptr, barrier ); - endSingleTimeCommands(*commandBuffer); - } - - void copyBufferToImage(const vk::raii::Buffer& buffer, vk::raii::Image& image, uint32_t width, uint32_t height) { - std::unique_ptr commandBuffer = beginSingleTimeCommands(); - vk::BufferImageCopy region{ - .bufferOffset = 0, - .bufferRowLength = 0, - .bufferImageHeight = 0, - .imageSubresource = { vk::ImageAspectFlagBits::eColor, 0, 0, 1 }, - .imageOffset = {0, 0, 0}, - .imageExtent = {width, height, 1} - }; - commandBuffer->copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, {region}); - endSingleTimeCommands(*commandBuffer); - } - - void loadModel() { - tinyobj::attrib_t attrib; - std::vector shapes; - std::vector materials; - std::string warn, err; - - if (!LoadObj(&attrib, &shapes, &materials, &warn, &err, MODEL_PATH.c_str())) { - throw std::runtime_error(warn + err); - } - - std::unordered_map uniqueVertices{}; - - for (const auto& shape : shapes) { - for (const auto& index : shape.mesh.indices) { - Vertex vertex{}; - - vertex.pos = { - attrib.vertices[3 * index.vertex_index + 0], - attrib.vertices[3 * index.vertex_index + 1], - attrib.vertices[3 * index.vertex_index + 2] - }; - - vertex.texCoord = { - attrib.texcoords[2 * index.texcoord_index + 0], - 1.0f - attrib.texcoords[2 * index.texcoord_index + 1] - }; - - vertex.color = {1.0f, 1.0f, 1.0f}; - - if (!uniqueVertices.contains(vertex)) { - uniqueVertices[vertex] = static_cast(vertices.size()); - vertices.push_back(vertex); - } - - indices.push_back(uniqueVertices[vertex]); - } - } - } - - void createVertexBuffer() { - vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(dataStaging, vertices.data(), bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); - - copyBuffer(stagingBuffer, vertexBuffer, bufferSize); - } - - void createIndexBuffer() { - vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); - - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(data, indices.data(), bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); - - copyBuffer(stagingBuffer, indexBuffer, bufferSize); - } - - void createUniformBuffers() { - uniformBuffers.clear(); - uniformBuffersMemory.clear(); - uniformBuffersMapped.clear(); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DeviceSize bufferSize = sizeof(UniformBufferObject); - vk::raii::Buffer buffer({}); - vk::raii::DeviceMemory bufferMem({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); - uniformBuffers.emplace_back(std::move(buffer)); - uniformBuffersMemory.emplace_back(std::move(bufferMem)); - uniformBuffersMapped.emplace_back( uniformBuffersMemory[i].mapMemory(0, bufferSize)); - } - } - - void createDescriptorPool() { - std::array poolSize { - vk::DescriptorPoolSize( vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT), - vk::DescriptorPoolSize( vk::DescriptorType::eCombinedImageSampler, MAX_FRAMES_IN_FLIGHT) - }; - vk::DescriptorPoolCreateInfo poolInfo{ - .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, - .maxSets = MAX_FRAMES_IN_FLIGHT, - .poolSizeCount = static_cast(poolSize.size()), - .pPoolSizes = poolSize.data() - }; - descriptorPool = vk::raii::DescriptorPool(device, poolInfo); - } - - void createDescriptorSets() { - std::vector layouts(MAX_FRAMES_IN_FLIGHT, descriptorSetLayout); - vk::DescriptorSetAllocateInfo allocInfo{ - .descriptorPool = descriptorPool, - .descriptorSetCount = static_cast(layouts.size()), - .pSetLayouts = layouts.data() - }; - - descriptorSets.clear(); - descriptorSets = device.allocateDescriptorSets(allocInfo); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DescriptorBufferInfo bufferInfo{ - .buffer = uniformBuffers[i], - .offset = 0, - .range = sizeof(UniformBufferObject) - }; - vk::DescriptorImageInfo imageInfo{ - .sampler = textureSampler, - .imageView = textureImageView, - .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal - }; - std::array descriptorWrites{ - vk::WriteDescriptorSet{ - .dstSet = descriptorSets[i], - .dstBinding = 0, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eUniformBuffer, - .pBufferInfo = &bufferInfo - }, - vk::WriteDescriptorSet{ - .dstSet = descriptorSets[i], - .dstBinding = 1, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eCombinedImageSampler, - .pImageInfo = &imageInfo - } - }; - device.updateDescriptorSets(descriptorWrites, {}); - } - } - - void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer& buffer, vk::raii::DeviceMemory& bufferMemory) { - vk::BufferCreateInfo bufferInfo{ - .size = size, - .usage = usage, - .sharingMode = vk::SharingMode::eExclusive - }; - buffer = vk::raii::Buffer(device, bufferInfo); - vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ - .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) - }; - bufferMemory = vk::raii::DeviceMemory(device, allocInfo); - buffer.bindMemory(bufferMemory, 0); - } - - std::unique_ptr beginSingleTimeCommands() { - vk::CommandBufferAllocateInfo allocInfo{ - .commandPool = commandPool, - .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = 1 - }; - std::unique_ptr commandBuffer = std::make_unique(std::move(vk::raii::CommandBuffers(device, allocInfo).front())); - - vk::CommandBufferBeginInfo beginInfo{ - .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit - }; - commandBuffer->begin(beginInfo); - - return commandBuffer; - } - - void endSingleTimeCommands(const vk::raii::CommandBuffer& commandBuffer) const { - commandBuffer.end(); - - vk::SubmitInfo submitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandBuffer }; - queue.submit(submitInfo, nullptr); - queue.waitIdle(); - } - - void copyBuffer(vk::raii::Buffer & srcBuffer, vk::raii::Buffer & dstBuffer, vk::DeviceSize size) { - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1 }; - vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); - commandCopyBuffer.begin(vk::CommandBufferBeginInfo{ .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit }); - commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy{ .size = size }); - commandCopyBuffer.end(); - queue.submit(vk::SubmitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer }, nullptr); - queue.waitIdle(); - } - - uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) { - vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); - - for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { - if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { - return i; - } - } - - throw std::runtime_error("failed to find suitable memory type!"); - } - - void createCommandBuffers() { - commandBuffers.clear(); - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = MAX_FRAMES_IN_FLIGHT }; - commandBuffers = vk::raii::CommandBuffers(device, allocInfo); - } - - void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin({}); - // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL - transition_image_layout( - imageIndex, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage - ); - // Transition depth image to depth attachment optimal layout - vk::ImageMemoryBarrier2 depthBarrier = { - .srcStageMask = vk::PipelineStageFlagBits2::eTopOfPipe, - .srcAccessMask = {}, - .dstStageMask = vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, - .dstAccessMask = vk::AccessFlagBits2::eDepthStencilAttachmentRead | vk::AccessFlagBits2::eDepthStencilAttachmentWrite, - .oldLayout = vk::ImageLayout::eUndefined, - .newLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = depthImage, - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eDepth, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo depthDependencyInfo = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &depthBarrier - }; - commandBuffers[currentFrame].pipelineBarrier2(depthDependencyInfo); - - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::ClearValue clearDepth = vk::ClearDepthStencilValue(1.0f, 0); - - vk::RenderingAttachmentInfo colorAttachmentInfo = { - .imageView = swapChainImageViews[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor - }; - - vk::RenderingAttachmentInfo depthAttachmentInfo = { - .imageView = depthImageView, - .imageLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eDontCare, - .clearValue = clearDepth - }; - - vk::RenderingInfo renderingInfo = { - .renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &colorAttachmentInfo, - .pDepthAttachment = &depthAttachmentInfo - }; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].bindIndexBuffer( *indexBuffer, 0, vk::IndexType::eUint32 ); - commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); - commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); - commandBuffers[currentFrame].endRendering(); - // After rendering, transition the swapchain image to PRESENT_SRC - transition_image_layout( - imageIndex, - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage - ); - commandBuffers[currentFrame].end(); - } - - void transition_image_layout( - uint32_t imageIndex, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::AccessFlags2 src_access_mask, - vk::AccessFlags2 dst_access_mask, - vk::PipelineStageFlags2 src_stage_mask, - vk::PipelineStageFlags2 dst_stage_mask - ) { - vk::ImageMemoryBarrier2 barrier = { - .srcStageMask = src_stage_mask, - .srcAccessMask = src_access_mask, - .dstStageMask = dst_stage_mask, - .dstAccessMask = dst_access_mask, - .oldLayout = old_layout, - .newLayout = new_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); - } - - void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); - - for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - } - - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - inFlightFences.emplace_back(device, vk::FenceCreateInfo{ .flags = vk::FenceCreateFlagBits::eSignaled }); - } - } - - void updateUniformBuffer(uint32_t currentImage) const { - static auto startTime = std::chrono::high_resolution_clock::now(); - - auto currentTime = std::chrono::high_resolution_clock::now(); - float time = std::chrono::duration(currentTime - startTime).count(); - - UniformBufferObject ubo{}; - ubo.model = rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.view = lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); - ubo.proj[1][1] *= -1; - - memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); - } - - void drawFrame() { - while ( vk::Result::eTimeout == device.waitForFences( *inFlightFences[currentFrame], vk::True, UINT64_MAX ) ) - ; - auto [result, imageIndex] = swapChain.acquireNextImage( UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr ); - - if (result == vk::Result::eErrorOutOfDateKHR) { - recreateSwapChain(); - return; - } - if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) { - throw std::runtime_error("failed to acquire swap chain image!"); - } - updateUniformBuffer(currentFrame); - - device.resetFences( *inFlightFences[currentFrame] ); - commandBuffers[currentFrame].reset(); - recordCommandBuffer(imageIndex); - - vk::PipelineStageFlags waitDestinationStageMask( vk::PipelineStageFlagBits::eColorAttachmentOutput ); - const vk::SubmitInfo submitInfo{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], - .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], - .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex] }; - queue.submit(submitInfo, *inFlightFences[currentFrame]); - - - const vk::PresentInfoKHR presentInfoKHR{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], - .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex }; - result = queue.presentKHR(presentInfoKHR); - if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { - framebufferResized = false; - recreateSwapChain(); - } else if (result != vk::Result::eSuccess) { - throw std::runtime_error("failed to present swap chain image!"); - } - semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; - } - - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size(), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static vk::Format chooseSwapSurfaceFormat(const std::vector& availableFormats) { - const auto formatIt = std::ranges::find_if(availableFormats, - [](const auto& format) { - return format.format == vk::Format::eB8G8R8A8Srgb && - format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; - }); - return formatIt != availableFormats.end() ? formatIt->format : availableFormats[0].format; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : 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) - }; - } - - [[nodiscard]] std::vector getRequiredExtensions() const { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - 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: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::Format swapChainImageFormat = vk::Format::eUndefined; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + + vk::raii::Image depthImage = nullptr; + vk::raii::DeviceMemory depthImageMemory = nullptr; + vk::raii::ImageView depthImageView = nullptr; + + vk::raii::Image textureImage = nullptr; + vk::raii::DeviceMemory textureImageMemory = nullptr; + vk::raii::ImageView textureImageView = nullptr; + vk::raii::Sampler textureSampler = nullptr; + + std::vector vertices; + std::vector indices; + vk::raii::Buffer vertexBuffer = nullptr; + vk::raii::DeviceMemory vertexBufferMemory = nullptr; + vk::raii::Buffer indexBuffer = nullptr; + vk::raii::DeviceMemory indexBufferMemory = nullptr; + + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + std::vector uniformBuffersMapped; + + vk::raii::DescriptorPool descriptorPool = nullptr; + std::vector descriptorSets; + + vk::raii::CommandPool commandPool = nullptr; + std::vector commandBuffers; + + std::vector presentCompleteSemaphore; + std::vector renderFinishedSemaphore; + std::vector inFlightFences; + uint32_t semaphoreIndex = 0; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow *window, int width, int height) + { + auto app = static_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createDescriptorSetLayout(); + createGraphicsPipeline(); + createCommandPool(); + createDepthResources(); + createTextureImage(); + createTextureImageView(); + createTextureSampler(); + loadModel(); + createVertexBuffer(); + createIndexBuffer(); + createUniformBuffers(); + createDescriptorPool(); + createDescriptorSets(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() + { + while (!glfwWindowShouldClose(window)) + { + glfwPollEvents(); + drawFrame(); + } + + device.waitIdle(); + } + + void cleanupSwapChain() + { + swapChainImageViews.clear(); + swapChain = nullptr; + } + + void cleanup() const + { + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() + { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) + { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + device.waitIdle(); + + cleanupSwapChain(); + createSwapChain(); + createImageViews(); + createDepthResources(); + } + + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().features.samplerAnisotropy && + features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain featureChain = { + {.features = {.samplerAnisotropy = true}}, // vk::PhysicalDeviceFeatures2 + {.synchronization2 = true, .dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface); + swapChainImageFormat = 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{ + .surface = surface, .minImageCount = minImageCount, .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + void createImageViews() + { + 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(device, imageViewCreateInfo); + } + } + + void createDescriptorSetLayout() + { + std::array bindings = { + vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr), + vk::DescriptorSetLayoutBinding(1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment, nullptr)}; + + vk::DescriptorSetLayoutCreateInfo layoutInfo{.bindingCount = static_cast(bindings.size()), .pBindings = bindings.data()}; + descriptorSetLayout = vk::raii::DescriptorSetLayout(device, layoutInfo); + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; + + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{ + .vertexBindingDescriptionCount = 1, + .pVertexBindingDescriptions = &bindingDescription, + .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), + .pVertexAttributeDescriptions = attributeDescriptions.data()}; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ + .topology = vk::PrimitiveTopology::eTriangleList, + .primitiveRestartEnable = vk::False}; + vk::PipelineViewportStateCreateInfo viewportState{ + .viewportCount = 1, + .scissorCount = 1}; + vk::PipelineRasterizationStateCreateInfo rasterizer{ + .depthClampEnable = vk::False, + .rasterizerDiscardEnable = vk::False, + .polygonMode = vk::PolygonMode::eFill, + .cullMode = vk::CullModeFlagBits::eBack, + .frontFace = vk::FrontFace::eCounterClockwise, + .depthBiasEnable = vk::False}; + rasterizer.lineWidth = 1.0f; + vk::PipelineMultisampleStateCreateInfo multisampling{ + .rasterizationSamples = vk::SampleCountFlagBits::e1, + .sampleShadingEnable = vk::False}; + vk::PipelineDepthStencilStateCreateInfo depthStencil{ + .depthTestEnable = vk::True, + .depthWriteEnable = vk::True, + .depthCompareOp = vk::CompareOp::eLess, + .depthBoundsTestEnable = vk::False, + .stencilTestEnable = vk::False}; + vk::PipelineColorBlendAttachmentState colorBlendAttachment; + colorBlendAttachment.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA; + colorBlendAttachment.blendEnable = vk::False; + + vk::PipelineColorBlendStateCreateInfo colorBlending{ + .logicOpEnable = vk::False, + .logicOp = vk::LogicOp::eCopy, + .attachmentCount = 1, + .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0}; + + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::Format depthFormat = findDepthFormat(); + vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ + .colorAttachmentCount = 1, + .pColorAttachmentFormats = &swapChainImageFormat, + .depthAttachmentFormat = depthFormat}; + vk::GraphicsPipelineCreateInfo pipelineInfo{.pNext = &pipelineRenderingCreateInfo, + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pDepthStencilState = &depthStencil, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{ + .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex}; + commandPool = vk::raii::CommandPool(device, poolInfo); + } + + void createDepthResources() + { + vk::Format depthFormat = findDepthFormat(); + + createImage(swapChainExtent.width, swapChainExtent.height, depthFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eDepthStencilAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, depthImage, depthImageMemory); + depthImageView = createImageView(depthImage, depthFormat, vk::ImageAspectFlagBits::eDepth); + } + + vk::Format findSupportedFormat(const std::vector &candidates, vk::ImageTiling tiling, vk::FormatFeatureFlags features) const + { + for (const auto format : candidates) + { + vk::FormatProperties props = physicalDevice.getFormatProperties(format); + + if (tiling == vk::ImageTiling::eLinear && (props.linearTilingFeatures & features) == features) + { + return format; + } + if (tiling == vk::ImageTiling::eOptimal && (props.optimalTilingFeatures & features) == features) + { + return format; + } + } + + throw std::runtime_error("failed to find supported format!"); + } + + [[nodiscard]] vk::Format findDepthFormat() const + { + return findSupportedFormat( + {vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, vk::Format::eD24UnormS8Uint}, + vk::ImageTiling::eOptimal, + vk::FormatFeatureFlagBits::eDepthStencilAttachment); + } + + static bool hasStencilComponent(vk::Format format) + { + return format == vk::Format::eD32SfloatS8Uint || format == vk::Format::eD24UnormS8Uint; + } + + void createTextureImage() + { + int texWidth, texHeight, texChannels; + stbi_uc *pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + vk::DeviceSize imageSize = texWidth * texHeight * 4; + + if (!pixels) + { + throw std::runtime_error("failed to load texture image!"); + } + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, imageSize); + memcpy(data, pixels, imageSize); + stagingBufferMemory.unmapMemory(); + + stbi_image_free(pixels); + + createImage(texWidth, texHeight, vk::Format::eR8G8B8A8Srgb, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory); + + transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); + transitionImageLayout(textureImage, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal); + } + + void createTextureImageView() + { + textureImageView = createImageView(textureImage, vk::Format::eR8G8B8A8Srgb, vk::ImageAspectFlagBits::eColor); + } + + void createTextureSampler() + { + vk::PhysicalDeviceProperties properties = physicalDevice.getProperties(); + vk::SamplerCreateInfo samplerInfo{ + .magFilter = vk::Filter::eLinear, + .minFilter = vk::Filter::eLinear, + .mipmapMode = vk::SamplerMipmapMode::eLinear, + .addressModeU = vk::SamplerAddressMode::eRepeat, + .addressModeV = vk::SamplerAddressMode::eRepeat, + .addressModeW = vk::SamplerAddressMode::eRepeat, + .mipLodBias = 0.0f, + .anisotropyEnable = vk::True, + .maxAnisotropy = properties.limits.maxSamplerAnisotropy, + .compareEnable = vk::False, + .compareOp = vk::CompareOp::eAlways}; + textureSampler = vk::raii::Sampler(device, samplerInfo); + } + + vk::raii::ImageView createImageView(vk::raii::Image &image, vk::Format format, vk::ImageAspectFlags aspectFlags) + { + vk::ImageViewCreateInfo viewInfo{ + .image = image, + .viewType = vk::ImageViewType::e2D, + .format = format, + .subresourceRange = {aspectFlags, 0, 1, 0, 1}}; + return vk::raii::ImageView(device, viewInfo); + } + + void createImage(uint32_t width, uint32_t height, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image &image, vk::raii::DeviceMemory &imageMemory) + { + vk::ImageCreateInfo imageInfo{ + .imageType = vk::ImageType::e2D, + .format = format, + .extent = {width, height, 1}, + .mipLevels = 1, + .arrayLayers = 1, + .samples = vk::SampleCountFlagBits::e1, + .tiling = tiling, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive, + .initialLayout = vk::ImageLayout::eUndefined}; + image = vk::raii::Image(device, imageInfo); + + vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{ + .allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + imageMemory = vk::raii::DeviceMemory(device, allocInfo); + image.bindMemory(imageMemory, 0); + } + + void transitionImageLayout(const vk::raii::Image &image, vk::ImageLayout oldLayout, vk::ImageLayout newLayout) + { + auto commandBuffer = beginSingleTimeCommands(); + + vk::ImageMemoryBarrier barrier{ + .oldLayout = oldLayout, + .newLayout = newLayout, + .image = image, + .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + + vk::PipelineStageFlags sourceStage; + vk::PipelineStageFlags destinationStage; + + if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) + { + barrier.srcAccessMask = {}; + barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; + + sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; + destinationStage = vk::PipelineStageFlagBits::eTransfer; + } + else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) + { + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + + sourceStage = vk::PipelineStageFlagBits::eTransfer; + destinationStage = vk::PipelineStageFlagBits::eFragmentShader; + } + else + { + throw std::invalid_argument("unsupported layout transition!"); + } + commandBuffer->pipelineBarrier(sourceStage, destinationStage, {}, {}, nullptr, barrier); + endSingleTimeCommands(*commandBuffer); + } + + void copyBufferToImage(const vk::raii::Buffer &buffer, vk::raii::Image &image, uint32_t width, uint32_t height) + { + std::unique_ptr commandBuffer = beginSingleTimeCommands(); + vk::BufferImageCopy region{ + .bufferOffset = 0, + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource = {vk::ImageAspectFlagBits::eColor, 0, 0, 1}, + .imageOffset = {0, 0, 0}, + .imageExtent = {width, height, 1}}; + commandBuffer->copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, {region}); + endSingleTimeCommands(*commandBuffer); + } + + void loadModel() + { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn, err; + + if (!LoadObj(&attrib, &shapes, &materials, &warn, &err, MODEL_PATH.c_str())) + { + throw std::runtime_error(warn + err); + } + + std::unordered_map uniqueVertices{}; + + for (const auto &shape : shapes) + { + for (const auto &index : shape.mesh.indices) + { + Vertex vertex{}; + + vertex.pos = { + attrib.vertices[3 * index.vertex_index + 0], + attrib.vertices[3 * index.vertex_index + 1], + attrib.vertices[3 * index.vertex_index + 2]}; + + vertex.texCoord = { + attrib.texcoords[2 * index.texcoord_index + 0], + 1.0f - attrib.texcoords[2 * index.texcoord_index + 1]}; + + vertex.color = {1.0f, 1.0f, 1.0f}; + + if (!uniqueVertices.contains(vertex)) + { + uniqueVertices[vertex] = static_cast(vertices.size()); + vertices.push_back(vertex); + } + + indices.push_back(uniqueVertices[vertex]); + } + } + } + + void createVertexBuffer() + { + vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(dataStaging, vertices.data(), bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + } + + void createIndexBuffer() + { + vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(data, indices.data(), bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); + + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + } + + void createUniformBuffers() + { + uniformBuffers.clear(); + uniformBuffersMemory.clear(); + uniformBuffersMapped.clear(); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DeviceSize bufferSize = sizeof(UniformBufferObject); + vk::raii::Buffer buffer({}); + vk::raii::DeviceMemory bufferMem({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); + uniformBuffers.emplace_back(std::move(buffer)); + uniformBuffersMemory.emplace_back(std::move(bufferMem)); + uniformBuffersMapped.emplace_back(uniformBuffersMemory[i].mapMemory(0, bufferSize)); + } + } + + void createDescriptorPool() + { + std::array poolSize{ + vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT), + vk::DescriptorPoolSize(vk::DescriptorType::eCombinedImageSampler, MAX_FRAMES_IN_FLIGHT)}; + vk::DescriptorPoolCreateInfo poolInfo{ + .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, + .maxSets = MAX_FRAMES_IN_FLIGHT, + .poolSizeCount = static_cast(poolSize.size()), + .pPoolSizes = poolSize.data()}; + descriptorPool = vk::raii::DescriptorPool(device, poolInfo); + } + + void createDescriptorSets() + { + std::vector layouts(MAX_FRAMES_IN_FLIGHT, descriptorSetLayout); + vk::DescriptorSetAllocateInfo allocInfo{ + .descriptorPool = descriptorPool, + .descriptorSetCount = static_cast(layouts.size()), + .pSetLayouts = layouts.data()}; + + descriptorSets.clear(); + descriptorSets = device.allocateDescriptorSets(allocInfo); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DescriptorBufferInfo bufferInfo{ + .buffer = uniformBuffers[i], + .offset = 0, + .range = sizeof(UniformBufferObject)}; + vk::DescriptorImageInfo imageInfo{ + .sampler = textureSampler, + .imageView = textureImageView, + .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal}; + std::array descriptorWrites{ + vk::WriteDescriptorSet{ + .dstSet = descriptorSets[i], + .dstBinding = 0, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eUniformBuffer, + .pBufferInfo = &bufferInfo}, + vk::WriteDescriptorSet{ + .dstSet = descriptorSets[i], + .dstBinding = 1, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .pImageInfo = &imageInfo}}; + device.updateDescriptorSets(descriptorWrites, {}); + } + } + + void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer &buffer, vk::raii::DeviceMemory &bufferMemory) + { + vk::BufferCreateInfo bufferInfo{ + .size = size, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive}; + buffer = vk::raii::Buffer(device, bufferInfo); + vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{ + .allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + bufferMemory = vk::raii::DeviceMemory(device, allocInfo); + buffer.bindMemory(bufferMemory, 0); + } + + std::unique_ptr beginSingleTimeCommands() + { + vk::CommandBufferAllocateInfo allocInfo{ + .commandPool = commandPool, + .level = vk::CommandBufferLevel::ePrimary, + .commandBufferCount = 1}; + std::unique_ptr commandBuffer = std::make_unique(std::move(vk::raii::CommandBuffers(device, allocInfo).front())); + + vk::CommandBufferBeginInfo beginInfo{ + .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; + commandBuffer->begin(beginInfo); + + return commandBuffer; + } + + void endSingleTimeCommands(const vk::raii::CommandBuffer &commandBuffer) const + { + commandBuffer.end(); + + vk::SubmitInfo submitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandBuffer}; + queue.submit(submitInfo, nullptr); + queue.waitIdle(); + } + + void copyBuffer(vk::raii::Buffer &srcBuffer, vk::raii::Buffer &dstBuffer, vk::DeviceSize size) + { + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1}; + vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); + commandCopyBuffer.begin(vk::CommandBufferBeginInfo{.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); + commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy{.size = size}); + commandCopyBuffer.end(); + queue.submit(vk::SubmitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer}, nullptr); + queue.waitIdle(); + } + + uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) + { + vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) + { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) + { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + void createCommandBuffers() + { + commandBuffers.clear(); + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = MAX_FRAMES_IN_FLIGHT}; + commandBuffers = vk::raii::CommandBuffers(device, allocInfo); + } + + void recordCommandBuffer(uint32_t imageIndex) + { + commandBuffers[currentFrame].begin({}); + // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL + transition_image_layout( + imageIndex, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage + ); + // Transition depth image to depth attachment optimal layout + vk::ImageMemoryBarrier2 depthBarrier = { + .srcStageMask = vk::PipelineStageFlagBits2::eTopOfPipe, + .srcAccessMask = {}, + .dstStageMask = vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, + .dstAccessMask = vk::AccessFlagBits2::eDepthStencilAttachmentRead | vk::AccessFlagBits2::eDepthStencilAttachmentWrite, + .oldLayout = vk::ImageLayout::eUndefined, + .newLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = depthImage, + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eDepth, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo depthDependencyInfo = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &depthBarrier}; + commandBuffers[currentFrame].pipelineBarrier2(depthDependencyInfo); + + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::ClearValue clearDepth = vk::ClearDepthStencilValue(1.0f, 0); + + vk::RenderingAttachmentInfo colorAttachmentInfo = { + .imageView = swapChainImageViews[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor}; + + vk::RenderingAttachmentInfo depthAttachmentInfo = { + .imageView = depthImageView, + .imageLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eDontCare, + .clearValue = clearDepth}; + + vk::RenderingInfo renderingInfo = { + .renderArea = {.offset = {0, 0}, .extent = swapChainExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &colorAttachmentInfo, + .pDepthAttachment = &depthAttachmentInfo}; + commandBuffers[currentFrame].beginRendering(renderingInfo); + commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint32); + commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); + commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); + commandBuffers[currentFrame].endRendering(); + // After rendering, transition the swapchain image to PRESENT_SRC + transition_image_layout( + imageIndex, + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage + ); + commandBuffers[currentFrame].end(); + } + + void transition_image_layout( + uint32_t imageIndex, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout, + vk::AccessFlags2 src_access_mask, + vk::AccessFlags2 dst_access_mask, + vk::PipelineStageFlags2 src_stage_mask, + vk::PipelineStageFlags2 dst_stage_mask) + { + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = src_stage_mask, + .srcAccessMask = src_access_mask, + .dstStageMask = dst_stage_mask, + .dstAccessMask = dst_access_mask, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo dependency_info = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + } + + void createSyncObjects() + { + presentCompleteSemaphore.clear(); + renderFinishedSemaphore.clear(); + inFlightFences.clear(); + + for (size_t i = 0; i < swapChainImages.size(); i++) + { + presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); + } + } + + void updateUniformBuffer(uint32_t currentImage) const + { + static auto startTime = std::chrono::high_resolution_clock::now(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + UniformBufferObject ubo{}; + ubo.model = rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.view = lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); + ubo.proj[1][1] *= -1; + + memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); + } + + void drawFrame() + { + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + ; + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr); + + if (result == vk::Result::eErrorOutOfDateKHR) + { + recreateSwapChain(); + return; + } + if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) + { + throw std::runtime_error("failed to acquire swap chain image!"); + } + updateUniformBuffer(currentFrame); + + device.resetFences(*inFlightFences[currentFrame]); + commandBuffers[currentFrame].reset(); + recordCommandBuffer(imageIndex); + + vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[currentFrame]); + + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + result = queue.presentKHR(presentInfoKHR); + if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) + { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != vk::Result::eSuccess) + { + throw std::runtime_error("failed to present swap chain image!"); + } + semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size(), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static vk::Format chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + const auto formatIt = std::ranges::find_if(availableFormats, + [](const auto &format) { + return format.format == vk::Format::eB8G8R8A8Srgb && + format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; + }); + return formatIt != availableFormats.end() ? formatIt->format : availableFormats[0].format; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + 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)}; + } + + [[nodiscard]] std::vector getRequiredExtensions() const + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + 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; + } }; -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/29_mipmapping.cpp b/attachments/29_mipmapping.cpp index 07abbc05..d6ddddef 100644 --- a/attachments/29_mipmapping.cpp +++ b/attachments/29_mipmapping.cpp @@ -1,24 +1,24 @@ -#include -#include -#include -#include -#include -#include -#include #include -#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include #define GLM_FORCE_RADIANS @@ -34,16 +34,15 @@ import vulkan_hpp; #define TINYOBJLOADER_IMPLEMENTATION #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -constexpr uint64_t FenceTimeout = 100000000; -const std::string MODEL_PATH = "models/viking_room.obj"; -const std::string TEXTURE_PATH = "textures/viking_room.png"; -constexpr int MAX_FRAMES_IN_FLIGHT = 2; +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; +constexpr uint64_t FenceTimeout = 100000000; +const std::string MODEL_PATH = "models/viking_room.obj"; +const std::string TEXTURE_PATH = "textures/viking_room.png"; +constexpr int MAX_FRAMES_IN_FLIGHT = 2; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -51,1182 +50,1220 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -struct Vertex { - glm::vec3 pos; - glm::vec3 color; - glm::vec2 texCoord; - - static vk::VertexInputBindingDescription getBindingDescription() { - return { 0, sizeof(Vertex), vk::VertexInputRate::eVertex }; - } - - static std::array getAttributeDescriptions() { - return { - vk::VertexInputAttributeDescription( 0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, pos) ), - vk::VertexInputAttributeDescription( 1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color) ), - vk::VertexInputAttributeDescription( 2, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, texCoord) ) - }; - } - - bool operator==(const Vertex& other) const { - return pos == other.pos && color == other.color && texCoord == other.texCoord; - } +struct Vertex +{ + glm::vec3 pos; + glm::vec3 color; + glm::vec2 texCoord; + + static vk::VertexInputBindingDescription getBindingDescription() + { + return {0, sizeof(Vertex), vk::VertexInputRate::eVertex}; + } + + static std::array getAttributeDescriptions() + { + return { + vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, pos)), + vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color)), + vk::VertexInputAttributeDescription(2, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, texCoord))}; + } + + bool operator==(const Vertex &other) const + { + return pos == other.pos && color == other.color && texCoord == other.texCoord; + } }; -template<> struct std::hash { - size_t operator()(Vertex const& vertex) const noexcept { - return ((hash()(vertex.pos) ^ (hash()(vertex.color) << 1)) >> 1) ^ (hash()(vertex.texCoord) << 1); - } +template <> +struct std::hash +{ + size_t operator()(Vertex const &vertex) const noexcept + { + return ((hash()(vertex.pos) ^ (hash()(vertex.color) << 1)) >> 1) ^ (hash()(vertex.texCoord) << 1); + } }; -struct UniformBufferObject { - alignas(16) glm::mat4 model; - alignas(16) glm::mat4 view; - alignas(16) glm::mat4 proj; +struct UniformBufferObject +{ + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; }; -class HelloTriangleApplication { -public: - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } - -private: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::Format swapChainImageFormat = vk::Format::eUndefined; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - - vk::raii::Image depthImage = nullptr; - vk::raii::DeviceMemory depthImageMemory = nullptr; - vk::raii::ImageView depthImageView = nullptr; - - uint32_t mipLevels = 0; - vk::raii::Image textureImage = nullptr; - vk::raii::DeviceMemory textureImageMemory = nullptr; - vk::raii::ImageView textureImageView = nullptr; - vk::raii::Sampler textureSampler = nullptr; - - std::vector vertices; - std::vector indices; - vk::raii::Buffer vertexBuffer = nullptr; - vk::raii::DeviceMemory vertexBufferMemory = nullptr; - vk::raii::Buffer indexBuffer = nullptr; - vk::raii::DeviceMemory indexBufferMemory = nullptr; - - std::vector uniformBuffers; - std::vector uniformBuffersMemory; - std::vector uniformBuffersMapped; - - vk::raii::DescriptorPool descriptorPool = nullptr; - std::vector descriptorSets; - - vk::raii::CommandPool commandPool = nullptr; - std::vector commandBuffers; - - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; - std::vector inFlightFences; - uint32_t semaphoreIndex = 0; - uint32_t currentFrame = 0; - - bool framebufferResized = false; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); - } - - static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { - auto app = static_cast(glfwGetWindowUserPointer(window)); - app->framebufferResized = true; - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - createSurface(); - pickPhysicalDevice(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createDescriptorSetLayout(); - createGraphicsPipeline(); - createCommandPool(); - createDepthResources(); - createTextureImage(); - createTextureImageView(); - createTextureSampler(); - loadModel(); - createVertexBuffer(); - createIndexBuffer(); - createUniformBuffers(); - createDescriptorPool(); - createDescriptorSets(); - createCommandBuffers(); - createSyncObjects(); - } - - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - drawFrame(); - } - - device.waitIdle(); - } - - void cleanupSwapChain() { - swapChainImageViews.clear(); - swapChain = nullptr; - } - - void cleanup() const { - glfwDestroyWindow(window); - - glfwTerminate(); - } - - void recreateSwapChain() { - int width = 0, height = 0; - glfwGetFramebufferSize(window, &width, &height); - while (width == 0 || height == 0) { - glfwGetFramebufferSize(window, &width, &height); - glfwWaitEvents(); - } - - device.waitIdle(); - - cleanupSwapChain(); - createSwapChain(); - createImageViews(); - createDepthResources(); - } - - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations +class HelloTriangleApplication +{ + public: + void run() + { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + + private: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::Format swapChainImageFormat = vk::Format::eUndefined; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + + vk::raii::Image depthImage = nullptr; + vk::raii::DeviceMemory depthImageMemory = nullptr; + vk::raii::ImageView depthImageView = nullptr; + + uint32_t mipLevels = 0; + vk::raii::Image textureImage = nullptr; + vk::raii::DeviceMemory textureImageMemory = nullptr; + vk::raii::ImageView textureImageView = nullptr; + vk::raii::Sampler textureSampler = nullptr; + + std::vector vertices; + std::vector indices; + vk::raii::Buffer vertexBuffer = nullptr; + vk::raii::DeviceMemory vertexBufferMemory = nullptr; + vk::raii::Buffer indexBuffer = nullptr; + vk::raii::DeviceMemory indexBufferMemory = nullptr; + + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + std::vector uniformBuffersMapped; + + vk::raii::DescriptorPool descriptorPool = nullptr; + std::vector descriptorSets; + + vk::raii::CommandPool commandPool = nullptr; + std::vector commandBuffers; + + std::vector presentCompleteSemaphore; + std::vector renderFinishedSemaphore; + std::vector inFlightFences; + uint32_t semaphoreIndex = 0; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow *window, int width, int height) + { + auto app = static_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createDescriptorSetLayout(); + createGraphicsPipeline(); + createCommandPool(); + createDepthResources(); + createTextureImage(); + createTextureImageView(); + createTextureSampler(); + loadModel(); + createVertexBuffer(); + createIndexBuffer(); + createUniformBuffers(); + createDescriptorPool(); + createDescriptorSets(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() + { + while (!glfwWindowShouldClose(window)) + { + glfwPollEvents(); + drawFrame(); + } + + device.waitIdle(); + } + + void cleanupSwapChain() + { + swapChainImageViews.clear(); + swapChain = nullptr; + } + + void cleanup() const + { + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() + { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) + { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + device.waitIdle(); + + cleanupSwapChain(); + createSwapChain(); + createImageViews(); + createDepthResources(); + } + + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().features.samplerAnisotropy && - features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - }); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error("failed to find a suitable GPU!"); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain featureChain = { - {.features = {.samplerAnisotropy = true } }, // vk::PhysicalDeviceFeatures2 - {.synchronization2 = true, .dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface); - swapChainImageFormat = 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{ - .surface = surface, .minImageCount = minImageCount, - .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, - .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 }; - - swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); - swapChainImages = swapChain.getImages(); - } - - void createImageViews() { - 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( device, imageViewCreateInfo ); - } - } - - void createDescriptorSetLayout() { - std::array bindings = { - vk::DescriptorSetLayoutBinding( 0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr), - vk::DescriptorSetLayoutBinding( 1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment, nullptr) - }; - - vk::DescriptorSetLayoutCreateInfo layoutInfo{ .bindingCount = static_cast(bindings.size()), .pBindings = bindings.data() }; - descriptorSetLayout = vk::raii::DescriptorSetLayout(device, layoutInfo); - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; - - auto bindingDescription = Vertex::getBindingDescription(); - auto attributeDescriptions = Vertex::getAttributeDescriptions(); - vk::PipelineVertexInputStateCreateInfo vertexInputInfo{ - .vertexBindingDescriptionCount = 1, - .pVertexBindingDescriptions = &bindingDescription, - .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), - .pVertexAttributeDescriptions = attributeDescriptions.data() - }; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ - .topology = vk::PrimitiveTopology::eTriangleList, - .primitiveRestartEnable = vk::False - }; - vk::PipelineViewportStateCreateInfo viewportState{ - .viewportCount = 1, - .scissorCount = 1 - }; - vk::PipelineRasterizationStateCreateInfo rasterizer{ - .depthClampEnable = vk::False, - .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, - .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eCounterClockwise, - .depthBiasEnable = vk::False - }; - rasterizer.lineWidth = 1.0f; - vk::PipelineMultisampleStateCreateInfo multisampling{ - .rasterizationSamples = vk::SampleCountFlagBits::e1, - .sampleShadingEnable = vk::False - }; - vk::PipelineDepthStencilStateCreateInfo depthStencil{ - .depthTestEnable = vk::True, - .depthWriteEnable = vk::True, - .depthCompareOp = vk::CompareOp::eLess, - .depthBoundsTestEnable = vk::False, - .stencilTestEnable = vk::False - }; - vk::PipelineColorBlendAttachmentState colorBlendAttachment; - colorBlendAttachment.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA; - colorBlendAttachment.blendEnable = vk::False; - - vk::PipelineColorBlendStateCreateInfo colorBlending{ - .logicOpEnable = vk::False, - .logicOp = vk::LogicOp::eCopy, - .attachmentCount = 1, - .pAttachments = &colorBlendAttachment - }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0 }; - - pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); - - vk::Format depthFormat = findDepthFormat(); - vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ - .colorAttachmentCount = 1, - .pColorAttachmentFormats = &swapChainImageFormat, - .depthAttachmentFormat = depthFormat - }; - vk::GraphicsPipelineCreateInfo pipelineInfo{ .pNext = &pipelineRenderingCreateInfo, - .stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pDepthStencilState = &depthStencil, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = pipelineLayout, - .renderPass = nullptr - }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{ - .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex - }; - commandPool = vk::raii::CommandPool(device, poolInfo); - } - - void createDepthResources() { - vk::Format depthFormat = findDepthFormat(); - - createImage(swapChainExtent.width, swapChainExtent.height, 1, depthFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eDepthStencilAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, depthImage, depthImageMemory); - depthImageView = createImageView(depthImage, depthFormat, vk::ImageAspectFlagBits::eDepth, 1); - } - - vk::Format findSupportedFormat(const std::vector& candidates, vk::ImageTiling tiling, vk::FormatFeatureFlags features) const { - for (const auto format : candidates) { - vk::FormatProperties props = physicalDevice.getFormatProperties(format); - - if (tiling == vk::ImageTiling::eLinear && (props.linearTilingFeatures & features) == features) { - return format; - } - if (tiling == vk::ImageTiling::eOptimal && (props.optimalTilingFeatures & features) == features) { - return format; - } - } - - throw std::runtime_error("failed to find supported format!"); - } - - [[nodiscard]] vk::Format findDepthFormat() const { - return findSupportedFormat( - {vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, vk::Format::eD24UnormS8Uint}, - vk::ImageTiling::eOptimal, - vk::FormatFeatureFlagBits::eDepthStencilAttachment - ); - } - - static bool hasStencilComponent(vk::Format format) { - return format == vk::Format::eD32SfloatS8Uint || format == vk::Format::eD24UnormS8Uint; - } - - void createTextureImage() { - int texWidth, texHeight, texChannels; - stbi_uc* pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); - vk::DeviceSize imageSize = texWidth * texHeight * 4; - mipLevels = static_cast(std::floor(std::log2(std::max(texWidth, texHeight)))) + 1; - - if (!pixels) { - throw std::runtime_error("failed to load texture image!"); - } - - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, imageSize); - memcpy(data, pixels, imageSize); - stagingBufferMemory.unmapMemory(); - - stbi_image_free(pixels); - - createImage(texWidth, texHeight, mipLevels, vk::Format::eR8G8B8A8Srgb, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory); - - transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, mipLevels); - copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); - - generateMipmaps(textureImage, vk::Format::eR8G8B8A8Srgb, texWidth, texHeight, mipLevels); - } - - void generateMipmaps(vk::raii::Image& image, vk::Format imageFormat, int32_t texWidth, int32_t texHeight, uint32_t mipLevels) { - // Check if image format supports linear blit-ing - vk::FormatProperties formatProperties = physicalDevice.getFormatProperties(imageFormat); - - if (!(formatProperties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eSampledImageFilterLinear)) { - throw std::runtime_error("texture image format does not support linear blitting!"); - } - - std::unique_ptr commandBuffer = beginSingleTimeCommands(); - - vk::ImageMemoryBarrier barrier = { .srcAccessMask = vk::AccessFlagBits::eTransferWrite, .dstAccessMask =vk::AccessFlagBits::eTransferRead - , .oldLayout = vk::ImageLayout::eTransferDstOptimal, .newLayout = vk::ImageLayout::eTransferSrcOptimal - , .srcQueueFamilyIndex = vk::QueueFamilyIgnored, .dstQueueFamilyIndex = vk::QueueFamilyIgnored, .image = image }; - barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; - barrier.subresourceRange.baseArrayLayer = 0; - barrier.subresourceRange.layerCount = 1; - barrier.subresourceRange.levelCount = 1; - - int32_t mipWidth = texWidth; - int32_t mipHeight = texHeight; - - for (uint32_t i = 1; i < mipLevels; i++) { - barrier.subresourceRange.baseMipLevel = i - 1; - barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal; - barrier.newLayout = vk::ImageLayout::eTransferSrcOptimal; - barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; - barrier.dstAccessMask = vk::AccessFlagBits::eTransferRead; - - commandBuffer->pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer, {}, {}, {}, barrier); - - vk::ArrayWrapper1D offsets, dstOffsets; - offsets[0] = vk::Offset3D(0, 0, 0); - offsets[1] = vk::Offset3D(mipWidth, mipHeight, 1); - dstOffsets[0] = vk::Offset3D(0, 0, 0); - dstOffsets[1] = vk::Offset3D(mipWidth > 1 ? mipWidth / 2 : 1, mipHeight > 1 ? mipHeight / 2 : 1, 1); - vk::ImageBlit blit = { .srcSubresource = {}, .srcOffsets = offsets, - .dstSubresource = {}, .dstOffsets = dstOffsets }; - blit.srcSubresource = vk::ImageSubresourceLayers( vk::ImageAspectFlagBits::eColor, i - 1, 0, 1); - blit.dstSubresource = vk::ImageSubresourceLayers( vk::ImageAspectFlagBits::eColor, i, 0, 1); - - commandBuffer->blitImage(image, vk::ImageLayout::eTransferSrcOptimal, image, vk::ImageLayout::eTransferDstOptimal, { blit }, vk::Filter::eLinear); - - barrier.oldLayout = vk::ImageLayout::eTransferSrcOptimal; - barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal; - barrier.srcAccessMask = vk::AccessFlagBits::eTransferRead; - barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - - commandBuffer->pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {}, barrier); - - if (mipWidth > 1) mipWidth /= 2; - if (mipHeight > 1) mipHeight /= 2; - } - - barrier.subresourceRange.baseMipLevel = mipLevels - 1; - barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal; - barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal; - barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; - barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - - commandBuffer->pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {}, barrier); - - endSingleTimeCommands(*commandBuffer); - } - - void createTextureImageView() { - textureImageView = createImageView(textureImage, vk::Format::eR8G8B8A8Srgb, vk::ImageAspectFlagBits::eColor, mipLevels); - } - - void createTextureSampler() { - vk::PhysicalDeviceProperties properties = physicalDevice.getProperties(); - vk::SamplerCreateInfo samplerInfo { - .magFilter = vk::Filter::eLinear, - .minFilter = vk::Filter::eLinear, - .mipmapMode = vk::SamplerMipmapMode::eLinear, - .addressModeU = vk::SamplerAddressMode::eRepeat, - .addressModeV = vk::SamplerAddressMode::eRepeat, - .addressModeW = vk::SamplerAddressMode::eRepeat, - .mipLodBias = 0.0f, - .anisotropyEnable = vk::True, - .maxAnisotropy = properties.limits.maxSamplerAnisotropy, - .compareEnable = vk::False, - .compareOp = vk::CompareOp::eAlways - }; - textureSampler = vk::raii::Sampler(device, samplerInfo); - } - - [[nodiscard]] vk::raii::ImageView createImageView(const vk::raii::Image& image, vk::Format format, vk::ImageAspectFlags aspectFlags, uint32_t mipLevels) const { - vk::ImageViewCreateInfo viewInfo{ - .image = image, - .viewType = vk::ImageViewType::e2D, - .format = format, - .subresourceRange = { aspectFlags, 0, mipLevels, 0, 1 } - }; - return vk::raii::ImageView( device, viewInfo ); - } - - void createImage(uint32_t width, uint32_t height, uint32_t mipLevels, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image& image, vk::raii::DeviceMemory& imageMemory) { - vk::ImageCreateInfo imageInfo{ - .imageType = vk::ImageType::e2D, - .format = format, - .extent = {width, height, 1}, - .mipLevels = mipLevels, - .arrayLayers = 1, - .samples = vk::SampleCountFlagBits::e1, - .tiling = tiling, - .usage = usage, - .sharingMode = vk::SharingMode::eExclusive, - .initialLayout = vk::ImageLayout::eUndefined - }; - image = vk::raii::Image(device, imageInfo); - - vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ - .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) - }; - imageMemory = vk::raii::DeviceMemory(device, allocInfo); - image.bindMemory(imageMemory, 0); - } - - void transitionImageLayout(const vk::raii::Image& image, const vk::ImageLayout oldLayout, const vk::ImageLayout newLayout, uint32_t mipLevels) { - const auto commandBuffer = beginSingleTimeCommands(); - - vk::ImageMemoryBarrier barrier{ - .oldLayout = oldLayout, - .newLayout = newLayout, - .image = image, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, mipLevels, 0, 1 } - }; - - vk::PipelineStageFlags sourceStage; - vk::PipelineStageFlags destinationStage; - - if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) { - barrier.srcAccessMask = {}; - barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; - - sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; - destinationStage = vk::PipelineStageFlagBits::eTransfer; - } else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) { - barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; - barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - - sourceStage = vk::PipelineStageFlagBits::eTransfer; - destinationStage = vk::PipelineStageFlagBits::eFragmentShader; - } else { - throw std::invalid_argument("unsupported layout transition!"); - } - commandBuffer->pipelineBarrier( sourceStage, destinationStage, {}, {}, nullptr, barrier ); - endSingleTimeCommands(*commandBuffer); - } - - void copyBufferToImage(const vk::raii::Buffer& buffer, const vk::raii::Image& image, uint32_t width, uint32_t height) { - std::unique_ptr commandBuffer = beginSingleTimeCommands(); - vk::BufferImageCopy region{ - .bufferOffset = 0, - .bufferRowLength = 0, - .bufferImageHeight = 0, - .imageSubresource = { vk::ImageAspectFlagBits::eColor, 0, 0, 1 }, - .imageOffset = {0, 0, 0}, - .imageExtent = {width, height, 1} - }; - commandBuffer->copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, {region}); - endSingleTimeCommands(*commandBuffer); - } - - void loadModel() { - tinyobj::attrib_t attrib; - std::vector shapes; - std::vector materials; - std::string warn, err; - - if (!LoadObj(&attrib, &shapes, &materials, &warn, &err, MODEL_PATH.c_str())) { - throw std::runtime_error(warn + err); - } - - std::unordered_map uniqueVertices{}; - - for (const auto& shape : shapes) { - for (const auto& index : shape.mesh.indices) { - Vertex vertex{}; - - vertex.pos = { - attrib.vertices[3 * index.vertex_index + 0], - attrib.vertices[3 * index.vertex_index + 1], - attrib.vertices[3 * index.vertex_index + 2] - }; - - vertex.texCoord = { - attrib.texcoords[2 * index.texcoord_index + 0], - 1.0f - attrib.texcoords[2 * index.texcoord_index + 1] - }; - - vertex.color = {1.0f, 1.0f, 1.0f}; - - if (!uniqueVertices.contains(vertex)) { - uniqueVertices[vertex] = static_cast(vertices.size()); - vertices.push_back(vertex); - } - - indices.push_back(uniqueVertices[vertex]); - } - } - } - - void createVertexBuffer() { - vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(dataStaging, vertices.data(), bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); - - copyBuffer(stagingBuffer, vertexBuffer, bufferSize); - } - - void createIndexBuffer() { - vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); - - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(data, indices.data(), bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); - - copyBuffer(stagingBuffer, indexBuffer, bufferSize); - } - - void createUniformBuffers() { - uniformBuffers.clear(); - uniformBuffersMemory.clear(); - uniformBuffersMapped.clear(); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DeviceSize bufferSize = sizeof(UniformBufferObject); - vk::raii::Buffer buffer({}); - vk::raii::DeviceMemory bufferMem({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); - uniformBuffers.emplace_back(std::move(buffer)); - uniformBuffersMemory.emplace_back(std::move(bufferMem)); - uniformBuffersMapped.emplace_back( uniformBuffersMemory[i].mapMemory(0, bufferSize)); - } - } - - void createDescriptorPool() { - std::array poolSize { - vk::DescriptorPoolSize( vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT), - vk::DescriptorPoolSize( vk::DescriptorType::eCombinedImageSampler, MAX_FRAMES_IN_FLIGHT) - }; - vk::DescriptorPoolCreateInfo poolInfo{ - .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, - .maxSets = MAX_FRAMES_IN_FLIGHT, - .poolSizeCount = static_cast(poolSize.size()), - .pPoolSizes = poolSize.data() - }; - descriptorPool = vk::raii::DescriptorPool(device, poolInfo); - } - - void createDescriptorSets() { - std::vector layouts(MAX_FRAMES_IN_FLIGHT, descriptorSetLayout); - vk::DescriptorSetAllocateInfo allocInfo{ - .descriptorPool = descriptorPool, - .descriptorSetCount = static_cast(layouts.size()), - .pSetLayouts = layouts.data() - }; - - descriptorSets.clear(); - descriptorSets = device.allocateDescriptorSets(allocInfo); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DescriptorBufferInfo bufferInfo{ - .buffer = uniformBuffers[i], - .offset = 0, - .range = sizeof(UniformBufferObject) - }; - vk::DescriptorImageInfo imageInfo{ - .sampler = textureSampler, - .imageView = textureImageView, - .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal - }; - std::array descriptorWrites{ - vk::WriteDescriptorSet{ - .dstSet = descriptorSets[i], - .dstBinding = 0, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eUniformBuffer, - .pBufferInfo = &bufferInfo - }, - vk::WriteDescriptorSet{ - .dstSet = descriptorSets[i], - .dstBinding = 1, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eCombinedImageSampler, - .pImageInfo = &imageInfo - } - }; - device.updateDescriptorSets(descriptorWrites, {}); - } - } - - void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer& buffer, vk::raii::DeviceMemory& bufferMemory) { - vk::BufferCreateInfo bufferInfo{ - .size = size, - .usage = usage, - .sharingMode = vk::SharingMode::eExclusive - }; - buffer = vk::raii::Buffer(device, bufferInfo); - vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ - .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) - }; - bufferMemory = vk::raii::DeviceMemory(device, allocInfo); - buffer.bindMemory(bufferMemory, 0); - } - - std::unique_ptr beginSingleTimeCommands() { - vk::CommandBufferAllocateInfo allocInfo{ - .commandPool = commandPool, - .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = 1 - }; - std::unique_ptr commandBuffer = std::make_unique(std::move(vk::raii::CommandBuffers(device, allocInfo).front())); - - vk::CommandBufferBeginInfo beginInfo{ - .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit - }; - commandBuffer->begin(beginInfo); - - return commandBuffer; - } - - void endSingleTimeCommands(const vk::raii::CommandBuffer& commandBuffer) const { - commandBuffer.end(); - - vk::SubmitInfo submitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandBuffer }; - queue.submit(submitInfo, nullptr); - queue.waitIdle(); - } - - void copyBuffer(vk::raii::Buffer & srcBuffer, vk::raii::Buffer & dstBuffer, vk::DeviceSize size) { - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1 }; - vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); - commandCopyBuffer.begin(vk::CommandBufferBeginInfo{ .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit }); - commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy{ .size = size }); - commandCopyBuffer.end(); - queue.submit(vk::SubmitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer }, nullptr); - queue.waitIdle(); - } - - uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) { - vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); - - for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { - if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { - return i; - } - } - - throw std::runtime_error("failed to find suitable memory type!"); - } - - void createCommandBuffers() { - commandBuffers.clear(); - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = MAX_FRAMES_IN_FLIGHT }; - commandBuffers = vk::raii::CommandBuffers(device, allocInfo); - } - - void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin({}); - // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL - transition_image_layout( - imageIndex, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage - ); - // Transition depth image to depth attachment optimal layout - vk::ImageMemoryBarrier2 depthBarrier = { - .srcStageMask = vk::PipelineStageFlagBits2::eTopOfPipe, - .srcAccessMask = {}, - .dstStageMask = vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, - .dstAccessMask = vk::AccessFlagBits2::eDepthStencilAttachmentRead | vk::AccessFlagBits2::eDepthStencilAttachmentWrite, - .oldLayout = vk::ImageLayout::eUndefined, - .newLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = depthImage, - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eDepth, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo depthDependencyInfo = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &depthBarrier - }; - commandBuffers[currentFrame].pipelineBarrier2(depthDependencyInfo); - - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::ClearValue clearDepth = vk::ClearDepthStencilValue(1.0f, 0); - - vk::RenderingAttachmentInfo colorAttachmentInfo = { - .imageView = swapChainImageViews[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor - }; - - vk::RenderingAttachmentInfo depthAttachmentInfo = { - .imageView = depthImageView, - .imageLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eDontCare, - .clearValue = clearDepth - }; - - vk::RenderingInfo renderingInfo = { - .renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &colorAttachmentInfo, - .pDepthAttachment = &depthAttachmentInfo - }; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].bindIndexBuffer( *indexBuffer, 0, vk::IndexType::eUint32 ); - commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); - commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); - commandBuffers[currentFrame].endRendering(); - // After rendering, transition the swapchain image to PRESENT_SRC - transition_image_layout( - imageIndex, - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage - ); - commandBuffers[currentFrame].end(); - } - - void transition_image_layout( - uint32_t imageIndex, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::AccessFlags2 src_access_mask, - vk::AccessFlags2 dst_access_mask, - vk::PipelineStageFlags2 src_stage_mask, - vk::PipelineStageFlags2 dst_stage_mask - ) { - vk::ImageMemoryBarrier2 barrier = { - .srcStageMask = src_stage_mask, - .srcAccessMask = src_access_mask, - .dstStageMask = dst_stage_mask, - .dstAccessMask = dst_access_mask, - .oldLayout = old_layout, - .newLayout = new_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); - } - - void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); - - for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - } - - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - inFlightFences.emplace_back(device, vk::FenceCreateInfo{ .flags = vk::FenceCreateFlagBits::eSignaled }); - } - } - - void updateUniformBuffer(uint32_t currentImage) const { - static auto startTime = std::chrono::high_resolution_clock::now(); - - auto currentTime = std::chrono::high_resolution_clock::now(); - float time = std::chrono::duration(currentTime - startTime).count(); - - UniformBufferObject ubo{}; - ubo.model = rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.view = lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); - ubo.proj[1][1] *= -1; - - memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); - } - - void drawFrame() { - while ( vk::Result::eTimeout == device.waitForFences( *inFlightFences[currentFrame], vk::True, UINT64_MAX ) ) - ; - auto [result, imageIndex] = swapChain.acquireNextImage( UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr ); - - if (result == vk::Result::eErrorOutOfDateKHR) { - recreateSwapChain(); - return; - } - if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) { - throw std::runtime_error("failed to acquire swap chain image!"); - } - updateUniformBuffer(currentFrame); - - device.resetFences( *inFlightFences[currentFrame] ); - commandBuffers[currentFrame].reset(); - recordCommandBuffer(imageIndex); - - vk::PipelineStageFlags waitDestinationStageMask( vk::PipelineStageFlagBits::eColorAttachmentOutput ); - const vk::SubmitInfo submitInfo{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], - .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], - .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex] }; - queue.submit(submitInfo, *inFlightFences[currentFrame]); - - - const vk::PresentInfoKHR presentInfoKHR{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], - .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex }; - result = queue.presentKHR(presentInfoKHR); - if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { - framebufferResized = false; - recreateSwapChain(); - } else if (result != vk::Result::eSuccess) { - throw std::runtime_error("failed to present swap chain image!"); - } - semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; - } - - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size(), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static vk::Format chooseSwapSurfaceFormat(const std::vector& availableFormats) { - const auto formatIt = std::ranges::find_if(availableFormats, - [](const auto& format) { - return format.format == vk::Format::eB8G8R8A8Srgb && - format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; - }); - return formatIt != availableFormats.end() ? formatIt->format : availableFormats[0].format; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eFifo; - } - - [[nodiscard]] vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) const { - 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) - }; - } - - [[nodiscard]] std::vector getRequiredExtensions() const { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - 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; - } + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().features.samplerAnisotropy && + features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain featureChain = { + {.features = {.samplerAnisotropy = true}}, // vk::PhysicalDeviceFeatures2 + {.synchronization2 = true, .dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface); + swapChainImageFormat = 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{ + .surface = surface, .minImageCount = minImageCount, .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + void createImageViews() + { + 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(device, imageViewCreateInfo); + } + } + + void createDescriptorSetLayout() + { + std::array bindings = { + vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr), + vk::DescriptorSetLayoutBinding(1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment, nullptr)}; + + vk::DescriptorSetLayoutCreateInfo layoutInfo{.bindingCount = static_cast(bindings.size()), .pBindings = bindings.data()}; + descriptorSetLayout = vk::raii::DescriptorSetLayout(device, layoutInfo); + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; + + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{ + .vertexBindingDescriptionCount = 1, + .pVertexBindingDescriptions = &bindingDescription, + .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), + .pVertexAttributeDescriptions = attributeDescriptions.data()}; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ + .topology = vk::PrimitiveTopology::eTriangleList, + .primitiveRestartEnable = vk::False}; + vk::PipelineViewportStateCreateInfo viewportState{ + .viewportCount = 1, + .scissorCount = 1}; + vk::PipelineRasterizationStateCreateInfo rasterizer{ + .depthClampEnable = vk::False, + .rasterizerDiscardEnable = vk::False, + .polygonMode = vk::PolygonMode::eFill, + .cullMode = vk::CullModeFlagBits::eBack, + .frontFace = vk::FrontFace::eCounterClockwise, + .depthBiasEnable = vk::False}; + rasterizer.lineWidth = 1.0f; + vk::PipelineMultisampleStateCreateInfo multisampling{ + .rasterizationSamples = vk::SampleCountFlagBits::e1, + .sampleShadingEnable = vk::False}; + vk::PipelineDepthStencilStateCreateInfo depthStencil{ + .depthTestEnable = vk::True, + .depthWriteEnable = vk::True, + .depthCompareOp = vk::CompareOp::eLess, + .depthBoundsTestEnable = vk::False, + .stencilTestEnable = vk::False}; + vk::PipelineColorBlendAttachmentState colorBlendAttachment; + colorBlendAttachment.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA; + colorBlendAttachment.blendEnable = vk::False; + + vk::PipelineColorBlendStateCreateInfo colorBlending{ + .logicOpEnable = vk::False, + .logicOp = vk::LogicOp::eCopy, + .attachmentCount = 1, + .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0}; + + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::Format depthFormat = findDepthFormat(); + vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ + .colorAttachmentCount = 1, + .pColorAttachmentFormats = &swapChainImageFormat, + .depthAttachmentFormat = depthFormat}; + vk::GraphicsPipelineCreateInfo pipelineInfo{.pNext = &pipelineRenderingCreateInfo, + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pDepthStencilState = &depthStencil, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{ + .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex}; + commandPool = vk::raii::CommandPool(device, poolInfo); + } + + void createDepthResources() + { + vk::Format depthFormat = findDepthFormat(); + + createImage(swapChainExtent.width, swapChainExtent.height, 1, depthFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eDepthStencilAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, depthImage, depthImageMemory); + depthImageView = createImageView(depthImage, depthFormat, vk::ImageAspectFlagBits::eDepth, 1); + } + + vk::Format findSupportedFormat(const std::vector &candidates, vk::ImageTiling tiling, vk::FormatFeatureFlags features) const + { + for (const auto format : candidates) + { + vk::FormatProperties props = physicalDevice.getFormatProperties(format); + + if (tiling == vk::ImageTiling::eLinear && (props.linearTilingFeatures & features) == features) + { + return format; + } + if (tiling == vk::ImageTiling::eOptimal && (props.optimalTilingFeatures & features) == features) + { + return format; + } + } + + throw std::runtime_error("failed to find supported format!"); + } + + [[nodiscard]] vk::Format findDepthFormat() const + { + return findSupportedFormat( + {vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, vk::Format::eD24UnormS8Uint}, + vk::ImageTiling::eOptimal, + vk::FormatFeatureFlagBits::eDepthStencilAttachment); + } + + static bool hasStencilComponent(vk::Format format) + { + return format == vk::Format::eD32SfloatS8Uint || format == vk::Format::eD24UnormS8Uint; + } + + void createTextureImage() + { + int texWidth, texHeight, texChannels; + stbi_uc *pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + vk::DeviceSize imageSize = texWidth * texHeight * 4; + mipLevels = static_cast(std::floor(std::log2(std::max(texWidth, texHeight)))) + 1; + + if (!pixels) + { + throw std::runtime_error("failed to load texture image!"); + } + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, imageSize); + memcpy(data, pixels, imageSize); + stagingBufferMemory.unmapMemory(); + + stbi_image_free(pixels); + + createImage(texWidth, texHeight, mipLevels, vk::Format::eR8G8B8A8Srgb, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory); + + transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, mipLevels); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); + + generateMipmaps(textureImage, vk::Format::eR8G8B8A8Srgb, texWidth, texHeight, mipLevels); + } + + void generateMipmaps(vk::raii::Image &image, vk::Format imageFormat, int32_t texWidth, int32_t texHeight, uint32_t mipLevels) + { + // Check if image format supports linear blit-ing + vk::FormatProperties formatProperties = physicalDevice.getFormatProperties(imageFormat); + + if (!(formatProperties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eSampledImageFilterLinear)) + { + throw std::runtime_error("texture image format does not support linear blitting!"); + } + + std::unique_ptr commandBuffer = beginSingleTimeCommands(); + + vk::ImageMemoryBarrier barrier = {.srcAccessMask = vk::AccessFlagBits::eTransferWrite, .dstAccessMask = vk::AccessFlagBits::eTransferRead, .oldLayout = vk::ImageLayout::eTransferDstOptimal, .newLayout = vk::ImageLayout::eTransferSrcOptimal, .srcQueueFamilyIndex = vk::QueueFamilyIgnored, .dstQueueFamilyIndex = vk::QueueFamilyIgnored, .image = image}; + barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + barrier.subresourceRange.levelCount = 1; + + int32_t mipWidth = texWidth; + int32_t mipHeight = texHeight; + + for (uint32_t i = 1; i < mipLevels; i++) + { + barrier.subresourceRange.baseMipLevel = i - 1; + barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal; + barrier.newLayout = vk::ImageLayout::eTransferSrcOptimal; + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eTransferRead; + + commandBuffer->pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer, {}, {}, {}, barrier); + + vk::ArrayWrapper1D offsets, dstOffsets; + offsets[0] = vk::Offset3D(0, 0, 0); + offsets[1] = vk::Offset3D(mipWidth, mipHeight, 1); + dstOffsets[0] = vk::Offset3D(0, 0, 0); + dstOffsets[1] = vk::Offset3D(mipWidth > 1 ? mipWidth / 2 : 1, mipHeight > 1 ? mipHeight / 2 : 1, 1); + vk::ImageBlit blit = {.srcSubresource = {}, .srcOffsets = offsets, .dstSubresource = {}, .dstOffsets = dstOffsets}; + blit.srcSubresource = vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, i - 1, 0, 1); + blit.dstSubresource = vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, i, 0, 1); + + commandBuffer->blitImage(image, vk::ImageLayout::eTransferSrcOptimal, image, vk::ImageLayout::eTransferDstOptimal, {blit}, vk::Filter::eLinear); + + barrier.oldLayout = vk::ImageLayout::eTransferSrcOptimal; + barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal; + barrier.srcAccessMask = vk::AccessFlagBits::eTransferRead; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + + commandBuffer->pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {}, barrier); + + if (mipWidth > 1) + mipWidth /= 2; + if (mipHeight > 1) + mipHeight /= 2; + } + + barrier.subresourceRange.baseMipLevel = mipLevels - 1; + barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal; + barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal; + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + + commandBuffer->pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {}, barrier); + + endSingleTimeCommands(*commandBuffer); + } + + void createTextureImageView() + { + textureImageView = createImageView(textureImage, vk::Format::eR8G8B8A8Srgb, vk::ImageAspectFlagBits::eColor, mipLevels); + } + + void createTextureSampler() + { + vk::PhysicalDeviceProperties properties = physicalDevice.getProperties(); + vk::SamplerCreateInfo samplerInfo{ + .magFilter = vk::Filter::eLinear, + .minFilter = vk::Filter::eLinear, + .mipmapMode = vk::SamplerMipmapMode::eLinear, + .addressModeU = vk::SamplerAddressMode::eRepeat, + .addressModeV = vk::SamplerAddressMode::eRepeat, + .addressModeW = vk::SamplerAddressMode::eRepeat, + .mipLodBias = 0.0f, + .anisotropyEnable = vk::True, + .maxAnisotropy = properties.limits.maxSamplerAnisotropy, + .compareEnable = vk::False, + .compareOp = vk::CompareOp::eAlways}; + textureSampler = vk::raii::Sampler(device, samplerInfo); + } + + [[nodiscard]] vk::raii::ImageView createImageView(const vk::raii::Image &image, vk::Format format, vk::ImageAspectFlags aspectFlags, uint32_t mipLevels) const + { + vk::ImageViewCreateInfo viewInfo{ + .image = image, + .viewType = vk::ImageViewType::e2D, + .format = format, + .subresourceRange = {aspectFlags, 0, mipLevels, 0, 1}}; + return vk::raii::ImageView(device, viewInfo); + } + + void createImage(uint32_t width, uint32_t height, uint32_t mipLevels, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image &image, vk::raii::DeviceMemory &imageMemory) + { + vk::ImageCreateInfo imageInfo{ + .imageType = vk::ImageType::e2D, + .format = format, + .extent = {width, height, 1}, + .mipLevels = mipLevels, + .arrayLayers = 1, + .samples = vk::SampleCountFlagBits::e1, + .tiling = tiling, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive, + .initialLayout = vk::ImageLayout::eUndefined}; + image = vk::raii::Image(device, imageInfo); + + vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{ + .allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + imageMemory = vk::raii::DeviceMemory(device, allocInfo); + image.bindMemory(imageMemory, 0); + } + + void transitionImageLayout(const vk::raii::Image &image, const vk::ImageLayout oldLayout, const vk::ImageLayout newLayout, uint32_t mipLevels) + { + const auto commandBuffer = beginSingleTimeCommands(); + + vk::ImageMemoryBarrier barrier{ + .oldLayout = oldLayout, + .newLayout = newLayout, + .image = image, + .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, mipLevels, 0, 1}}; + + vk::PipelineStageFlags sourceStage; + vk::PipelineStageFlags destinationStage; + + if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) + { + barrier.srcAccessMask = {}; + barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; + + sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; + destinationStage = vk::PipelineStageFlagBits::eTransfer; + } + else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) + { + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + + sourceStage = vk::PipelineStageFlagBits::eTransfer; + destinationStage = vk::PipelineStageFlagBits::eFragmentShader; + } + else + { + throw std::invalid_argument("unsupported layout transition!"); + } + commandBuffer->pipelineBarrier(sourceStage, destinationStage, {}, {}, nullptr, barrier); + endSingleTimeCommands(*commandBuffer); + } + + void copyBufferToImage(const vk::raii::Buffer &buffer, const vk::raii::Image &image, uint32_t width, uint32_t height) + { + std::unique_ptr commandBuffer = beginSingleTimeCommands(); + vk::BufferImageCopy region{ + .bufferOffset = 0, + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource = {vk::ImageAspectFlagBits::eColor, 0, 0, 1}, + .imageOffset = {0, 0, 0}, + .imageExtent = {width, height, 1}}; + commandBuffer->copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, {region}); + endSingleTimeCommands(*commandBuffer); + } + + void loadModel() + { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn, err; + + if (!LoadObj(&attrib, &shapes, &materials, &warn, &err, MODEL_PATH.c_str())) + { + throw std::runtime_error(warn + err); + } + + std::unordered_map uniqueVertices{}; + + for (const auto &shape : shapes) + { + for (const auto &index : shape.mesh.indices) + { + Vertex vertex{}; + + vertex.pos = { + attrib.vertices[3 * index.vertex_index + 0], + attrib.vertices[3 * index.vertex_index + 1], + attrib.vertices[3 * index.vertex_index + 2]}; + + vertex.texCoord = { + attrib.texcoords[2 * index.texcoord_index + 0], + 1.0f - attrib.texcoords[2 * index.texcoord_index + 1]}; + + vertex.color = {1.0f, 1.0f, 1.0f}; + + if (!uniqueVertices.contains(vertex)) + { + uniqueVertices[vertex] = static_cast(vertices.size()); + vertices.push_back(vertex); + } + + indices.push_back(uniqueVertices[vertex]); + } + } + } + + void createVertexBuffer() + { + vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(dataStaging, vertices.data(), bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + } + + void createIndexBuffer() + { + vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(data, indices.data(), bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); + + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + } + + void createUniformBuffers() + { + uniformBuffers.clear(); + uniformBuffersMemory.clear(); + uniformBuffersMapped.clear(); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DeviceSize bufferSize = sizeof(UniformBufferObject); + vk::raii::Buffer buffer({}); + vk::raii::DeviceMemory bufferMem({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); + uniformBuffers.emplace_back(std::move(buffer)); + uniformBuffersMemory.emplace_back(std::move(bufferMem)); + uniformBuffersMapped.emplace_back(uniformBuffersMemory[i].mapMemory(0, bufferSize)); + } + } + + void createDescriptorPool() + { + std::array poolSize{ + vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT), + vk::DescriptorPoolSize(vk::DescriptorType::eCombinedImageSampler, MAX_FRAMES_IN_FLIGHT)}; + vk::DescriptorPoolCreateInfo poolInfo{ + .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, + .maxSets = MAX_FRAMES_IN_FLIGHT, + .poolSizeCount = static_cast(poolSize.size()), + .pPoolSizes = poolSize.data()}; + descriptorPool = vk::raii::DescriptorPool(device, poolInfo); + } + + void createDescriptorSets() + { + std::vector layouts(MAX_FRAMES_IN_FLIGHT, descriptorSetLayout); + vk::DescriptorSetAllocateInfo allocInfo{ + .descriptorPool = descriptorPool, + .descriptorSetCount = static_cast(layouts.size()), + .pSetLayouts = layouts.data()}; + + descriptorSets.clear(); + descriptorSets = device.allocateDescriptorSets(allocInfo); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DescriptorBufferInfo bufferInfo{ + .buffer = uniformBuffers[i], + .offset = 0, + .range = sizeof(UniformBufferObject)}; + vk::DescriptorImageInfo imageInfo{ + .sampler = textureSampler, + .imageView = textureImageView, + .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal}; + std::array descriptorWrites{ + vk::WriteDescriptorSet{ + .dstSet = descriptorSets[i], + .dstBinding = 0, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eUniformBuffer, + .pBufferInfo = &bufferInfo}, + vk::WriteDescriptorSet{ + .dstSet = descriptorSets[i], + .dstBinding = 1, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .pImageInfo = &imageInfo}}; + device.updateDescriptorSets(descriptorWrites, {}); + } + } + + void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer &buffer, vk::raii::DeviceMemory &bufferMemory) + { + vk::BufferCreateInfo bufferInfo{ + .size = size, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive}; + buffer = vk::raii::Buffer(device, bufferInfo); + vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{ + .allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + bufferMemory = vk::raii::DeviceMemory(device, allocInfo); + buffer.bindMemory(bufferMemory, 0); + } + + std::unique_ptr beginSingleTimeCommands() + { + vk::CommandBufferAllocateInfo allocInfo{ + .commandPool = commandPool, + .level = vk::CommandBufferLevel::ePrimary, + .commandBufferCount = 1}; + std::unique_ptr commandBuffer = std::make_unique(std::move(vk::raii::CommandBuffers(device, allocInfo).front())); + + vk::CommandBufferBeginInfo beginInfo{ + .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; + commandBuffer->begin(beginInfo); + + return commandBuffer; + } + + void endSingleTimeCommands(const vk::raii::CommandBuffer &commandBuffer) const + { + commandBuffer.end(); + + vk::SubmitInfo submitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandBuffer}; + queue.submit(submitInfo, nullptr); + queue.waitIdle(); + } + + void copyBuffer(vk::raii::Buffer &srcBuffer, vk::raii::Buffer &dstBuffer, vk::DeviceSize size) + { + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1}; + vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); + commandCopyBuffer.begin(vk::CommandBufferBeginInfo{.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); + commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy{.size = size}); + commandCopyBuffer.end(); + queue.submit(vk::SubmitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer}, nullptr); + queue.waitIdle(); + } + + uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) + { + vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) + { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) + { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + void createCommandBuffers() + { + commandBuffers.clear(); + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = MAX_FRAMES_IN_FLIGHT}; + commandBuffers = vk::raii::CommandBuffers(device, allocInfo); + } + + void recordCommandBuffer(uint32_t imageIndex) + { + commandBuffers[currentFrame].begin({}); + // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL + transition_image_layout( + imageIndex, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage + ); + // Transition depth image to depth attachment optimal layout + vk::ImageMemoryBarrier2 depthBarrier = { + .srcStageMask = vk::PipelineStageFlagBits2::eTopOfPipe, + .srcAccessMask = {}, + .dstStageMask = vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, + .dstAccessMask = vk::AccessFlagBits2::eDepthStencilAttachmentRead | vk::AccessFlagBits2::eDepthStencilAttachmentWrite, + .oldLayout = vk::ImageLayout::eUndefined, + .newLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = depthImage, + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eDepth, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo depthDependencyInfo = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &depthBarrier}; + commandBuffers[currentFrame].pipelineBarrier2(depthDependencyInfo); + + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::ClearValue clearDepth = vk::ClearDepthStencilValue(1.0f, 0); + + vk::RenderingAttachmentInfo colorAttachmentInfo = { + .imageView = swapChainImageViews[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor}; + + vk::RenderingAttachmentInfo depthAttachmentInfo = { + .imageView = depthImageView, + .imageLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eDontCare, + .clearValue = clearDepth}; + + vk::RenderingInfo renderingInfo = { + .renderArea = {.offset = {0, 0}, .extent = swapChainExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &colorAttachmentInfo, + .pDepthAttachment = &depthAttachmentInfo}; + commandBuffers[currentFrame].beginRendering(renderingInfo); + commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint32); + commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); + commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); + commandBuffers[currentFrame].endRendering(); + // After rendering, transition the swapchain image to PRESENT_SRC + transition_image_layout( + imageIndex, + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage + ); + commandBuffers[currentFrame].end(); + } + + void transition_image_layout( + uint32_t imageIndex, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout, + vk::AccessFlags2 src_access_mask, + vk::AccessFlags2 dst_access_mask, + vk::PipelineStageFlags2 src_stage_mask, + vk::PipelineStageFlags2 dst_stage_mask) + { + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = src_stage_mask, + .srcAccessMask = src_access_mask, + .dstStageMask = dst_stage_mask, + .dstAccessMask = dst_access_mask, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo dependency_info = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + } + + void createSyncObjects() + { + presentCompleteSemaphore.clear(); + renderFinishedSemaphore.clear(); + inFlightFences.clear(); + + for (size_t i = 0; i < swapChainImages.size(); i++) + { + presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); + } + } + + void updateUniformBuffer(uint32_t currentImage) const + { + static auto startTime = std::chrono::high_resolution_clock::now(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + UniformBufferObject ubo{}; + ubo.model = rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.view = lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); + ubo.proj[1][1] *= -1; + + memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); + } + + void drawFrame() + { + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + ; + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr); + + if (result == vk::Result::eErrorOutOfDateKHR) + { + recreateSwapChain(); + return; + } + if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) + { + throw std::runtime_error("failed to acquire swap chain image!"); + } + updateUniformBuffer(currentFrame); + + device.resetFences(*inFlightFences[currentFrame]); + commandBuffers[currentFrame].reset(); + recordCommandBuffer(imageIndex); + + vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[currentFrame]); + + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + result = queue.presentKHR(presentInfoKHR); + if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) + { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != vk::Result::eSuccess) + { + throw std::runtime_error("failed to present swap chain image!"); + } + semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size(), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static vk::Format chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + const auto formatIt = std::ranges::find_if(availableFormats, + [](const auto &format) { + return format.format == vk::Format::eB8G8R8A8Srgb && + format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; + }); + return formatIt != availableFormats.end() ? formatIt->format : availableFormats[0].format; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + vk::PresentModeKHR::eFifo; + } + + [[nodiscard]] vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR &capabilities) const + { + 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)}; + } + + [[nodiscard]] std::vector getRequiredExtensions() const + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + 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; + } }; -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/30_multisampling.cpp b/attachments/30_multisampling.cpp index 99b48cda..6cd082e9 100644 --- a/attachments/30_multisampling.cpp +++ b/attachments/30_multisampling.cpp @@ -1,24 +1,24 @@ -#include -#include -#include -#include -#include -#include -#include #include -#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include #define GLM_FORCE_RADIANS @@ -34,16 +34,15 @@ import vulkan_hpp; #define TINYOBJLOADER_IMPLEMENTATION #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -constexpr uint64_t FenceTimeout = 100000000; -const std::string MODEL_PATH = "models/viking_room.obj"; -const std::string TEXTURE_PATH = "textures/viking_room.png"; -constexpr int MAX_FRAMES_IN_FLIGHT = 2; +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; +constexpr uint64_t FenceTimeout = 100000000; +const std::string MODEL_PATH = "models/viking_room.obj"; +const std::string TEXTURE_PATH = "textures/viking_room.png"; +constexpr int MAX_FRAMES_IN_FLIGHT = 2; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -51,1256 +50,1312 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -struct Vertex { - glm::vec3 pos; - glm::vec3 color; - glm::vec2 texCoord; - - static vk::VertexInputBindingDescription getBindingDescription() { - return { 0, sizeof(Vertex), vk::VertexInputRate::eVertex }; - } - - static std::array getAttributeDescriptions() { - return { - vk::VertexInputAttributeDescription( 0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, pos) ), - vk::VertexInputAttributeDescription( 1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color) ), - vk::VertexInputAttributeDescription( 2, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, texCoord) ) - }; - } - - bool operator==(const Vertex& other) const { - return pos == other.pos && color == other.color && texCoord == other.texCoord; - } +struct Vertex +{ + glm::vec3 pos; + glm::vec3 color; + glm::vec2 texCoord; + + static vk::VertexInputBindingDescription getBindingDescription() + { + return {0, sizeof(Vertex), vk::VertexInputRate::eVertex}; + } + + static std::array getAttributeDescriptions() + { + return { + vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, pos)), + vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color)), + vk::VertexInputAttributeDescription(2, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, texCoord))}; + } + + bool operator==(const Vertex &other) const + { + return pos == other.pos && color == other.color && texCoord == other.texCoord; + } }; -template<> struct std::hash { - size_t operator()(Vertex const& vertex) const noexcept { - return ((hash()(vertex.pos) ^ (hash()(vertex.color) << 1)) >> 1) ^ (hash()(vertex.texCoord) << 1); - } - }; +template <> +struct std::hash +{ + size_t operator()(Vertex const &vertex) const noexcept + { + return ((hash()(vertex.pos) ^ (hash()(vertex.color) << 1)) >> 1) ^ (hash()(vertex.texCoord) << 1); + } +}; -struct UniformBufferObject { - alignas(16) glm::mat4 model; - alignas(16) glm::mat4 view; - alignas(16) glm::mat4 proj; +struct UniformBufferObject +{ + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; }; -class HelloTriangleApplication { -public: - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } - -private: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::SampleCountFlagBits msaaSamples = vk::SampleCountFlagBits::e1; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::Format swapChainImageFormat = vk::Format::eUndefined; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - - vk::raii::Image colorImage = nullptr; - vk::raii::DeviceMemory colorImageMemory = nullptr; - vk::raii::ImageView colorImageView = nullptr; - - vk::raii::Image depthImage = nullptr; - vk::raii::DeviceMemory depthImageMemory = nullptr; - vk::raii::ImageView depthImageView = nullptr; - - uint32_t mipLevels = 0; - vk::raii::Image textureImage = nullptr; - vk::raii::DeviceMemory textureImageMemory = nullptr; - vk::raii::ImageView textureImageView = nullptr; - vk::raii::Sampler textureSampler = nullptr; - - std::vector vertices; - std::vector indices; - vk::raii::Buffer vertexBuffer = nullptr; - vk::raii::DeviceMemory vertexBufferMemory = nullptr; - vk::raii::Buffer indexBuffer = nullptr; - vk::raii::DeviceMemory indexBufferMemory = nullptr; - - std::vector uniformBuffers; - std::vector uniformBuffersMemory; - std::vector uniformBuffersMapped; - - vk::raii::DescriptorPool descriptorPool = nullptr; - std::vector descriptorSets; - - vk::raii::CommandPool commandPool = nullptr; - std::vector commandBuffers; - - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; - std::vector inFlightFences; - uint32_t semaphoreIndex = 0; - uint32_t currentFrame = 0; - - bool framebufferResized = false; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); - } - - static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { - auto app = static_cast(glfwGetWindowUserPointer(window)); - app->framebufferResized = true; - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - createSurface(); - pickPhysicalDevice(); - msaaSamples = getMaxUsableSampleCount(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createDescriptorSetLayout(); - createGraphicsPipeline(); - createCommandPool(); - createColorResources(); - createDepthResources(); - createTextureImage(); - createTextureImageView(); - createTextureSampler(); - loadModel(); - createVertexBuffer(); - createIndexBuffer(); - createUniformBuffers(); - createDescriptorPool(); - createDescriptorSets(); - createCommandBuffers(); - createSyncObjects(); - } - - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - drawFrame(); - } - - device.waitIdle(); - } - - void cleanupSwapChain() { - swapChainImageViews.clear(); - swapChain = nullptr; - } - - void cleanup() const { - glfwDestroyWindow(window); - - glfwTerminate(); - } - - void recreateSwapChain() { - int width = 0, height = 0; - glfwGetFramebufferSize(window, &width, &height); - while (width == 0 || height == 0) { - glfwGetFramebufferSize(window, &width, &height); - glfwWaitEvents(); - } - - device.waitIdle(); - - cleanupSwapChain(); - createSwapChain(); - createImageViews(); - createColorResources(); - createDepthResources(); - } - - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations +class HelloTriangleApplication +{ + public: + void run() + { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + + private: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::SampleCountFlagBits msaaSamples = vk::SampleCountFlagBits::e1; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::Format swapChainImageFormat = vk::Format::eUndefined; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + + vk::raii::Image colorImage = nullptr; + vk::raii::DeviceMemory colorImageMemory = nullptr; + vk::raii::ImageView colorImageView = nullptr; + + vk::raii::Image depthImage = nullptr; + vk::raii::DeviceMemory depthImageMemory = nullptr; + vk::raii::ImageView depthImageView = nullptr; + + uint32_t mipLevels = 0; + vk::raii::Image textureImage = nullptr; + vk::raii::DeviceMemory textureImageMemory = nullptr; + vk::raii::ImageView textureImageView = nullptr; + vk::raii::Sampler textureSampler = nullptr; + + std::vector vertices; + std::vector indices; + vk::raii::Buffer vertexBuffer = nullptr; + vk::raii::DeviceMemory vertexBufferMemory = nullptr; + vk::raii::Buffer indexBuffer = nullptr; + vk::raii::DeviceMemory indexBufferMemory = nullptr; + + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + std::vector uniformBuffersMapped; + + vk::raii::DescriptorPool descriptorPool = nullptr; + std::vector descriptorSets; + + vk::raii::CommandPool commandPool = nullptr; + std::vector commandBuffers; + + std::vector presentCompleteSemaphore; + std::vector renderFinishedSemaphore; + std::vector inFlightFences; + uint32_t semaphoreIndex = 0; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow *window, int width, int height) + { + auto app = static_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + msaaSamples = getMaxUsableSampleCount(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createDescriptorSetLayout(); + createGraphicsPipeline(); + createCommandPool(); + createColorResources(); + createDepthResources(); + createTextureImage(); + createTextureImageView(); + createTextureSampler(); + loadModel(); + createVertexBuffer(); + createIndexBuffer(); + createUniformBuffers(); + createDescriptorPool(); + createDescriptorSets(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() + { + while (!glfwWindowShouldClose(window)) + { + glfwPollEvents(); + drawFrame(); + } + + device.waitIdle(); + } + + void cleanupSwapChain() + { + swapChainImageViews.clear(); + swapChain = nullptr; + } + + void cleanup() const + { + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() + { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) + { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + device.waitIdle(); + + cleanupSwapChain(); + createSwapChain(); + createImageViews(); + createColorResources(); + createDepthResources(); + } + + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().features.samplerAnisotropy && - features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - }); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error("failed to find a suitable GPU!"); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain featureChain = { - {.features = {.samplerAnisotropy = true } }, // vk::PhysicalDeviceFeatures2 - {.synchronization2 = true, .dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface); - swapChainImageFormat = 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{ - .surface = surface, .minImageCount = minImageCount, - .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, - .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 }; - - swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); - swapChainImages = swapChain.getImages(); - } - - void createImageViews() { - 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( device, imageViewCreateInfo ); - } - } - - void createDescriptorSetLayout() { - std::array bindings = { - vk::DescriptorSetLayoutBinding( 0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr), - vk::DescriptorSetLayoutBinding( 1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment, nullptr) - }; - - vk::DescriptorSetLayoutCreateInfo layoutInfo{ .bindingCount = static_cast(bindings.size()), .pBindings = bindings.data() }; - descriptorSetLayout = vk::raii::DescriptorSetLayout(device, layoutInfo); - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; - - auto bindingDescription = Vertex::getBindingDescription(); - auto attributeDescriptions = Vertex::getAttributeDescriptions(); - vk::PipelineVertexInputStateCreateInfo vertexInputInfo{ - .vertexBindingDescriptionCount = 1, - .pVertexBindingDescriptions = &bindingDescription, - .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), - .pVertexAttributeDescriptions = attributeDescriptions.data() - }; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ - .topology = vk::PrimitiveTopology::eTriangleList, - .primitiveRestartEnable = vk::False - }; - vk::PipelineViewportStateCreateInfo viewportState{ - .viewportCount = 1, - .scissorCount = 1 - }; - vk::PipelineRasterizationStateCreateInfo rasterizer{ - .depthClampEnable = vk::False, - .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, - .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eCounterClockwise, - .depthBiasEnable = vk::False - }; - rasterizer.lineWidth = 1.0f; - vk::PipelineMultisampleStateCreateInfo multisampling{ - .rasterizationSamples = msaaSamples, - .sampleShadingEnable = vk::False - }; - vk::PipelineDepthStencilStateCreateInfo depthStencil{ - .depthTestEnable = vk::True, - .depthWriteEnable = vk::True, - .depthCompareOp = vk::CompareOp::eLess, - .depthBoundsTestEnable = vk::False, - .stencilTestEnable = vk::False - }; - vk::PipelineColorBlendAttachmentState colorBlendAttachment; - colorBlendAttachment.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA; - colorBlendAttachment.blendEnable = vk::False; - - vk::PipelineColorBlendStateCreateInfo colorBlending{ - .logicOpEnable = vk::False, - .logicOp = vk::LogicOp::eCopy, - .attachmentCount = 1, - .pAttachments = &colorBlendAttachment - }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0 }; - - pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); - - vk::Format depthFormat = findDepthFormat(); - vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ - .colorAttachmentCount = 1, - .pColorAttachmentFormats = &swapChainImageFormat, - .depthAttachmentFormat = depthFormat - }; - vk::GraphicsPipelineCreateInfo pipelineInfo{ .pNext = &pipelineRenderingCreateInfo, - .stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pDepthStencilState = &depthStencil, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = pipelineLayout, - .renderPass = nullptr - }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{ - .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex - }; - commandPool = vk::raii::CommandPool(device, poolInfo); - } - - void createColorResources() { - vk::Format colorFormat = swapChainImageFormat; - - createImage(swapChainExtent.width, swapChainExtent.height, 1, msaaSamples, colorFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransientAttachment | vk::ImageUsageFlagBits::eColorAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, colorImage, colorImageMemory); - colorImageView = createImageView(colorImage, colorFormat, vk::ImageAspectFlagBits::eColor, 1); - } - - void createDepthResources() { - vk::Format depthFormat = findDepthFormat(); - - createImage(swapChainExtent.width, swapChainExtent.height, 1, msaaSamples, depthFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eDepthStencilAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, depthImage, depthImageMemory); - depthImageView = createImageView(depthImage, depthFormat, vk::ImageAspectFlagBits::eDepth, 1); - } - - vk::Format findSupportedFormat(const std::vector& candidates, vk::ImageTiling tiling, vk::FormatFeatureFlags features) const { - for (const auto format : candidates) { - vk::FormatProperties props = physicalDevice.getFormatProperties(format); - - if (tiling == vk::ImageTiling::eLinear && (props.linearTilingFeatures & features) == features) { - return format; - } - if (tiling == vk::ImageTiling::eOptimal && (props.optimalTilingFeatures & features) == features) { - return format; - } - } - - throw std::runtime_error("failed to find supported format!"); - } - - [[nodiscard]] vk::Format findDepthFormat() const { - return findSupportedFormat( - {vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, vk::Format::eD24UnormS8Uint}, - vk::ImageTiling::eOptimal, - vk::FormatFeatureFlagBits::eDepthStencilAttachment - ); - } - - static bool hasStencilComponent(vk::Format format) { - return format == vk::Format::eD32SfloatS8Uint || format == vk::Format::eD24UnormS8Uint; - } - - void createTextureImage() { - int texWidth, texHeight, texChannels; - stbi_uc* pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); - vk::DeviceSize imageSize = texWidth * texHeight * 4; - mipLevels = static_cast(std::floor(std::log2(std::max(texWidth, texHeight)))) + 1; - - if (!pixels) { - throw std::runtime_error("failed to load texture image!"); - } - - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, imageSize); - memcpy(data, pixels, imageSize); - stagingBufferMemory.unmapMemory(); - - stbi_image_free(pixels); - - createImage(texWidth, texHeight, mipLevels, vk::SampleCountFlagBits::e1, vk::Format::eR8G8B8A8Srgb, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory); - - transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, mipLevels); - copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); - - generateMipmaps(textureImage, vk::Format::eR8G8B8A8Srgb, texWidth, texHeight, mipLevels); - } - - void generateMipmaps(vk::raii::Image& image, vk::Format imageFormat, int32_t texWidth, int32_t texHeight, uint32_t mipLevels) { - // Check if image format supports linear blit-ing - vk::FormatProperties formatProperties = physicalDevice.getFormatProperties(imageFormat); - - if (!(formatProperties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eSampledImageFilterLinear)) { - throw std::runtime_error("texture image format does not support linear blitting!"); - } - - std::unique_ptr commandBuffer = beginSingleTimeCommands(); - - vk::ImageMemoryBarrier barrier = { .srcAccessMask = vk::AccessFlagBits::eTransferWrite, .dstAccessMask =vk::AccessFlagBits::eTransferRead - , .oldLayout = vk::ImageLayout::eTransferDstOptimal, .newLayout = vk::ImageLayout::eTransferSrcOptimal - , .srcQueueFamilyIndex = vk::QueueFamilyIgnored, .dstQueueFamilyIndex = vk::QueueFamilyIgnored, .image = image }; - barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; - barrier.subresourceRange.baseArrayLayer = 0; - barrier.subresourceRange.layerCount = 1; - barrier.subresourceRange.levelCount = 1; - - int32_t mipWidth = texWidth; - int32_t mipHeight = texHeight; - - for (uint32_t i = 1; i < mipLevels; i++) { - barrier.subresourceRange.baseMipLevel = i - 1; - barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal; - barrier.newLayout = vk::ImageLayout::eTransferSrcOptimal; - barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; - barrier.dstAccessMask = vk::AccessFlagBits::eTransferRead; - - commandBuffer->pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer, {}, {}, {}, barrier); - - vk::ArrayWrapper1D offsets, dstOffsets; - offsets[0] = vk::Offset3D(0, 0, 0); - offsets[1] = vk::Offset3D(mipWidth, mipHeight, 1); - dstOffsets[0] = vk::Offset3D(0, 0, 0); - dstOffsets[1] = vk::Offset3D(mipWidth > 1 ? mipWidth / 2 : 1, mipHeight > 1 ? mipHeight / 2 : 1, 1); - vk::ImageBlit blit = { .srcSubresource = {}, .srcOffsets = offsets, - .dstSubresource = {}, .dstOffsets = dstOffsets }; - blit.srcSubresource = vk::ImageSubresourceLayers( vk::ImageAspectFlagBits::eColor, i - 1, 0, 1); - blit.dstSubresource = vk::ImageSubresourceLayers( vk::ImageAspectFlagBits::eColor, i, 0, 1); - - commandBuffer->blitImage(image, vk::ImageLayout::eTransferSrcOptimal, image, vk::ImageLayout::eTransferDstOptimal, { blit }, vk::Filter::eLinear); - - barrier.oldLayout = vk::ImageLayout::eTransferSrcOptimal; - barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal; - barrier.srcAccessMask = vk::AccessFlagBits::eTransferRead; - barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - - commandBuffer->pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {}, barrier); - - if (mipWidth > 1) mipWidth /= 2; - if (mipHeight > 1) mipHeight /= 2; - } - - barrier.subresourceRange.baseMipLevel = mipLevels - 1; - barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal; - barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal; - barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; - barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - - commandBuffer->pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {}, barrier); - - endSingleTimeCommands(*commandBuffer); - } - - vk::SampleCountFlagBits getMaxUsableSampleCount() { - vk::PhysicalDeviceProperties physicalDeviceProperties = physicalDevice.getProperties(); - - vk::SampleCountFlags counts = physicalDeviceProperties.limits.framebufferColorSampleCounts & physicalDeviceProperties.limits.framebufferDepthSampleCounts; - if (counts & vk::SampleCountFlagBits::e64) { return vk::SampleCountFlagBits::e64; } - if (counts & vk::SampleCountFlagBits::e32) { return vk::SampleCountFlagBits::e32; } - if (counts & vk::SampleCountFlagBits::e16) { return vk::SampleCountFlagBits::e16; } - if (counts & vk::SampleCountFlagBits::e8) { return vk::SampleCountFlagBits::e8; } - if (counts & vk::SampleCountFlagBits::e4) { return vk::SampleCountFlagBits::e4; } - if (counts & vk::SampleCountFlagBits::e2) { return vk::SampleCountFlagBits::e2; } - - return vk::SampleCountFlagBits::e1; - } - - void createTextureImageView() { - textureImageView = createImageView(textureImage, vk::Format::eR8G8B8A8Srgb, vk::ImageAspectFlagBits::eColor, mipLevels); - } - - void createTextureSampler() { - vk::PhysicalDeviceProperties properties = physicalDevice.getProperties(); - vk::SamplerCreateInfo samplerInfo { - .magFilter = vk::Filter::eLinear, - .minFilter = vk::Filter::eLinear, - .mipmapMode = vk::SamplerMipmapMode::eLinear, - .addressModeU = vk::SamplerAddressMode::eRepeat, - .addressModeV = vk::SamplerAddressMode::eRepeat, - .addressModeW = vk::SamplerAddressMode::eRepeat, - .mipLodBias = 0.0f, - .anisotropyEnable = vk::True, - .maxAnisotropy = properties.limits.maxSamplerAnisotropy, - .compareEnable = vk::False, - .compareOp = vk::CompareOp::eAlways - }; - textureSampler = vk::raii::Sampler(device, samplerInfo); - } - - [[nodiscard]] vk::raii::ImageView createImageView(const vk::raii::Image& image, vk::Format format, vk::ImageAspectFlags aspectFlags, uint32_t mipLevels) const { - vk::ImageViewCreateInfo viewInfo{ - .image = image, - .viewType = vk::ImageViewType::e2D, - .format = format, - .subresourceRange = { aspectFlags, 0, mipLevels, 0, 1 } - }; - return vk::raii::ImageView( device, viewInfo ); - } - - void createImage(uint32_t width, uint32_t height, uint32_t mipLevels, vk::SampleCountFlagBits numSamples, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image& image, vk::raii::DeviceMemory& imageMemory) { - vk::ImageCreateInfo imageInfo{ - .imageType = vk::ImageType::e2D, - .format = format, - .extent = {width, height, 1}, - .mipLevels = mipLevels, - .arrayLayers = 1, - .samples = numSamples, - .tiling = tiling, - .usage = usage, - .sharingMode = vk::SharingMode::eExclusive, - .initialLayout = vk::ImageLayout::eUndefined - }; - image = vk::raii::Image(device, imageInfo); - - vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ - .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) - }; - imageMemory = vk::raii::DeviceMemory(device, allocInfo); - image.bindMemory(imageMemory, 0); - } - - void transitionImageLayout(const vk::raii::Image& image, const vk::ImageLayout oldLayout, const vk::ImageLayout newLayout, uint32_t mipLevels) { - const auto commandBuffer = beginSingleTimeCommands(); - - vk::ImageMemoryBarrier barrier{ - .oldLayout = oldLayout, - .newLayout = newLayout, - .image = image, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, mipLevels, 0, 1 } - }; - - vk::PipelineStageFlags sourceStage; - vk::PipelineStageFlags destinationStage; - - if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) { - barrier.srcAccessMask = {}; - barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; - - sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; - destinationStage = vk::PipelineStageFlagBits::eTransfer; - } else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) { - barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; - barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - - sourceStage = vk::PipelineStageFlagBits::eTransfer; - destinationStage = vk::PipelineStageFlagBits::eFragmentShader; - } else { - throw std::invalid_argument("unsupported layout transition!"); - } - commandBuffer->pipelineBarrier( sourceStage, destinationStage, {}, {}, nullptr, barrier ); - endSingleTimeCommands(*commandBuffer); - } - - void copyBufferToImage(const vk::raii::Buffer& buffer, const vk::raii::Image& image, uint32_t width, uint32_t height) { - std::unique_ptr commandBuffer = beginSingleTimeCommands(); - vk::BufferImageCopy region{ - .bufferOffset = 0, - .bufferRowLength = 0, - .bufferImageHeight = 0, - .imageSubresource = { vk::ImageAspectFlagBits::eColor, 0, 0, 1 }, - .imageOffset = {0, 0, 0}, - .imageExtent = {width, height, 1} - }; - commandBuffer->copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, {region}); - endSingleTimeCommands(*commandBuffer); - } - - void loadModel() { - tinyobj::attrib_t attrib; - std::vector shapes; - std::vector materials; - std::string warn, err; - - if (!LoadObj(&attrib, &shapes, &materials, &warn, &err, MODEL_PATH.c_str())) { - throw std::runtime_error(warn + err); - } - - std::unordered_map uniqueVertices{}; - - for (const auto& shape : shapes) { - for (const auto& index : shape.mesh.indices) { - Vertex vertex{}; - - vertex.pos = { - attrib.vertices[3 * index.vertex_index + 0], - attrib.vertices[3 * index.vertex_index + 1], - attrib.vertices[3 * index.vertex_index + 2] - }; - - vertex.texCoord = { - attrib.texcoords[2 * index.texcoord_index + 0], - 1.0f - attrib.texcoords[2 * index.texcoord_index + 1] - }; - - vertex.color = {1.0f, 1.0f, 1.0f}; - - if (!uniqueVertices.contains(vertex)) { - uniqueVertices[vertex] = static_cast(vertices.size()); - vertices.push_back(vertex); - } - - indices.push_back(uniqueVertices[vertex]); - } - } - } - - void createVertexBuffer() { - vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(dataStaging, vertices.data(), bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); - - copyBuffer(stagingBuffer, vertexBuffer, bufferSize); - } - - void createIndexBuffer() { - vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); - - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(data, indices.data(), bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); - - copyBuffer(stagingBuffer, indexBuffer, bufferSize); - } - - void createUniformBuffers() { - uniformBuffers.clear(); - uniformBuffersMemory.clear(); - uniformBuffersMapped.clear(); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DeviceSize bufferSize = sizeof(UniformBufferObject); - vk::raii::Buffer buffer({}); - vk::raii::DeviceMemory bufferMem({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); - uniformBuffers.emplace_back(std::move(buffer)); - uniformBuffersMemory.emplace_back(std::move(bufferMem)); - uniformBuffersMapped.emplace_back( uniformBuffersMemory[i].mapMemory(0, bufferSize)); - } - } - - void createDescriptorPool() { - std::array poolSize { - vk::DescriptorPoolSize( vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT), - vk::DescriptorPoolSize( vk::DescriptorType::eCombinedImageSampler, MAX_FRAMES_IN_FLIGHT) - }; - vk::DescriptorPoolCreateInfo poolInfo{ - .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, - .maxSets = MAX_FRAMES_IN_FLIGHT, - .poolSizeCount = static_cast(poolSize.size()), - .pPoolSizes = poolSize.data() - }; - descriptorPool = vk::raii::DescriptorPool(device, poolInfo); - } - - void createDescriptorSets() { - std::vector layouts(MAX_FRAMES_IN_FLIGHT, descriptorSetLayout); - vk::DescriptorSetAllocateInfo allocInfo{ - .descriptorPool = descriptorPool, - .descriptorSetCount = static_cast(layouts.size()), - .pSetLayouts = layouts.data() - }; - - descriptorSets.clear(); - descriptorSets = device.allocateDescriptorSets(allocInfo); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DescriptorBufferInfo bufferInfo{ - .buffer = uniformBuffers[i], - .offset = 0, - .range = sizeof(UniformBufferObject) - }; - vk::DescriptorImageInfo imageInfo{ - .sampler = textureSampler, - .imageView = textureImageView, - .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal - }; - std::array descriptorWrites{ - vk::WriteDescriptorSet{ - .dstSet = descriptorSets[i], - .dstBinding = 0, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eUniformBuffer, - .pBufferInfo = &bufferInfo - }, - vk::WriteDescriptorSet{ - .dstSet = descriptorSets[i], - .dstBinding = 1, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eCombinedImageSampler, - .pImageInfo = &imageInfo - } - }; - device.updateDescriptorSets(descriptorWrites, {}); - } - } - - void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer& buffer, vk::raii::DeviceMemory& bufferMemory) { - vk::BufferCreateInfo bufferInfo{ - .size = size, - .usage = usage, - .sharingMode = vk::SharingMode::eExclusive - }; - buffer = vk::raii::Buffer(device, bufferInfo); - vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ - .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) - }; - bufferMemory = vk::raii::DeviceMemory(device, allocInfo); - buffer.bindMemory(bufferMemory, 0); - } - - std::unique_ptr beginSingleTimeCommands() { - vk::CommandBufferAllocateInfo allocInfo{ - .commandPool = commandPool, - .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = 1 - }; - std::unique_ptr commandBuffer = std::make_unique(std::move(vk::raii::CommandBuffers(device, allocInfo).front())); - - vk::CommandBufferBeginInfo beginInfo{ - .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit - }; - commandBuffer->begin(beginInfo); - - return commandBuffer; - } - - void endSingleTimeCommands(const vk::raii::CommandBuffer& commandBuffer) const { - commandBuffer.end(); - - vk::SubmitInfo submitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandBuffer }; - queue.submit(submitInfo, nullptr); - queue.waitIdle(); - } - - void copyBuffer(vk::raii::Buffer & srcBuffer, vk::raii::Buffer & dstBuffer, vk::DeviceSize size) { - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1 }; - vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); - commandCopyBuffer.begin(vk::CommandBufferBeginInfo{ .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit }); - commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy{ .size = size }); - commandCopyBuffer.end(); - queue.submit(vk::SubmitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer }, nullptr); - queue.waitIdle(); - } - - uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) { - vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); - - for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { - if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { - return i; - } - } - - throw std::runtime_error("failed to find suitable memory type!"); - } - - void createCommandBuffers() { - commandBuffers.clear(); - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = MAX_FRAMES_IN_FLIGHT }; - commandBuffers = vk::raii::CommandBuffers(device, allocInfo); - } - - void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin({}); - // Before starting rendering, transition the images to the appropriate layouts - // Transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL - transition_image_layout( - imageIndex, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage - ); - - // Transition the multisampled color image to COLOR_ATTACHMENT_OPTIMAL - transition_image_layout_custom( - colorImage, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, - vk::AccessFlagBits2::eColorAttachmentWrite, - vk::PipelineStageFlagBits2::eTopOfPipe, - vk::PipelineStageFlagBits2::eColorAttachmentOutput, - vk::ImageAspectFlagBits::eColor - ); - - // Transition the depth image to DEPTH_ATTACHMENT_OPTIMAL - transition_image_layout_custom( - depthImage, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eDepthAttachmentOptimal, - {}, - vk::AccessFlagBits2::eDepthStencilAttachmentWrite, - vk::PipelineStageFlagBits2::eTopOfPipe, - vk::PipelineStageFlagBits2::eEarlyFragmentTests, - vk::ImageAspectFlagBits::eDepth - ); - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::ClearValue clearDepth = vk::ClearDepthStencilValue(1.0f, 0); - - // Color attachment (multisampled) with resolve attachment - vk::RenderingAttachmentInfo colorAttachment = { - .imageView = colorImageView, - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .resolveMode = vk::ResolveModeFlagBits::eAverage, - .resolveImageView = swapChainImageViews[imageIndex], - .resolveImageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor - }; - - // Depth attachment - vk::RenderingAttachmentInfo depthAttachment = { - .imageView = depthImageView, - .imageLayout = vk::ImageLayout::eDepthAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eDontCare, - .clearValue = clearDepth - }; - - vk::RenderingInfo renderingInfo = { - .renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &colorAttachment, - .pDepthAttachment = &depthAttachment - }; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].bindIndexBuffer( *indexBuffer, 0, vk::IndexType::eUint32 ); - commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); - commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); - commandBuffers[currentFrame].endRendering(); - // After rendering, transition the images to appropriate layouts - - // Transition the swapchain image to PRESENT_SRC - transition_image_layout( - imageIndex, - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage - ); - commandBuffers[currentFrame].end(); - } - - void transition_image_layout( - uint32_t imageIndex, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::AccessFlags2 src_access_mask, - vk::AccessFlags2 dst_access_mask, - vk::PipelineStageFlags2 src_stage_mask, - vk::PipelineStageFlags2 dst_stage_mask - ) { - vk::ImageMemoryBarrier2 barrier = { - .srcStageMask = src_stage_mask, - .srcAccessMask = src_access_mask, - .dstStageMask = dst_stage_mask, - .dstAccessMask = dst_access_mask, - .oldLayout = old_layout, - .newLayout = new_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); - } - - void transition_image_layout_custom( - vk::raii::Image& image, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::AccessFlags2 src_access_mask, - vk::AccessFlags2 dst_access_mask, - vk::PipelineStageFlags2 src_stage_mask, - vk::PipelineStageFlags2 dst_stage_mask, - vk::ImageAspectFlags aspect_mask - ) { - vk::ImageMemoryBarrier2 barrier = { - .srcStageMask = src_stage_mask, - .srcAccessMask = src_access_mask, - .dstStageMask = dst_stage_mask, - .dstAccessMask = dst_access_mask, - .oldLayout = old_layout, - .newLayout = new_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = image, - .subresourceRange = { - .aspectMask = aspect_mask, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); - } - - void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); - - for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - } - - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - inFlightFences.emplace_back(device, vk::FenceCreateInfo{ .flags = vk::FenceCreateFlagBits::eSignaled }); - } - } - - void updateUniformBuffer(uint32_t currentImage) const { - static auto startTime = std::chrono::high_resolution_clock::now(); - - auto currentTime = std::chrono::high_resolution_clock::now(); - float time = std::chrono::duration(currentTime - startTime).count(); - - UniformBufferObject ubo{}; - ubo.model = rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.view = lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); - ubo.proj[1][1] *= -1; - - memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); - } - - void drawFrame() { - while ( vk::Result::eTimeout == device.waitForFences( *inFlightFences[currentFrame], vk::True, UINT64_MAX ) ) - ; - auto [result, imageIndex] = swapChain.acquireNextImage( UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr ); - - if (result == vk::Result::eErrorOutOfDateKHR) { - recreateSwapChain(); - return; - } - if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) { - throw std::runtime_error("failed to acquire swap chain image!"); - } - updateUniformBuffer(currentFrame); - - device.resetFences( *inFlightFences[currentFrame] ); - commandBuffers[currentFrame].reset(); - recordCommandBuffer(imageIndex); - - vk::PipelineStageFlags waitDestinationStageMask( vk::PipelineStageFlagBits::eColorAttachmentOutput ); - const vk::SubmitInfo submitInfo{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], - .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], - .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex] }; - queue.submit(submitInfo, *inFlightFences[currentFrame]); - - - const vk::PresentInfoKHR presentInfoKHR{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], - .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex }; - result = queue.presentKHR(presentInfoKHR); - if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { - framebufferResized = false; - recreateSwapChain(); - } else if (result != vk::Result::eSuccess) { - throw std::runtime_error("failed to present swap chain image!"); - } - semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; - } - - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size(), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static vk::Format chooseSwapSurfaceFormat(const std::vector& availableFormats) { - const auto formatIt = std::ranges::find_if(availableFormats, - [](const auto& format) { - return format.format == vk::Format::eB8G8R8A8Srgb && - format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; - }); - return formatIt != availableFormats.end() ? formatIt->format : availableFormats[0].format; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eFifo; - } - - [[nodiscard]] vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) const { - 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) - }; - } - - [[nodiscard]] std::vector getRequiredExtensions() const { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - 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; - } + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().features.samplerAnisotropy && + features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain featureChain = { + {.features = {.samplerAnisotropy = true}}, // vk::PhysicalDeviceFeatures2 + {.synchronization2 = true, .dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface); + swapChainImageFormat = 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{ + .surface = surface, .minImageCount = minImageCount, .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + void createImageViews() + { + 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(device, imageViewCreateInfo); + } + } + + void createDescriptorSetLayout() + { + std::array bindings = { + vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr), + vk::DescriptorSetLayoutBinding(1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment, nullptr)}; + + vk::DescriptorSetLayoutCreateInfo layoutInfo{.bindingCount = static_cast(bindings.size()), .pBindings = bindings.data()}; + descriptorSetLayout = vk::raii::DescriptorSetLayout(device, layoutInfo); + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; + + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{ + .vertexBindingDescriptionCount = 1, + .pVertexBindingDescriptions = &bindingDescription, + .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), + .pVertexAttributeDescriptions = attributeDescriptions.data()}; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ + .topology = vk::PrimitiveTopology::eTriangleList, + .primitiveRestartEnable = vk::False}; + vk::PipelineViewportStateCreateInfo viewportState{ + .viewportCount = 1, + .scissorCount = 1}; + vk::PipelineRasterizationStateCreateInfo rasterizer{ + .depthClampEnable = vk::False, + .rasterizerDiscardEnable = vk::False, + .polygonMode = vk::PolygonMode::eFill, + .cullMode = vk::CullModeFlagBits::eBack, + .frontFace = vk::FrontFace::eCounterClockwise, + .depthBiasEnable = vk::False}; + rasterizer.lineWidth = 1.0f; + vk::PipelineMultisampleStateCreateInfo multisampling{ + .rasterizationSamples = msaaSamples, + .sampleShadingEnable = vk::False}; + vk::PipelineDepthStencilStateCreateInfo depthStencil{ + .depthTestEnable = vk::True, + .depthWriteEnable = vk::True, + .depthCompareOp = vk::CompareOp::eLess, + .depthBoundsTestEnable = vk::False, + .stencilTestEnable = vk::False}; + vk::PipelineColorBlendAttachmentState colorBlendAttachment; + colorBlendAttachment.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA; + colorBlendAttachment.blendEnable = vk::False; + + vk::PipelineColorBlendStateCreateInfo colorBlending{ + .logicOpEnable = vk::False, + .logicOp = vk::LogicOp::eCopy, + .attachmentCount = 1, + .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0}; + + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::Format depthFormat = findDepthFormat(); + vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ + .colorAttachmentCount = 1, + .pColorAttachmentFormats = &swapChainImageFormat, + .depthAttachmentFormat = depthFormat}; + vk::GraphicsPipelineCreateInfo pipelineInfo{.pNext = &pipelineRenderingCreateInfo, + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pDepthStencilState = &depthStencil, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{ + .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex}; + commandPool = vk::raii::CommandPool(device, poolInfo); + } + + void createColorResources() + { + vk::Format colorFormat = swapChainImageFormat; + + createImage(swapChainExtent.width, swapChainExtent.height, 1, msaaSamples, colorFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransientAttachment | vk::ImageUsageFlagBits::eColorAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, colorImage, colorImageMemory); + colorImageView = createImageView(colorImage, colorFormat, vk::ImageAspectFlagBits::eColor, 1); + } + + void createDepthResources() + { + vk::Format depthFormat = findDepthFormat(); + + createImage(swapChainExtent.width, swapChainExtent.height, 1, msaaSamples, depthFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eDepthStencilAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, depthImage, depthImageMemory); + depthImageView = createImageView(depthImage, depthFormat, vk::ImageAspectFlagBits::eDepth, 1); + } + + vk::Format findSupportedFormat(const std::vector &candidates, vk::ImageTiling tiling, vk::FormatFeatureFlags features) const + { + for (const auto format : candidates) + { + vk::FormatProperties props = physicalDevice.getFormatProperties(format); + + if (tiling == vk::ImageTiling::eLinear && (props.linearTilingFeatures & features) == features) + { + return format; + } + if (tiling == vk::ImageTiling::eOptimal && (props.optimalTilingFeatures & features) == features) + { + return format; + } + } + + throw std::runtime_error("failed to find supported format!"); + } + + [[nodiscard]] vk::Format findDepthFormat() const + { + return findSupportedFormat( + {vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, vk::Format::eD24UnormS8Uint}, + vk::ImageTiling::eOptimal, + vk::FormatFeatureFlagBits::eDepthStencilAttachment); + } + + static bool hasStencilComponent(vk::Format format) + { + return format == vk::Format::eD32SfloatS8Uint || format == vk::Format::eD24UnormS8Uint; + } + + void createTextureImage() + { + int texWidth, texHeight, texChannels; + stbi_uc *pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + vk::DeviceSize imageSize = texWidth * texHeight * 4; + mipLevels = static_cast(std::floor(std::log2(std::max(texWidth, texHeight)))) + 1; + + if (!pixels) + { + throw std::runtime_error("failed to load texture image!"); + } + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, imageSize); + memcpy(data, pixels, imageSize); + stagingBufferMemory.unmapMemory(); + + stbi_image_free(pixels); + + createImage(texWidth, texHeight, mipLevels, vk::SampleCountFlagBits::e1, vk::Format::eR8G8B8A8Srgb, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory); + + transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, mipLevels); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); + + generateMipmaps(textureImage, vk::Format::eR8G8B8A8Srgb, texWidth, texHeight, mipLevels); + } + + void generateMipmaps(vk::raii::Image &image, vk::Format imageFormat, int32_t texWidth, int32_t texHeight, uint32_t mipLevels) + { + // Check if image format supports linear blit-ing + vk::FormatProperties formatProperties = physicalDevice.getFormatProperties(imageFormat); + + if (!(formatProperties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eSampledImageFilterLinear)) + { + throw std::runtime_error("texture image format does not support linear blitting!"); + } + + std::unique_ptr commandBuffer = beginSingleTimeCommands(); + + vk::ImageMemoryBarrier barrier = {.srcAccessMask = vk::AccessFlagBits::eTransferWrite, .dstAccessMask = vk::AccessFlagBits::eTransferRead, .oldLayout = vk::ImageLayout::eTransferDstOptimal, .newLayout = vk::ImageLayout::eTransferSrcOptimal, .srcQueueFamilyIndex = vk::QueueFamilyIgnored, .dstQueueFamilyIndex = vk::QueueFamilyIgnored, .image = image}; + barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + barrier.subresourceRange.levelCount = 1; + + int32_t mipWidth = texWidth; + int32_t mipHeight = texHeight; + + for (uint32_t i = 1; i < mipLevels; i++) + { + barrier.subresourceRange.baseMipLevel = i - 1; + barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal; + barrier.newLayout = vk::ImageLayout::eTransferSrcOptimal; + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eTransferRead; + + commandBuffer->pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer, {}, {}, {}, barrier); + + vk::ArrayWrapper1D offsets, dstOffsets; + offsets[0] = vk::Offset3D(0, 0, 0); + offsets[1] = vk::Offset3D(mipWidth, mipHeight, 1); + dstOffsets[0] = vk::Offset3D(0, 0, 0); + dstOffsets[1] = vk::Offset3D(mipWidth > 1 ? mipWidth / 2 : 1, mipHeight > 1 ? mipHeight / 2 : 1, 1); + vk::ImageBlit blit = {.srcSubresource = {}, .srcOffsets = offsets, .dstSubresource = {}, .dstOffsets = dstOffsets}; + blit.srcSubresource = vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, i - 1, 0, 1); + blit.dstSubresource = vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, i, 0, 1); + + commandBuffer->blitImage(image, vk::ImageLayout::eTransferSrcOptimal, image, vk::ImageLayout::eTransferDstOptimal, {blit}, vk::Filter::eLinear); + + barrier.oldLayout = vk::ImageLayout::eTransferSrcOptimal; + barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal; + barrier.srcAccessMask = vk::AccessFlagBits::eTransferRead; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + + commandBuffer->pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {}, barrier); + + if (mipWidth > 1) + mipWidth /= 2; + if (mipHeight > 1) + mipHeight /= 2; + } + + barrier.subresourceRange.baseMipLevel = mipLevels - 1; + barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal; + barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal; + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + + commandBuffer->pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {}, barrier); + + endSingleTimeCommands(*commandBuffer); + } + + vk::SampleCountFlagBits getMaxUsableSampleCount() + { + vk::PhysicalDeviceProperties physicalDeviceProperties = physicalDevice.getProperties(); + + vk::SampleCountFlags counts = physicalDeviceProperties.limits.framebufferColorSampleCounts & physicalDeviceProperties.limits.framebufferDepthSampleCounts; + if (counts & vk::SampleCountFlagBits::e64) + { + return vk::SampleCountFlagBits::e64; + } + if (counts & vk::SampleCountFlagBits::e32) + { + return vk::SampleCountFlagBits::e32; + } + if (counts & vk::SampleCountFlagBits::e16) + { + return vk::SampleCountFlagBits::e16; + } + if (counts & vk::SampleCountFlagBits::e8) + { + return vk::SampleCountFlagBits::e8; + } + if (counts & vk::SampleCountFlagBits::e4) + { + return vk::SampleCountFlagBits::e4; + } + if (counts & vk::SampleCountFlagBits::e2) + { + return vk::SampleCountFlagBits::e2; + } + + return vk::SampleCountFlagBits::e1; + } + + void createTextureImageView() + { + textureImageView = createImageView(textureImage, vk::Format::eR8G8B8A8Srgb, vk::ImageAspectFlagBits::eColor, mipLevels); + } + + void createTextureSampler() + { + vk::PhysicalDeviceProperties properties = physicalDevice.getProperties(); + vk::SamplerCreateInfo samplerInfo{ + .magFilter = vk::Filter::eLinear, + .minFilter = vk::Filter::eLinear, + .mipmapMode = vk::SamplerMipmapMode::eLinear, + .addressModeU = vk::SamplerAddressMode::eRepeat, + .addressModeV = vk::SamplerAddressMode::eRepeat, + .addressModeW = vk::SamplerAddressMode::eRepeat, + .mipLodBias = 0.0f, + .anisotropyEnable = vk::True, + .maxAnisotropy = properties.limits.maxSamplerAnisotropy, + .compareEnable = vk::False, + .compareOp = vk::CompareOp::eAlways}; + textureSampler = vk::raii::Sampler(device, samplerInfo); + } + + [[nodiscard]] vk::raii::ImageView createImageView(const vk::raii::Image &image, vk::Format format, vk::ImageAspectFlags aspectFlags, uint32_t mipLevels) const + { + vk::ImageViewCreateInfo viewInfo{ + .image = image, + .viewType = vk::ImageViewType::e2D, + .format = format, + .subresourceRange = {aspectFlags, 0, mipLevels, 0, 1}}; + return vk::raii::ImageView(device, viewInfo); + } + + void createImage(uint32_t width, uint32_t height, uint32_t mipLevels, vk::SampleCountFlagBits numSamples, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image &image, vk::raii::DeviceMemory &imageMemory) + { + vk::ImageCreateInfo imageInfo{ + .imageType = vk::ImageType::e2D, + .format = format, + .extent = {width, height, 1}, + .mipLevels = mipLevels, + .arrayLayers = 1, + .samples = numSamples, + .tiling = tiling, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive, + .initialLayout = vk::ImageLayout::eUndefined}; + image = vk::raii::Image(device, imageInfo); + + vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{ + .allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + imageMemory = vk::raii::DeviceMemory(device, allocInfo); + image.bindMemory(imageMemory, 0); + } + + void transitionImageLayout(const vk::raii::Image &image, const vk::ImageLayout oldLayout, const vk::ImageLayout newLayout, uint32_t mipLevels) + { + const auto commandBuffer = beginSingleTimeCommands(); + + vk::ImageMemoryBarrier barrier{ + .oldLayout = oldLayout, + .newLayout = newLayout, + .image = image, + .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, mipLevels, 0, 1}}; + + vk::PipelineStageFlags sourceStage; + vk::PipelineStageFlags destinationStage; + + if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) + { + barrier.srcAccessMask = {}; + barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; + + sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; + destinationStage = vk::PipelineStageFlagBits::eTransfer; + } + else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) + { + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + + sourceStage = vk::PipelineStageFlagBits::eTransfer; + destinationStage = vk::PipelineStageFlagBits::eFragmentShader; + } + else + { + throw std::invalid_argument("unsupported layout transition!"); + } + commandBuffer->pipelineBarrier(sourceStage, destinationStage, {}, {}, nullptr, barrier); + endSingleTimeCommands(*commandBuffer); + } + + void copyBufferToImage(const vk::raii::Buffer &buffer, const vk::raii::Image &image, uint32_t width, uint32_t height) + { + std::unique_ptr commandBuffer = beginSingleTimeCommands(); + vk::BufferImageCopy region{ + .bufferOffset = 0, + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource = {vk::ImageAspectFlagBits::eColor, 0, 0, 1}, + .imageOffset = {0, 0, 0}, + .imageExtent = {width, height, 1}}; + commandBuffer->copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, {region}); + endSingleTimeCommands(*commandBuffer); + } + + void loadModel() + { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn, err; + + if (!LoadObj(&attrib, &shapes, &materials, &warn, &err, MODEL_PATH.c_str())) + { + throw std::runtime_error(warn + err); + } + + std::unordered_map uniqueVertices{}; + + for (const auto &shape : shapes) + { + for (const auto &index : shape.mesh.indices) + { + Vertex vertex{}; + + vertex.pos = { + attrib.vertices[3 * index.vertex_index + 0], + attrib.vertices[3 * index.vertex_index + 1], + attrib.vertices[3 * index.vertex_index + 2]}; + + vertex.texCoord = { + attrib.texcoords[2 * index.texcoord_index + 0], + 1.0f - attrib.texcoords[2 * index.texcoord_index + 1]}; + + vertex.color = {1.0f, 1.0f, 1.0f}; + + if (!uniqueVertices.contains(vertex)) + { + uniqueVertices[vertex] = static_cast(vertices.size()); + vertices.push_back(vertex); + } + + indices.push_back(uniqueVertices[vertex]); + } + } + } + + void createVertexBuffer() + { + vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(dataStaging, vertices.data(), bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + } + + void createIndexBuffer() + { + vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(data, indices.data(), bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); + + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + } + + void createUniformBuffers() + { + uniformBuffers.clear(); + uniformBuffersMemory.clear(); + uniformBuffersMapped.clear(); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DeviceSize bufferSize = sizeof(UniformBufferObject); + vk::raii::Buffer buffer({}); + vk::raii::DeviceMemory bufferMem({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); + uniformBuffers.emplace_back(std::move(buffer)); + uniformBuffersMemory.emplace_back(std::move(bufferMem)); + uniformBuffersMapped.emplace_back(uniformBuffersMemory[i].mapMemory(0, bufferSize)); + } + } + + void createDescriptorPool() + { + std::array poolSize{ + vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT), + vk::DescriptorPoolSize(vk::DescriptorType::eCombinedImageSampler, MAX_FRAMES_IN_FLIGHT)}; + vk::DescriptorPoolCreateInfo poolInfo{ + .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, + .maxSets = MAX_FRAMES_IN_FLIGHT, + .poolSizeCount = static_cast(poolSize.size()), + .pPoolSizes = poolSize.data()}; + descriptorPool = vk::raii::DescriptorPool(device, poolInfo); + } + + void createDescriptorSets() + { + std::vector layouts(MAX_FRAMES_IN_FLIGHT, descriptorSetLayout); + vk::DescriptorSetAllocateInfo allocInfo{ + .descriptorPool = descriptorPool, + .descriptorSetCount = static_cast(layouts.size()), + .pSetLayouts = layouts.data()}; + + descriptorSets.clear(); + descriptorSets = device.allocateDescriptorSets(allocInfo); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DescriptorBufferInfo bufferInfo{ + .buffer = uniformBuffers[i], + .offset = 0, + .range = sizeof(UniformBufferObject)}; + vk::DescriptorImageInfo imageInfo{ + .sampler = textureSampler, + .imageView = textureImageView, + .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal}; + std::array descriptorWrites{ + vk::WriteDescriptorSet{ + .dstSet = descriptorSets[i], + .dstBinding = 0, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eUniformBuffer, + .pBufferInfo = &bufferInfo}, + vk::WriteDescriptorSet{ + .dstSet = descriptorSets[i], + .dstBinding = 1, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .pImageInfo = &imageInfo}}; + device.updateDescriptorSets(descriptorWrites, {}); + } + } + + void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer &buffer, vk::raii::DeviceMemory &bufferMemory) + { + vk::BufferCreateInfo bufferInfo{ + .size = size, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive}; + buffer = vk::raii::Buffer(device, bufferInfo); + vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{ + .allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + bufferMemory = vk::raii::DeviceMemory(device, allocInfo); + buffer.bindMemory(bufferMemory, 0); + } + + std::unique_ptr beginSingleTimeCommands() + { + vk::CommandBufferAllocateInfo allocInfo{ + .commandPool = commandPool, + .level = vk::CommandBufferLevel::ePrimary, + .commandBufferCount = 1}; + std::unique_ptr commandBuffer = std::make_unique(std::move(vk::raii::CommandBuffers(device, allocInfo).front())); + + vk::CommandBufferBeginInfo beginInfo{ + .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; + commandBuffer->begin(beginInfo); + + return commandBuffer; + } + + void endSingleTimeCommands(const vk::raii::CommandBuffer &commandBuffer) const + { + commandBuffer.end(); + + vk::SubmitInfo submitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandBuffer}; + queue.submit(submitInfo, nullptr); + queue.waitIdle(); + } + + void copyBuffer(vk::raii::Buffer &srcBuffer, vk::raii::Buffer &dstBuffer, vk::DeviceSize size) + { + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1}; + vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); + commandCopyBuffer.begin(vk::CommandBufferBeginInfo{.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); + commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy{.size = size}); + commandCopyBuffer.end(); + queue.submit(vk::SubmitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer}, nullptr); + queue.waitIdle(); + } + + uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) + { + vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) + { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) + { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + void createCommandBuffers() + { + commandBuffers.clear(); + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = MAX_FRAMES_IN_FLIGHT}; + commandBuffers = vk::raii::CommandBuffers(device, allocInfo); + } + + void recordCommandBuffer(uint32_t imageIndex) + { + commandBuffers[currentFrame].begin({}); + // Before starting rendering, transition the images to the appropriate layouts + // Transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL + transition_image_layout( + imageIndex, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage + ); + + // Transition the multisampled color image to COLOR_ATTACHMENT_OPTIMAL + transition_image_layout_custom( + colorImage, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, + vk::AccessFlagBits2::eColorAttachmentWrite, + vk::PipelineStageFlagBits2::eTopOfPipe, + vk::PipelineStageFlagBits2::eColorAttachmentOutput, + vk::ImageAspectFlagBits::eColor); + + // Transition the depth image to DEPTH_ATTACHMENT_OPTIMAL + transition_image_layout_custom( + depthImage, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eDepthAttachmentOptimal, + {}, + vk::AccessFlagBits2::eDepthStencilAttachmentWrite, + vk::PipelineStageFlagBits2::eTopOfPipe, + vk::PipelineStageFlagBits2::eEarlyFragmentTests, + vk::ImageAspectFlagBits::eDepth); + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::ClearValue clearDepth = vk::ClearDepthStencilValue(1.0f, 0); + + // Color attachment (multisampled) with resolve attachment + vk::RenderingAttachmentInfo colorAttachment = { + .imageView = colorImageView, + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .resolveMode = vk::ResolveModeFlagBits::eAverage, + .resolveImageView = swapChainImageViews[imageIndex], + .resolveImageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor}; + + // Depth attachment + vk::RenderingAttachmentInfo depthAttachment = { + .imageView = depthImageView, + .imageLayout = vk::ImageLayout::eDepthAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eDontCare, + .clearValue = clearDepth}; + + vk::RenderingInfo renderingInfo = { + .renderArea = {.offset = {0, 0}, .extent = swapChainExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &colorAttachment, + .pDepthAttachment = &depthAttachment}; + commandBuffers[currentFrame].beginRendering(renderingInfo); + commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint32); + commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); + commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); + commandBuffers[currentFrame].endRendering(); + // After rendering, transition the images to appropriate layouts + + // Transition the swapchain image to PRESENT_SRC + transition_image_layout( + imageIndex, + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage + ); + commandBuffers[currentFrame].end(); + } + + void transition_image_layout( + uint32_t imageIndex, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout, + vk::AccessFlags2 src_access_mask, + vk::AccessFlags2 dst_access_mask, + vk::PipelineStageFlags2 src_stage_mask, + vk::PipelineStageFlags2 dst_stage_mask) + { + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = src_stage_mask, + .srcAccessMask = src_access_mask, + .dstStageMask = dst_stage_mask, + .dstAccessMask = dst_access_mask, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo dependency_info = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + } + + void transition_image_layout_custom( + vk::raii::Image &image, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout, + vk::AccessFlags2 src_access_mask, + vk::AccessFlags2 dst_access_mask, + vk::PipelineStageFlags2 src_stage_mask, + vk::PipelineStageFlags2 dst_stage_mask, + vk::ImageAspectFlags aspect_mask) + { + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = src_stage_mask, + .srcAccessMask = src_access_mask, + .dstStageMask = dst_stage_mask, + .dstAccessMask = dst_access_mask, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = image, + .subresourceRange = { + .aspectMask = aspect_mask, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo dependency_info = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + } + + void createSyncObjects() + { + presentCompleteSemaphore.clear(); + renderFinishedSemaphore.clear(); + inFlightFences.clear(); + + for (size_t i = 0; i < swapChainImages.size(); i++) + { + presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); + } + } + + void updateUniformBuffer(uint32_t currentImage) const + { + static auto startTime = std::chrono::high_resolution_clock::now(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + UniformBufferObject ubo{}; + ubo.model = rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.view = lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); + ubo.proj[1][1] *= -1; + + memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); + } + + void drawFrame() + { + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + ; + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr); + + if (result == vk::Result::eErrorOutOfDateKHR) + { + recreateSwapChain(); + return; + } + if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) + { + throw std::runtime_error("failed to acquire swap chain image!"); + } + updateUniformBuffer(currentFrame); + + device.resetFences(*inFlightFences[currentFrame]); + commandBuffers[currentFrame].reset(); + recordCommandBuffer(imageIndex); + + vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[currentFrame]); + + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + result = queue.presentKHR(presentInfoKHR); + if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) + { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != vk::Result::eSuccess) + { + throw std::runtime_error("failed to present swap chain image!"); + } + semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size(), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static vk::Format chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + const auto formatIt = std::ranges::find_if(availableFormats, + [](const auto &format) { + return format.format == vk::Format::eB8G8R8A8Srgb && + format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; + }); + return formatIt != availableFormats.end() ? formatIt->format : availableFormats[0].format; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + vk::PresentModeKHR::eFifo; + } + + [[nodiscard]] vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR &capabilities) const + { + 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)}; + } + + [[nodiscard]] std::vector getRequiredExtensions() const + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + 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; + } }; -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/32_ecosystem_utilities.cpp b/attachments/32_ecosystem_utilities.cpp index 8375ad05..4a15748a 100644 --- a/attachments/32_ecosystem_utilities.cpp +++ b/attachments/32_ecosystem_utilities.cpp @@ -1,24 +1,24 @@ -#include -#include -#include -#include -#include -#include -#include #include -#include #include #include +#include +#include +#include +#include +#include +#include #include +#include +#include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include #define GLM_FORCE_RADIANS @@ -34,1657 +34,1757 @@ import vulkan_hpp; #define TINYOBJLOADER_IMPLEMENTATION #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -constexpr uint64_t FenceTimeout = 100000000; -const std::string MODEL_PATH = "models/viking_room.obj"; -const std::string TEXTURE_PATH = "textures/viking_room.png"; -constexpr int MAX_FRAMES_IN_FLIGHT = 2; +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; +constexpr uint64_t FenceTimeout = 100000000; +const std::string MODEL_PATH = "models/viking_room.obj"; +const std::string TEXTURE_PATH = "textures/viking_room.png"; +constexpr int MAX_FRAMES_IN_FLIGHT = 2; // Validation layers are now managed by vulkanconfig instead of being hard-coded // See the Ecosystem Utilities chapter for details on using vulkanconfig // Application info structure to store feature support flags -struct AppInfo { - bool dynamicRenderingSupported = false; - bool timelineSemaphoresSupported = false; - bool synchronization2Supported = false; +struct AppInfo +{ + bool dynamicRenderingSupported = false; + bool timelineSemaphoresSupported = false; + bool synchronization2Supported = false; }; -struct Vertex { - glm::vec3 pos; - glm::vec3 color; - glm::vec2 texCoord; - - static vk::VertexInputBindingDescription getBindingDescription() { - return { 0, sizeof(Vertex), vk::VertexInputRate::eVertex }; - } - - static std::array getAttributeDescriptions() { - return { - vk::VertexInputAttributeDescription( 0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, pos) ), - vk::VertexInputAttributeDescription( 1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color) ), - vk::VertexInputAttributeDescription( 2, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, texCoord) ) - }; - } - - bool operator==(const Vertex& other) const { - return pos == other.pos && color == other.color && texCoord == other.texCoord; - } +struct Vertex +{ + glm::vec3 pos; + glm::vec3 color; + glm::vec2 texCoord; + + static vk::VertexInputBindingDescription getBindingDescription() + { + return {0, sizeof(Vertex), vk::VertexInputRate::eVertex}; + } + + static std::array getAttributeDescriptions() + { + return { + vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, pos)), + vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color)), + vk::VertexInputAttributeDescription(2, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, texCoord))}; + } + + bool operator==(const Vertex &other) const + { + return pos == other.pos && color == other.color && texCoord == other.texCoord; + } }; -template<> struct std::hash { - size_t operator()(Vertex const& vertex) const noexcept { - return ((hash()(vertex.pos) ^ (hash()(vertex.color) << 1)) >> 1) ^ (hash()(vertex.texCoord) << 1); - } - }; +template <> +struct std::hash +{ + size_t operator()(Vertex const &vertex) const noexcept + { + return ((hash()(vertex.pos) ^ (hash()(vertex.color) << 1)) >> 1) ^ (hash()(vertex.texCoord) << 1); + } +}; -struct UniformBufferObject { - alignas(16) glm::mat4 model; - alignas(16) glm::mat4 view; - alignas(16) glm::mat4 proj; +struct UniformBufferObject +{ + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; }; -class HelloTriangleApplication { -public: - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } - -private: - AppInfo appInfo; - - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::SampleCountFlagBits msaaSamples = vk::SampleCountFlagBits::e1; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::Format swapChainImageFormat = vk::Format::eUndefined; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - // Traditional render pass (fallback for non-dynamic rendering) - vk::raii::RenderPass renderPass = nullptr; - std::vector swapChainFramebuffers; - - vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - - vk::raii::Image colorImage = nullptr; - vk::raii::DeviceMemory colorImageMemory = nullptr; - vk::raii::ImageView colorImageView = nullptr; - - vk::raii::Image depthImage = nullptr; - vk::raii::DeviceMemory depthImageMemory = nullptr; - vk::raii::ImageView depthImageView = nullptr; - - uint32_t mipLevels = 0; - vk::raii::Image textureImage = nullptr; - vk::raii::DeviceMemory textureImageMemory = nullptr; - vk::raii::ImageView textureImageView = nullptr; - vk::raii::Sampler textureSampler = nullptr; - - std::vector vertices; - std::vector indices; - vk::raii::Buffer vertexBuffer = nullptr; - vk::raii::DeviceMemory vertexBufferMemory = nullptr; - vk::raii::Buffer indexBuffer = nullptr; - vk::raii::DeviceMemory indexBufferMemory = nullptr; - - std::vector uniformBuffers; - std::vector uniformBuffersMemory; - std::vector uniformBuffersMapped; - - vk::raii::DescriptorPool descriptorPool = nullptr; - std::vector descriptorSets; - - vk::raii::CommandPool commandPool = nullptr; - std::vector commandBuffers; - - // Synchronization objects - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; - std::vector inFlightFences; - vk::raii::Semaphore timelineSemaphore = nullptr; - uint64_t timelineValue = 0; - uint32_t currentFrame = 0; - - bool framebufferResized = false; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan Compatibility Example", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); - } - - static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { - auto app = static_cast(glfwGetWindowUserPointer(window)); - app->framebufferResized = true; - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - createSurface(); - pickPhysicalDevice(); - detectFeatureSupport(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - - // Create traditional render pass if dynamic rendering is not supported - if (!appInfo.dynamicRenderingSupported) { - createRenderPass(); - createFramebuffers(); - } - - createDescriptorSetLayout(); - createGraphicsPipeline(); - createCommandPool(); - createColorResources(); - createDepthResources(); - createTextureImage(); - createTextureImageView(); - createTextureSampler(); - loadModel(); - createVertexBuffer(); - createIndexBuffer(); - createUniformBuffers(); - createDescriptorPool(); - createDescriptorSets(); - createCommandBuffers(); - createSyncObjects(); - - // Print feature support summary - std::cout << "\nFeature support summary:\n"; - std::cout << "- Dynamic Rendering: " << (appInfo.dynamicRenderingSupported ? "Yes" : "No") << "\n"; - std::cout << "- Timeline Semaphores: " << (appInfo.timelineSemaphoresSupported ? "Yes" : "No") << "\n"; - std::cout << "- Synchronization2: " << (appInfo.synchronization2Supported ? "Yes" : "No") << "\n"; - } - - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - drawFrame(); - } - - device.waitIdle(); - } - - void cleanupSwapChain() { - swapChainFramebuffers.clear(); - swapChainImageViews.clear(); - swapChain = nullptr; - } - - void cleanup() const { - glfwDestroyWindow(window); - glfwTerminate(); - } - - void recreateSwapChain() { - int width = 0, height = 0; - glfwGetFramebufferSize(window, &width, &height); - while (width == 0 || height == 0) { - glfwGetFramebufferSize(window, &width, &height); - glfwWaitEvents(); - } - - device.waitIdle(); - - cleanupSwapChain(); - createSwapChain(); - createImageViews(); - - // Recreate traditional render pass and framebuffers if dynamic rendering is not supported - if (!appInfo.dynamicRenderingSupported) { - createRenderPass(); - createFramebuffers(); - } - - createColorResources(); - createDepthResources(); - } - - void createInstance() { - // Validation layers are now managed by vulkanconfig instead of being hard-coded - - 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 - }; - - auto extensions = getRequiredExtensions(); - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledExtensionCount = static_cast(extensions.size()), - .ppEnabledExtensionNames = extensions.data() - }; - - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - // Always set up the debug messenger - // It will only be used if validation layers are enabled via vulkanconfig - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( - vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | - vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | - vk::DebugUtilsMessageSeverityFlagBitsEXT::eError - ); - - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( - vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | - vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | - vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation - ); - - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - - try { - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } catch (vk::SystemError& err) { - // If the debug utils extension is not available, this will fail - // That's okay; it just means validation layers aren't enabled - std::cout << "Debug messenger not available. Validation layers may not be enabled." << std::endl; - } - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - return supportsGraphics && supportsAllRequiredExtensions; - }); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - msaaSamples = getMaxUsableSampleCount(); - } - else - { - throw std::runtime_error("failed to find a suitable GPU!"); - } - } - - void detectFeatureSupport() { - // Get device properties to check Vulkan version - vk::PhysicalDeviceProperties deviceProperties = physicalDevice.getProperties(); - - // Get available extensions - std::vector availableExtensions = physicalDevice.enumerateDeviceExtensionProperties(); - - // Check for dynamic rendering support - if (deviceProperties.apiVersion >= VK_VERSION_1_3) { - appInfo.dynamicRenderingSupported = true; - std::cout << "Dynamic rendering supported via Vulkan 1.3\n"; - } else { - // Check for the extension on older Vulkan versions - for (const auto& extension : availableExtensions) { - if (strcmp(extension.extensionName, VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME) == 0) { - appInfo.dynamicRenderingSupported = true; - std::cout << "Dynamic rendering supported via extension\n"; - break; - } - } - } - - // Check for timeline semaphores support - if (deviceProperties.apiVersion >= VK_VERSION_1_2) { - appInfo.timelineSemaphoresSupported = true; - std::cout << "Timeline semaphores supported via Vulkan 1.2\n"; - } else { - // Check for the extension on older Vulkan versions - for (const auto& extension : availableExtensions) { - if (strcmp(extension.extensionName, VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME) == 0) { - appInfo.timelineSemaphoresSupported = true; - std::cout << "Timeline semaphores supported via extension\n"; - break; - } - } - } - - // Check for synchronization2 support - if (deviceProperties.apiVersion >= VK_VERSION_1_3) { - appInfo.synchronization2Supported = true; - std::cout << "Synchronization2 supported via Vulkan 1.3\n"; - } else { - // Check for the extension on older Vulkan versions - for (const auto& extension : availableExtensions) { - if (strcmp(extension.extensionName, VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME) == 0) { - appInfo.synchronization2Supported = true; - std::cout << "Synchronization2 supported via extension\n"; - break; - } - } - } - - // Add required extensions based on feature support - if (appInfo.dynamicRenderingSupported && deviceProperties.apiVersion < VK_VERSION_1_3) { - requiredDeviceExtension.push_back(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME); - } - - if (appInfo.timelineSemaphoresSupported && deviceProperties.apiVersion < VK_VERSION_1_2) { - requiredDeviceExtension.push_back(VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME); - } - - if (appInfo.synchronization2Supported && deviceProperties.apiVersion < VK_VERSION_1_3) { - requiredDeviceExtension.push_back(VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // Create device with appropriate features - auto features = physicalDevice.getFeatures2(); - - // Setup feature chain based on detected support - void* pNext = nullptr; - - // Add dynamic rendering if supported - vk::PhysicalDeviceVulkan13Features vulkan13Features; - vk::PhysicalDeviceDynamicRenderingFeatures dynamicRenderingFeatures; - - if (appInfo.dynamicRenderingSupported) { - if (appInfo.synchronization2Supported) { - vulkan13Features.dynamicRendering = vk::True; - vulkan13Features.synchronization2 = vk::True; - vulkan13Features.pNext = pNext; - pNext = &vulkan13Features; - } else { - dynamicRenderingFeatures.dynamicRendering = vk::True; - dynamicRenderingFeatures.pNext = pNext; - pNext = &dynamicRenderingFeatures; - } - } - - // Add timeline semaphores if supported - vk::PhysicalDeviceTimelineSemaphoreFeatures timelineSemaphoreFeatures; - if (appInfo.timelineSemaphoresSupported) { - timelineSemaphoreFeatures.timelineSemaphore = vk::True; - timelineSemaphoreFeatures.pNext = pNext; - pNext = &timelineSemaphoreFeatures; - } - - features.pNext = pNext; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ - .pNext = &features, - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() - }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface); - swapChainImageFormat = 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{ - .surface = surface, .minImageCount = minImageCount, - .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, - .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 }; - - swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); - swapChainImages = swapChain.getImages(); - } - - void createImageViews() { - 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( device, imageViewCreateInfo ); - } - } - - void createRenderPass() { - if (appInfo.dynamicRenderingSupported) { - // No render pass needed with dynamic rendering - std::cout << "Using dynamic rendering, skipping render pass creation\n"; - return; - } - - std::cout << "Creating traditional render pass\n"; - - // Color attachment description - vk::AttachmentDescription colorAttachment{ - .format = swapChainImageFormat, - .samples = msaaSamples, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .stencilLoadOp = vk::AttachmentLoadOp::eDontCare, - .stencilStoreOp = vk::AttachmentStoreOp::eDontCare, - .initialLayout = vk::ImageLayout::eUndefined, - .finalLayout = vk::ImageLayout::eColorAttachmentOptimal - }; - - vk::AttachmentDescription depthAttachment{ - .format = findDepthFormat(), - .samples = msaaSamples, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eDontCare, - .stencilLoadOp = vk::AttachmentLoadOp::eDontCare, - .stencilStoreOp = vk::AttachmentStoreOp::eDontCare, - .initialLayout = vk::ImageLayout::eUndefined, - .finalLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal - }; - - vk::AttachmentDescription colorAttachmentResolve{ - .format = swapChainImageFormat, - .samples = vk::SampleCountFlagBits::e1, - .loadOp = vk::AttachmentLoadOp::eDontCare, - .storeOp = vk::AttachmentStoreOp::eStore, - .stencilLoadOp = vk::AttachmentLoadOp::eDontCare, - .stencilStoreOp = vk::AttachmentStoreOp::eDontCare, - .initialLayout = vk::ImageLayout::eUndefined, - .finalLayout = vk::ImageLayout::ePresentSrcKHR - }; - - // Subpass references - vk::AttachmentReference colorAttachmentRef{ - .attachment = 0, - .layout = vk::ImageLayout::eColorAttachmentOptimal - }; - - vk::AttachmentReference depthAttachmentRef{ - .attachment = 1, - .layout = vk::ImageLayout::eDepthStencilAttachmentOptimal - }; - - vk::AttachmentReference colorAttachmentResolveRef{ - .attachment = 2, - .layout = vk::ImageLayout::eColorAttachmentOptimal - }; - - // Subpass description - vk::SubpassDescription subpass{ - .pipelineBindPoint = vk::PipelineBindPoint::eGraphics, - .colorAttachmentCount = 1, - .pColorAttachments = &colorAttachmentRef, - .pResolveAttachments = &colorAttachmentResolveRef, - .pDepthStencilAttachment = &depthAttachmentRef - }; - - // Dependency to ensure proper image layout transitions - vk::SubpassDependency dependency{ - .srcSubpass = VK_SUBPASS_EXTERNAL, - .dstSubpass = 0, - .srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eEarlyFragmentTests, - .dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eEarlyFragmentTests, - .srcAccessMask = vk::AccessFlagBits::eNone, - .dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite | vk::AccessFlagBits::eDepthStencilAttachmentWrite - }; - - // Create the render pass - std::array attachments = { colorAttachment, depthAttachment, colorAttachmentResolve }; - vk::RenderPassCreateInfo renderPassInfo{ - .attachmentCount = static_cast(attachments.size()), - .pAttachments = attachments.data(), - .subpassCount = 1, - .pSubpasses = &subpass, - .dependencyCount = 1, - .pDependencies = &dependency - }; - - renderPass = vk::raii::RenderPass(device, renderPassInfo); - } - - void createFramebuffers() { - if (appInfo.dynamicRenderingSupported) { - // No framebuffers needed with dynamic rendering - std::cout << "Using dynamic rendering, skipping framebuffer creation\n"; - return; - } - - std::cout << "Creating traditional framebuffers\n"; - - swapChainFramebuffers.clear(); - - for (size_t i = 0; i < swapChainImageViews.size(); i++) { - std::array attachments = { - *colorImageView, - *depthImageView, - *swapChainImageViews[i] - }; - - vk::FramebufferCreateInfo framebufferInfo{ - .renderPass = *renderPass, - .attachmentCount = static_cast(attachments.size()), - .pAttachments = attachments.data(), - .width = swapChainExtent.width, - .height = swapChainExtent.height, - .layers = 1 - }; - - swapChainFramebuffers.emplace_back(device, framebufferInfo); - } - } - - void createDescriptorSetLayout() { - std::array bindings = { - vk::DescriptorSetLayoutBinding( 0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr), - vk::DescriptorSetLayoutBinding( 1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment, nullptr) - }; - - vk::DescriptorSetLayoutCreateInfo layoutInfo{ .bindingCount = static_cast(bindings.size()), .pBindings = bindings.data() }; - descriptorSetLayout = vk::raii::DescriptorSetLayout(device, layoutInfo); - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; - - auto bindingDescription = Vertex::getBindingDescription(); - auto attributeDescriptions = Vertex::getAttributeDescriptions(); - vk::PipelineVertexInputStateCreateInfo vertexInputInfo{ - .vertexBindingDescriptionCount = 1, - .pVertexBindingDescriptions = &bindingDescription, - .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), - .pVertexAttributeDescriptions = attributeDescriptions.data() - }; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ - .topology = vk::PrimitiveTopology::eTriangleList, - .primitiveRestartEnable = vk::False - }; - vk::PipelineViewportStateCreateInfo viewportState{ - .viewportCount = 1, - .scissorCount = 1 - }; - vk::PipelineRasterizationStateCreateInfo rasterizer{ - .depthClampEnable = vk::False, - .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, - .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eCounterClockwise, - .depthBiasEnable = vk::False - }; - rasterizer.lineWidth = 1.0f; - vk::PipelineMultisampleStateCreateInfo multisampling{ - .rasterizationSamples = msaaSamples, - .sampleShadingEnable = vk::False - }; - vk::PipelineDepthStencilStateCreateInfo depthStencil{ - .depthTestEnable = vk::True, - .depthWriteEnable = vk::True, - .depthCompareOp = vk::CompareOp::eLess, - .depthBoundsTestEnable = vk::False, - .stencilTestEnable = vk::False - }; - vk::PipelineColorBlendAttachmentState colorBlendAttachment; - colorBlendAttachment.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA; - colorBlendAttachment.blendEnable = vk::False; - - vk::PipelineColorBlendStateCreateInfo colorBlending{ - .logicOpEnable = vk::False, - .logicOp = vk::LogicOp::eCopy, - .attachmentCount = 1, - .pAttachments = &colorBlendAttachment - }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0 }; - - pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); - - vk::GraphicsPipelineCreateInfo pipelineInfo{ - .stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pDepthStencilState = &depthStencil, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = pipelineLayout - }; - - // Configure pipeline based on dynamic rendering support - vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo; - if (appInfo.dynamicRenderingSupported) { - std::cout << "Configuring pipeline for dynamic rendering\n"; - pipelineRenderingCreateInfo.colorAttachmentCount = 1; - pipelineRenderingCreateInfo.pColorAttachmentFormats = &swapChainImageFormat; - pipelineRenderingCreateInfo.depthAttachmentFormat = findDepthFormat(); - - pipelineInfo.pNext = &pipelineRenderingCreateInfo; - pipelineInfo.renderPass = nullptr; - } else { - std::cout << "Configuring pipeline for traditional render pass\n"; - pipelineInfo.pNext = nullptr; - pipelineInfo.renderPass = *renderPass; - pipelineInfo.subpass = 0; - } - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{ - .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex - }; - commandPool = vk::raii::CommandPool(device, poolInfo); - } - - void createColorResources() { - vk::Format colorFormat = swapChainImageFormat; - - createImage(swapChainExtent.width, swapChainExtent.height, 1, msaaSamples, colorFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransientAttachment | vk::ImageUsageFlagBits::eColorAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, colorImage, colorImageMemory); - colorImageView = createImageView(colorImage, colorFormat, vk::ImageAspectFlagBits::eColor, 1); - } - - void createDepthResources() { - vk::Format depthFormat = findDepthFormat(); - - createImage(swapChainExtent.width, swapChainExtent.height, 1, msaaSamples, depthFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eDepthStencilAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, depthImage, depthImageMemory); - depthImageView = createImageView(depthImage, depthFormat, vk::ImageAspectFlagBits::eDepth, 1); - } - - vk::Format findSupportedFormat(const std::vector& candidates, vk::ImageTiling tiling, vk::FormatFeatureFlags features) const { - for (const auto format : candidates) { - vk::FormatProperties props = physicalDevice.getFormatProperties(format); - - if (tiling == vk::ImageTiling::eLinear && (props.linearTilingFeatures & features) == features) { - return format; - } - if (tiling == vk::ImageTiling::eOptimal && (props.optimalTilingFeatures & features) == features) { - return format; - } - } - - throw std::runtime_error("failed to find supported format!"); - } - - [[nodiscard]] vk::Format findDepthFormat() const { - return findSupportedFormat( - {vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, vk::Format::eD24UnormS8Uint}, - vk::ImageTiling::eOptimal, - vk::FormatFeatureFlagBits::eDepthStencilAttachment - ); - } - - static bool hasStencilComponent(vk::Format format) { - return format == vk::Format::eD32SfloatS8Uint || format == vk::Format::eD24UnormS8Uint; - } - - void createTextureImage() { - int texWidth, texHeight, texChannels; - stbi_uc* pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); - vk::DeviceSize imageSize = texWidth * texHeight * 4; - mipLevels = static_cast(std::floor(std::log2(std::max(texWidth, texHeight)))) + 1; - - if (!pixels) { - throw std::runtime_error("failed to load texture image!"); - } - - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, imageSize); - memcpy(data, pixels, imageSize); - stagingBufferMemory.unmapMemory(); - - stbi_image_free(pixels); - - createImage(texWidth, texHeight, mipLevels, vk::SampleCountFlagBits::e1, vk::Format::eR8G8B8A8Srgb, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory); - - transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, mipLevels); - copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); - - generateMipmaps(textureImage, vk::Format::eR8G8B8A8Srgb, texWidth, texHeight, mipLevels); - } - - void generateMipmaps(vk::raii::Image& image, vk::Format imageFormat, int32_t texWidth, int32_t texHeight, uint32_t mipLevels) { - // Check if image format supports linear blit-ing - vk::FormatProperties formatProperties = physicalDevice.getFormatProperties(imageFormat); - - if (!(formatProperties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eSampledImageFilterLinear)) { - throw std::runtime_error("texture image format does not support linear blitting!"); - } - - std::unique_ptr commandBuffer = beginSingleTimeCommands(); - - vk::ImageMemoryBarrier barrier = { .srcAccessMask = vk::AccessFlagBits::eTransferWrite, .dstAccessMask =vk::AccessFlagBits::eTransferRead - , .oldLayout = vk::ImageLayout::eTransferDstOptimal, .newLayout = vk::ImageLayout::eTransferSrcOptimal - , .srcQueueFamilyIndex = vk::QueueFamilyIgnored, .dstQueueFamilyIndex = vk::QueueFamilyIgnored, .image = image }; - barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; - barrier.subresourceRange.baseArrayLayer = 0; - barrier.subresourceRange.layerCount = 1; - barrier.subresourceRange.levelCount = 1; - - int32_t mipWidth = texWidth; - int32_t mipHeight = texHeight; - - for (uint32_t i = 1; i < mipLevels; i++) { - barrier.subresourceRange.baseMipLevel = i - 1; - barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal; - barrier.newLayout = vk::ImageLayout::eTransferSrcOptimal; - barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; - barrier.dstAccessMask = vk::AccessFlagBits::eTransferRead; - - commandBuffer->pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer, {}, {}, {}, barrier); - - vk::ArrayWrapper1D offsets, dstOffsets; - offsets[0] = vk::Offset3D(0, 0, 0); - offsets[1] = vk::Offset3D(mipWidth, mipHeight, 1); - dstOffsets[0] = vk::Offset3D(0, 0, 0); - dstOffsets[1] = vk::Offset3D(mipWidth > 1 ? mipWidth / 2 : 1, mipHeight > 1 ? mipHeight / 2 : 1, 1); - vk::ImageBlit blit = { .srcSubresource = {}, .srcOffsets = offsets, - .dstSubresource = {}, .dstOffsets = dstOffsets }; - blit.srcSubresource = vk::ImageSubresourceLayers( vk::ImageAspectFlagBits::eColor, i - 1, 0, 1); - blit.dstSubresource = vk::ImageSubresourceLayers( vk::ImageAspectFlagBits::eColor, i, 0, 1); - - commandBuffer->blitImage(image, vk::ImageLayout::eTransferSrcOptimal, image, vk::ImageLayout::eTransferDstOptimal, { blit }, vk::Filter::eLinear); - - barrier.oldLayout = vk::ImageLayout::eTransferSrcOptimal; - barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal; - barrier.srcAccessMask = vk::AccessFlagBits::eTransferRead; - barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - - commandBuffer->pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {}, barrier); - - if (mipWidth > 1) mipWidth /= 2; - if (mipHeight > 1) mipHeight /= 2; - } - - barrier.subresourceRange.baseMipLevel = mipLevels - 1; - barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal; - barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal; - barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; - barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - - commandBuffer->pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {}, barrier); - - endSingleTimeCommands(*commandBuffer); - } - - vk::SampleCountFlagBits getMaxUsableSampleCount() { - vk::PhysicalDeviceProperties physicalDeviceProperties = physicalDevice.getProperties(); - - vk::SampleCountFlags counts = physicalDeviceProperties.limits.framebufferColorSampleCounts & physicalDeviceProperties.limits.framebufferDepthSampleCounts; - if (counts & vk::SampleCountFlagBits::e64) { return vk::SampleCountFlagBits::e64; } - if (counts & vk::SampleCountFlagBits::e32) { return vk::SampleCountFlagBits::e32; } - if (counts & vk::SampleCountFlagBits::e16) { return vk::SampleCountFlagBits::e16; } - if (counts & vk::SampleCountFlagBits::e8) { return vk::SampleCountFlagBits::e8; } - if (counts & vk::SampleCountFlagBits::e4) { return vk::SampleCountFlagBits::e4; } - if (counts & vk::SampleCountFlagBits::e2) { return vk::SampleCountFlagBits::e2; } - - return vk::SampleCountFlagBits::e1; - } - - void createTextureImageView() { - textureImageView = createImageView(textureImage, vk::Format::eR8G8B8A8Srgb, vk::ImageAspectFlagBits::eColor, mipLevels); - } - - void createTextureSampler() { - vk::PhysicalDeviceProperties properties = physicalDevice.getProperties(); - vk::SamplerCreateInfo samplerInfo { - .magFilter = vk::Filter::eLinear, - .minFilter = vk::Filter::eLinear, - .mipmapMode = vk::SamplerMipmapMode::eLinear, - .addressModeU = vk::SamplerAddressMode::eRepeat, - .addressModeV = vk::SamplerAddressMode::eRepeat, - .addressModeW = vk::SamplerAddressMode::eRepeat, - .mipLodBias = 0.0f, - .anisotropyEnable = vk::True, - .maxAnisotropy = properties.limits.maxSamplerAnisotropy, - .compareEnable = vk::False, - .compareOp = vk::CompareOp::eAlways - }; - textureSampler = vk::raii::Sampler(device, samplerInfo); - } - - [[nodiscard]] vk::raii::ImageView createImageView(const vk::raii::Image& image, vk::Format format, vk::ImageAspectFlags aspectFlags, uint32_t mipLevels) const { - vk::ImageViewCreateInfo viewInfo{ - .image = image, - .viewType = vk::ImageViewType::e2D, - .format = format, - .subresourceRange = { aspectFlags, 0, mipLevels, 0, 1 } - }; - return vk::raii::ImageView( device, viewInfo ); - } - - void createImage(uint32_t width, uint32_t height, uint32_t mipLevels, vk::SampleCountFlagBits numSamples, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image& image, vk::raii::DeviceMemory& imageMemory) { - vk::ImageCreateInfo imageInfo{ - .imageType = vk::ImageType::e2D, - .format = format, - .extent = {width, height, 1}, - .mipLevels = mipLevels, - .arrayLayers = 1, - .samples = numSamples, - .tiling = tiling, - .usage = usage, - .sharingMode = vk::SharingMode::eExclusive, - .initialLayout = vk::ImageLayout::eUndefined - }; - image = vk::raii::Image(device, imageInfo); - - vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ - .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) - }; - imageMemory = vk::raii::DeviceMemory(device, allocInfo); - image.bindMemory(imageMemory, 0); - } - - void transitionImageLayout(const vk::raii::Image& image, const vk::ImageLayout oldLayout, const vk::ImageLayout newLayout, uint32_t mipLevels) { - const auto commandBuffer = beginSingleTimeCommands(); - - if (appInfo.synchronization2Supported) { - // Use Synchronization2 API - vk::ImageMemoryBarrier2 barrier{ - .srcStageMask = vk::PipelineStageFlagBits2::eAllCommands, - .dstStageMask = vk::PipelineStageFlagBits2::eAllCommands, - .oldLayout = oldLayout, - .newLayout = newLayout, - .image = image, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, mipLevels, 0, 1 } - }; - - if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) { - barrier.srcAccessMask = vk::AccessFlagBits2::eNone; - barrier.dstAccessMask = vk::AccessFlagBits2::eTransferWrite; - barrier.srcStageMask = vk::PipelineStageFlagBits2::eTopOfPipe; - barrier.dstStageMask = vk::PipelineStageFlagBits2::eTransfer; - } else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) { - barrier.srcAccessMask = vk::AccessFlagBits2::eTransferWrite; - barrier.dstAccessMask = vk::AccessFlagBits2::eShaderRead; - barrier.srcStageMask = vk::PipelineStageFlagBits2::eTransfer; - barrier.dstStageMask = vk::PipelineStageFlagBits2::eFragmentShader; - } else { - throw std::invalid_argument("unsupported layout transition!"); - } - - vk::DependencyInfo dependencyInfo{ - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - - commandBuffer->pipelineBarrier2(dependencyInfo); - } else { - // Use traditional synchronization API - vk::ImageMemoryBarrier barrier{ - .oldLayout = oldLayout, - .newLayout = newLayout, - .image = image, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, mipLevels, 0, 1 } - }; - - vk::PipelineStageFlags sourceStage; - vk::PipelineStageFlags destinationStage; - - if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) { - barrier.srcAccessMask = {}; - barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; - - sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; - destinationStage = vk::PipelineStageFlagBits::eTransfer; - } else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) { - barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; - barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - - sourceStage = vk::PipelineStageFlagBits::eTransfer; - destinationStage = vk::PipelineStageFlagBits::eFragmentShader; - } else { - throw std::invalid_argument("unsupported layout transition!"); - } - commandBuffer->pipelineBarrier(sourceStage, destinationStage, {}, {}, nullptr, barrier); - } - - endSingleTimeCommands(*commandBuffer); - } - - void copyBufferToImage(const vk::raii::Buffer& buffer, const vk::raii::Image& image, uint32_t width, uint32_t height) { - std::unique_ptr commandBuffer = beginSingleTimeCommands(); - vk::BufferImageCopy region{ - .bufferOffset = 0, - .bufferRowLength = 0, - .bufferImageHeight = 0, - .imageSubresource = { vk::ImageAspectFlagBits::eColor, 0, 0, 1 }, - .imageOffset = {0, 0, 0}, - .imageExtent = {width, height, 1} - }; - commandBuffer->copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, {region}); - endSingleTimeCommands(*commandBuffer); - } - - void loadModel() { - tinyobj::attrib_t attrib; - std::vector shapes; - std::vector materials; - std::string warn, err; - - if (!LoadObj(&attrib, &shapes, &materials, &warn, &err, MODEL_PATH.c_str())) { - throw std::runtime_error(warn + err); - } - - std::unordered_map uniqueVertices{}; - - for (const auto& shape : shapes) { - for (const auto& index : shape.mesh.indices) { - Vertex vertex{}; - - vertex.pos = { - attrib.vertices[3 * index.vertex_index + 0], - attrib.vertices[3 * index.vertex_index + 1], - attrib.vertices[3 * index.vertex_index + 2] - }; - - vertex.texCoord = { - attrib.texcoords[2 * index.texcoord_index + 0], - 1.0f - attrib.texcoords[2 * index.texcoord_index + 1] - }; - - vertex.color = {1.0f, 1.0f, 1.0f}; - - if (!uniqueVertices.contains(vertex)) { - uniqueVertices[vertex] = static_cast(vertices.size()); - vertices.push_back(vertex); - } - - indices.push_back(uniqueVertices[vertex]); - } - } - } - - void createVertexBuffer() { - vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(dataStaging, vertices.data(), bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); - - copyBuffer(stagingBuffer, vertexBuffer, bufferSize); - } - - void createIndexBuffer() { - vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); - - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(data, indices.data(), bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); - - copyBuffer(stagingBuffer, indexBuffer, bufferSize); - } - - void createUniformBuffers() { - uniformBuffers.clear(); - uniformBuffersMemory.clear(); - uniformBuffersMapped.clear(); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DeviceSize bufferSize = sizeof(UniformBufferObject); - vk::raii::Buffer buffer({}); - vk::raii::DeviceMemory bufferMem({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); - uniformBuffers.emplace_back(std::move(buffer)); - uniformBuffersMemory.emplace_back(std::move(bufferMem)); - uniformBuffersMapped.emplace_back(uniformBuffersMemory[i].mapMemory(0, bufferSize)); - } - } - - void createDescriptorPool() { - std::array poolSize { - vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT), - vk::DescriptorPoolSize(vk::DescriptorType::eCombinedImageSampler, MAX_FRAMES_IN_FLIGHT) - }; - vk::DescriptorPoolCreateInfo poolInfo{ - .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, - .maxSets = MAX_FRAMES_IN_FLIGHT, - .poolSizeCount = static_cast(poolSize.size()), - .pPoolSizes = poolSize.data() - }; - descriptorPool = vk::raii::DescriptorPool(device, poolInfo); - } - - void createDescriptorSets() { - std::vector layouts(MAX_FRAMES_IN_FLIGHT, descriptorSetLayout); - vk::DescriptorSetAllocateInfo allocInfo{ - .descriptorPool = descriptorPool, - .descriptorSetCount = static_cast(layouts.size()), - .pSetLayouts = layouts.data() - }; - - descriptorSets.clear(); - descriptorSets = device.allocateDescriptorSets(allocInfo); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DescriptorBufferInfo bufferInfo{ - .buffer = uniformBuffers[i], - .offset = 0, - .range = sizeof(UniformBufferObject) - }; - vk::DescriptorImageInfo imageInfo{ - .sampler = textureSampler, - .imageView = textureImageView, - .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal - }; - std::array descriptorWrites{ - vk::WriteDescriptorSet{ - .dstSet = descriptorSets[i], - .dstBinding = 0, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eUniformBuffer, - .pBufferInfo = &bufferInfo - }, - vk::WriteDescriptorSet{ - .dstSet = descriptorSets[i], - .dstBinding = 1, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eCombinedImageSampler, - .pImageInfo = &imageInfo - } - }; - device.updateDescriptorSets(descriptorWrites, {}); - } - } - - void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer& buffer, vk::raii::DeviceMemory& bufferMemory) { - vk::BufferCreateInfo bufferInfo{ - .size = size, - .usage = usage, - .sharingMode = vk::SharingMode::eExclusive - }; - buffer = vk::raii::Buffer(device, bufferInfo); - vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ - .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) - }; - bufferMemory = vk::raii::DeviceMemory(device, allocInfo); - buffer.bindMemory(bufferMemory, 0); - } - - std::unique_ptr beginSingleTimeCommands() { - vk::CommandBufferAllocateInfo allocInfo{ - .commandPool = commandPool, - .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = 1 - }; - std::unique_ptr commandBuffer = std::make_unique(std::move(vk::raii::CommandBuffers(device, allocInfo).front())); - - vk::CommandBufferBeginInfo beginInfo{ - .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit - }; - commandBuffer->begin(beginInfo); - - return commandBuffer; - } - - void endSingleTimeCommands(const vk::raii::CommandBuffer& commandBuffer) const { - commandBuffer.end(); - - vk::SubmitInfo submitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandBuffer }; - queue.submit(submitInfo, nullptr); - queue.waitIdle(); - } - - void copyBuffer(vk::raii::Buffer & srcBuffer, vk::raii::Buffer & dstBuffer, vk::DeviceSize size) { - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1 }; - vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); - commandCopyBuffer.begin(vk::CommandBufferBeginInfo{ .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit }); - commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy{ .size = size }); - commandCopyBuffer.end(); - queue.submit(vk::SubmitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer }, nullptr); - queue.waitIdle(); - } - - uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) { - vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); - - for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { - if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { - return i; - } - } - - throw std::runtime_error("failed to find suitable memory type!"); - } - - void createCommandBuffers() { - commandBuffers.clear(); - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = MAX_FRAMES_IN_FLIGHT }; - commandBuffers = vk::raii::CommandBuffers(device, allocInfo); - } - - void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin({}); - - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::ClearValue clearDepth = vk::ClearDepthStencilValue(1.0f, 0); - std::array clearValues = { clearColor, clearDepth }; - - if (appInfo.dynamicRenderingSupported) { - // Transition attachments to the correct layout - if (appInfo.synchronization2Supported) { - // Use Synchronization2 API for image transitions - vk::ImageMemoryBarrier2 colorBarrier{ - .srcStageMask = vk::PipelineStageFlagBits2::eTopOfPipe, - .srcAccessMask = vk::AccessFlagBits2::eNone, - .dstStageMask = vk::PipelineStageFlagBits2::eColorAttachmentOutput, - .dstAccessMask = vk::AccessFlagBits2::eColorAttachmentWrite, - .oldLayout = vk::ImageLayout::eUndefined, - .newLayout = vk::ImageLayout::eColorAttachmentOptimal, - .image = *colorImage, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } - }; - - vk::ImageMemoryBarrier2 depthBarrier{ - .srcStageMask = vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, - .srcAccessMask = vk::AccessFlagBits2::eNone, - .dstStageMask = vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, - .dstAccessMask = vk::AccessFlagBits2::eDepthStencilAttachmentWrite, - .oldLayout = vk::ImageLayout::eUndefined, - .newLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal, - .image = *depthImage, - .subresourceRange = { vk::ImageAspectFlagBits::eDepth, 0, 1, 0, 1 } - }; - - vk::ImageMemoryBarrier2 swapchainBarrier{ - .srcStageMask = vk::PipelineStageFlagBits2::eTopOfPipe, - .srcAccessMask = vk::AccessFlagBits2::eNone, - .dstStageMask = vk::PipelineStageFlagBits2::eColorAttachmentOutput, - .dstAccessMask = vk::AccessFlagBits2::eColorAttachmentWrite, - .oldLayout = vk::ImageLayout::eUndefined, - .newLayout = vk::ImageLayout::eColorAttachmentOptimal, - .image = swapChainImages[imageIndex], - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } - }; - - std::array barriers = { colorBarrier, depthBarrier, swapchainBarrier }; - vk::DependencyInfo dependencyInfo{ - .imageMemoryBarrierCount = static_cast(barriers.size()), - .pImageMemoryBarriers = barriers.data() - }; - - commandBuffers[currentFrame].pipelineBarrier2(dependencyInfo); - } else { - // Use traditional synchronization API - vk::ImageMemoryBarrier colorBarrier{ - .srcAccessMask = vk::AccessFlagBits::eNone, - .dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite, - .oldLayout = vk::ImageLayout::eUndefined, - .newLayout = vk::ImageLayout::eColorAttachmentOptimal, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = *colorImage, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } - }; - - vk::ImageMemoryBarrier depthBarrier{ - .srcAccessMask = vk::AccessFlagBits::eNone, - .dstAccessMask = vk::AccessFlagBits::eDepthStencilAttachmentWrite, - .oldLayout = vk::ImageLayout::eUndefined, - .newLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = *depthImage, - .subresourceRange = { vk::ImageAspectFlagBits::eDepth, 0, 1, 0, 1 } - }; - - vk::ImageMemoryBarrier swapchainBarrier{ - .srcAccessMask = vk::AccessFlagBits::eNone, - .dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite, - .oldLayout = vk::ImageLayout::eUndefined, - .newLayout = vk::ImageLayout::eColorAttachmentOptimal, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } - }; - - std::array barriers = { colorBarrier, depthBarrier, swapchainBarrier }; - commandBuffers[currentFrame].pipelineBarrier( - vk::PipelineStageFlagBits::eTopOfPipe, - vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eEarlyFragmentTests, - vk::DependencyFlagBits::eByRegion, - {}, - {}, - barriers - ); - } - - // Setup rendering attachments - vk::RenderingAttachmentInfo colorAttachment{ - .imageView = *colorImageView, - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .resolveMode = vk::ResolveModeFlagBits::eAverage, - .resolveImageView = *swapChainImageViews[imageIndex], - .resolveImageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor - }; - - vk::RenderingAttachmentInfo depthAttachment{ - .imageView = *depthImageView, - .imageLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eDontCare, - .clearValue = clearDepth - }; - - vk::RenderingInfo renderingInfo{ - .renderArea = {{0, 0}, swapChainExtent}, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &colorAttachment, - .pDepthAttachment = &depthAttachment - }; - - commandBuffers[currentFrame].beginRendering(renderingInfo); - } else { - // Use traditional render pass - std::cout << "Recording command buffer with traditional render pass\n"; - - vk::RenderPassBeginInfo renderPassInfo{ - .renderPass = *renderPass, - .framebuffer = *swapChainFramebuffers[imageIndex], - .renderArea = {{0, 0}, swapChainExtent}, - .clearValueCount = static_cast(clearValues.size()), - .pClearValues = clearValues.data() - }; - - commandBuffers[currentFrame].beginRenderPass(renderPassInfo, vk::SubpassContents::eInline); - } - - // Common rendering commands - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint32); - commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); - commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); - - if (appInfo.dynamicRenderingSupported) { - commandBuffers[currentFrame].endRendering(); - - // Transition swapchain image to present layout - if (appInfo.synchronization2Supported) { - vk::ImageMemoryBarrier2 barrier{ - .srcStageMask = vk::PipelineStageFlagBits2::eColorAttachmentOutput, - .srcAccessMask = vk::AccessFlagBits2::eColorAttachmentWrite, - .dstStageMask = vk::PipelineStageFlagBits2::eBottomOfPipe, - .dstAccessMask = vk::AccessFlagBits2::eNone, - .oldLayout = vk::ImageLayout::eColorAttachmentOptimal, - .newLayout = vk::ImageLayout::ePresentSrcKHR, - .image = swapChainImages[imageIndex], - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } - }; - - vk::DependencyInfo dependencyInfo{ - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - - commandBuffers[currentFrame].pipelineBarrier2(dependencyInfo); - } else { - vk::ImageMemoryBarrier barrier{ - .srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite, - .dstAccessMask = vk::AccessFlagBits::eNone, - .oldLayout = vk::ImageLayout::eColorAttachmentOptimal, - .newLayout = vk::ImageLayout::ePresentSrcKHR, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } - }; - - commandBuffers[currentFrame].pipelineBarrier( - vk::PipelineStageFlagBits::eColorAttachmentOutput, - vk::PipelineStageFlagBits::eBottomOfPipe, - vk::DependencyFlagBits::eByRegion, - {}, - {}, - { barrier } - ); - } - } else { - commandBuffers[currentFrame].endRenderPass(); - } - - commandBuffers[currentFrame].end(); - } - - void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); - - if (appInfo.timelineSemaphoresSupported) { - // Create timeline semaphore - std::cout << "Creating timeline semaphores\n"; - vk::SemaphoreTypeCreateInfo timelineCreateInfo{ - .semaphoreType = vk::SemaphoreType::eTimeline, - .initialValue = 0 - }; - - vk::SemaphoreCreateInfo semaphoreInfo{ - .pNext = &timelineCreateInfo - }; - - timelineSemaphore = vk::raii::Semaphore(device, semaphoreInfo); - - // Still need binary semaphores for swapchain operations - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - } - } else { - // Create binary semaphores and fences - std::cout << "Creating binary semaphores and fences\n"; - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - } - } - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - inFlightFences.emplace_back(device, vk::FenceCreateInfo{ .flags = vk::FenceCreateFlagBits::eSignaled }); - } - } - - void updateUniformBuffer(uint32_t currentImage) const { - static auto startTime = std::chrono::high_resolution_clock::now(); - - auto currentTime = std::chrono::high_resolution_clock::now(); - float time = std::chrono::duration(currentTime - startTime).count(); - - UniformBufferObject ubo{}; - ubo.model = rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.view = lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); - ubo.proj[1][1] *= -1; - - memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); - } - - void drawFrame() { - while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, FenceTimeout)) - ; - auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[currentFrame], nullptr); - - if (result == vk::Result::eErrorOutOfDateKHR) { - recreateSwapChain(); - return; - } - if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) { - throw std::runtime_error("failed to acquire swap chain image!"); - } - updateUniformBuffer(currentFrame); - - device.resetFences(*inFlightFences[currentFrame]); - commandBuffers[currentFrame].reset(); - recordCommandBuffer(imageIndex); - - if (appInfo.timelineSemaphoresSupported) { - // Use timeline semaphores for GPU synchronization - uint64_t waitValue = timelineValue; - uint64_t signalValue = ++timelineValue; - - vk::TimelineSemaphoreSubmitInfo timelineInfo{ - .waitSemaphoreValueCount = 0, // We'll still use binary semaphore for swapchain - .signalSemaphoreValueCount = 1, - .pSignalSemaphoreValues = &signalValue - }; - - std::array waitSemaphores = { *presentCompleteSemaphore[currentFrame], *timelineSemaphore }; - std::array waitStages = { vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::PipelineStageFlagBits::eVertexInput }; - std::array waitValues = { 0, waitValue }; // Binary semaphore value is ignored - - std::array signalSemaphores = { *renderFinishedSemaphore[currentFrame], *timelineSemaphore }; - std::array signalValues = { 0, signalValue }; // Binary semaphore value is ignored - - timelineInfo.waitSemaphoreValueCount = 1; // Only for the timeline semaphore - timelineInfo.pWaitSemaphoreValues = &waitValues[1]; - timelineInfo.signalSemaphoreValueCount = 1; // Only for the timeline semaphore - timelineInfo.pSignalSemaphoreValues = &signalValues[1]; - - vk::SubmitInfo submitInfo{ - .pNext = &timelineInfo, - .waitSemaphoreCount = 1, // Only wait on the binary semaphore - .pWaitSemaphores = &waitSemaphores[0], - .pWaitDstStageMask = &waitStages[0], - .commandBufferCount = 1, - .pCommandBuffers = &*commandBuffers[currentFrame], - .signalSemaphoreCount = 2, // Signal both semaphores - .pSignalSemaphores = signalSemaphores.data() - }; - - queue.submit(submitInfo, *inFlightFences[currentFrame]); - } else { - // Use traditional binary semaphores - vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); - const vk::SubmitInfo submitInfo{ - .waitSemaphoreCount = 1, - .pWaitSemaphores = &*presentCompleteSemaphore[currentFrame], - .pWaitDstStageMask = &waitDestinationStageMask, - .commandBufferCount = 1, - .pCommandBuffers = &*commandBuffers[currentFrame], - .signalSemaphoreCount = 1, - .pSignalSemaphores = &*renderFinishedSemaphore[currentFrame] - }; - queue.submit(submitInfo, *inFlightFences[currentFrame]); - } - - const vk::PresentInfoKHR presentInfoKHR{ - .waitSemaphoreCount = 1, - .pWaitSemaphores = &*renderFinishedSemaphore[currentFrame], - .swapchainCount = 1, - .pSwapchains = &*swapChain, - .pImageIndices = &imageIndex - }; - result = queue.presentKHR(presentInfoKHR); - if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { - framebufferResized = false; - recreateSwapChain(); - } else if (result != vk::Result::eSuccess) { - throw std::runtime_error("failed to present swap chain image!"); - } - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; - } - - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size(), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static vk::Format chooseSwapSurfaceFormat(const std::vector& availableFormats) { - return (availableFormats[0].format == vk::Format::eUndefined) ? vk::Format::eB8G8R8A8Unorm : availableFormats[0].format; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eFifo; - } - - [[nodiscard]] vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) const { - 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) - }; - } - - [[nodiscard]] std::vector getRequiredExtensions() const { - // Get the required extensions from GLFW - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - - // Check if the debug utils extension is available - std::vector props = context.enumerateInstanceExtensionProperties(); - bool debugUtilsAvailable = std::ranges::any_of(props, - [](vk::ExtensionProperties const & ep) { - return strcmp(ep.extensionName, vk::EXTDebugUtilsExtensionName) == 0; +class HelloTriangleApplication +{ + public: + void run() + { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + + private: + AppInfo appInfo; + + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::SampleCountFlagBits msaaSamples = vk::SampleCountFlagBits::e1; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::Format swapChainImageFormat = vk::Format::eUndefined; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + // Traditional render pass (fallback for non-dynamic rendering) + vk::raii::RenderPass renderPass = nullptr; + std::vector swapChainFramebuffers; + + vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + + vk::raii::Image colorImage = nullptr; + vk::raii::DeviceMemory colorImageMemory = nullptr; + vk::raii::ImageView colorImageView = nullptr; + + vk::raii::Image depthImage = nullptr; + vk::raii::DeviceMemory depthImageMemory = nullptr; + vk::raii::ImageView depthImageView = nullptr; + + uint32_t mipLevels = 0; + vk::raii::Image textureImage = nullptr; + vk::raii::DeviceMemory textureImageMemory = nullptr; + vk::raii::ImageView textureImageView = nullptr; + vk::raii::Sampler textureSampler = nullptr; + + std::vector vertices; + std::vector indices; + vk::raii::Buffer vertexBuffer = nullptr; + vk::raii::DeviceMemory vertexBufferMemory = nullptr; + vk::raii::Buffer indexBuffer = nullptr; + vk::raii::DeviceMemory indexBufferMemory = nullptr; + + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + std::vector uniformBuffersMapped; + + vk::raii::DescriptorPool descriptorPool = nullptr; + std::vector descriptorSets; + + vk::raii::CommandPool commandPool = nullptr; + std::vector commandBuffers; + + // Synchronization objects + std::vector presentCompleteSemaphore; + std::vector renderFinishedSemaphore; + std::vector inFlightFences; + vk::raii::Semaphore timelineSemaphore = nullptr; + uint64_t timelineValue = 0; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan Compatibility Example", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow *window, int width, int height) + { + auto app = static_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + detectFeatureSupport(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + + // Create traditional render pass if dynamic rendering is not supported + if (!appInfo.dynamicRenderingSupported) + { + createRenderPass(); + createFramebuffers(); + } + + createDescriptorSetLayout(); + createGraphicsPipeline(); + createCommandPool(); + createColorResources(); + createDepthResources(); + createTextureImage(); + createTextureImageView(); + createTextureSampler(); + loadModel(); + createVertexBuffer(); + createIndexBuffer(); + createUniformBuffers(); + createDescriptorPool(); + createDescriptorSets(); + createCommandBuffers(); + createSyncObjects(); + + // Print feature support summary + std::cout << "\nFeature support summary:\n"; + std::cout << "- Dynamic Rendering: " << (appInfo.dynamicRenderingSupported ? "Yes" : "No") << "\n"; + std::cout << "- Timeline Semaphores: " << (appInfo.timelineSemaphoresSupported ? "Yes" : "No") << "\n"; + std::cout << "- Synchronization2: " << (appInfo.synchronization2Supported ? "Yes" : "No") << "\n"; + } + + void mainLoop() + { + while (!glfwWindowShouldClose(window)) + { + glfwPollEvents(); + drawFrame(); + } + + device.waitIdle(); + } + + void cleanupSwapChain() + { + swapChainFramebuffers.clear(); + swapChainImageViews.clear(); + swapChain = nullptr; + } + + void cleanup() const + { + glfwDestroyWindow(window); + glfwTerminate(); + } + + void recreateSwapChain() + { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) + { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + device.waitIdle(); + + cleanupSwapChain(); + createSwapChain(); + createImageViews(); + + // Recreate traditional render pass and framebuffers if dynamic rendering is not supported + if (!appInfo.dynamicRenderingSupported) + { + createRenderPass(); + createFramebuffers(); + } + + createColorResources(); + createDepthResources(); + } + + void createInstance() + { + // Validation layers are now managed by vulkanconfig instead of being hard-coded + + 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}; + + auto extensions = getRequiredExtensions(); + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledExtensionCount = static_cast(extensions.size()), + .ppEnabledExtensionNames = extensions.data()}; + + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + // Always set up the debug messenger + // It will only be used if validation layers are enabled via vulkanconfig + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( + vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | + vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | + vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( + vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | + vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | + vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + + try + { + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + catch (vk::SystemError &err) + { + // If the debug utils extension is not available, this will fail + // That's okay; it just means validation layers aren't enabled + std::cout << "Debug messenger not available. Validation layers may not be enabled." << std::endl; + } + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + return supportsGraphics && supportsAllRequiredExtensions; }); - - // Always include the debug utils extension if available - // This allows validation layers to be enabled via vulkanconfig - if (debugUtilsAvailable) { - extensions.push_back(vk::EXTDebugUtilsExtensionName); - } else { - std::cout << "VK_EXT_debug_utils extension not available. Validation layers may not work." << std::endl; - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - 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; - } + if (devIter != devices.end()) + { + physicalDevice = *devIter; + msaaSamples = getMaxUsableSampleCount(); + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void detectFeatureSupport() + { + // Get device properties to check Vulkan version + vk::PhysicalDeviceProperties deviceProperties = physicalDevice.getProperties(); + + // Get available extensions + std::vector availableExtensions = physicalDevice.enumerateDeviceExtensionProperties(); + + // Check for dynamic rendering support + if (deviceProperties.apiVersion >= VK_VERSION_1_3) + { + appInfo.dynamicRenderingSupported = true; + std::cout << "Dynamic rendering supported via Vulkan 1.3\n"; + } + else + { + // Check for the extension on older Vulkan versions + for (const auto &extension : availableExtensions) + { + if (strcmp(extension.extensionName, VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME) == 0) + { + appInfo.dynamicRenderingSupported = true; + std::cout << "Dynamic rendering supported via extension\n"; + break; + } + } + } + + // Check for timeline semaphores support + if (deviceProperties.apiVersion >= VK_VERSION_1_2) + { + appInfo.timelineSemaphoresSupported = true; + std::cout << "Timeline semaphores supported via Vulkan 1.2\n"; + } + else + { + // Check for the extension on older Vulkan versions + for (const auto &extension : availableExtensions) + { + if (strcmp(extension.extensionName, VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME) == 0) + { + appInfo.timelineSemaphoresSupported = true; + std::cout << "Timeline semaphores supported via extension\n"; + break; + } + } + } + + // Check for synchronization2 support + if (deviceProperties.apiVersion >= VK_VERSION_1_3) + { + appInfo.synchronization2Supported = true; + std::cout << "Synchronization2 supported via Vulkan 1.3\n"; + } + else + { + // Check for the extension on older Vulkan versions + for (const auto &extension : availableExtensions) + { + if (strcmp(extension.extensionName, VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME) == 0) + { + appInfo.synchronization2Supported = true; + std::cout << "Synchronization2 supported via extension\n"; + break; + } + } + } + + // Add required extensions based on feature support + if (appInfo.dynamicRenderingSupported && deviceProperties.apiVersion < VK_VERSION_1_3) + { + requiredDeviceExtension.push_back(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME); + } + + if (appInfo.timelineSemaphoresSupported && deviceProperties.apiVersion < VK_VERSION_1_2) + { + requiredDeviceExtension.push_back(VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME); + } + + if (appInfo.synchronization2Supported && deviceProperties.apiVersion < VK_VERSION_1_3) + { + requiredDeviceExtension.push_back(VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // Create device with appropriate features + auto features = physicalDevice.getFeatures2(); + + // Setup feature chain based on detected support + void *pNext = nullptr; + + // Add dynamic rendering if supported + vk::PhysicalDeviceVulkan13Features vulkan13Features; + vk::PhysicalDeviceDynamicRenderingFeatures dynamicRenderingFeatures; + + if (appInfo.dynamicRenderingSupported) + { + if (appInfo.synchronization2Supported) + { + vulkan13Features.dynamicRendering = vk::True; + vulkan13Features.synchronization2 = vk::True; + vulkan13Features.pNext = pNext; + pNext = &vulkan13Features; + } + else + { + dynamicRenderingFeatures.dynamicRendering = vk::True; + dynamicRenderingFeatures.pNext = pNext; + pNext = &dynamicRenderingFeatures; + } + } + + // Add timeline semaphores if supported + vk::PhysicalDeviceTimelineSemaphoreFeatures timelineSemaphoreFeatures; + if (appInfo.timelineSemaphoresSupported) + { + timelineSemaphoreFeatures.timelineSemaphore = vk::True; + timelineSemaphoreFeatures.pNext = pNext; + pNext = &timelineSemaphoreFeatures; + } + + features.pNext = pNext; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{ + .pNext = &features, + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface); + swapChainImageFormat = 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{ + .surface = surface, .minImageCount = minImageCount, .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + void createImageViews() + { + 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(device, imageViewCreateInfo); + } + } + + void createRenderPass() + { + if (appInfo.dynamicRenderingSupported) + { + // No render pass needed with dynamic rendering + std::cout << "Using dynamic rendering, skipping render pass creation\n"; + return; + } + + std::cout << "Creating traditional render pass\n"; + + // Color attachment description + vk::AttachmentDescription colorAttachment{ + .format = swapChainImageFormat, + .samples = msaaSamples, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .stencilLoadOp = vk::AttachmentLoadOp::eDontCare, + .stencilStoreOp = vk::AttachmentStoreOp::eDontCare, + .initialLayout = vk::ImageLayout::eUndefined, + .finalLayout = vk::ImageLayout::eColorAttachmentOptimal}; + + vk::AttachmentDescription depthAttachment{ + .format = findDepthFormat(), + .samples = msaaSamples, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eDontCare, + .stencilLoadOp = vk::AttachmentLoadOp::eDontCare, + .stencilStoreOp = vk::AttachmentStoreOp::eDontCare, + .initialLayout = vk::ImageLayout::eUndefined, + .finalLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal}; + + vk::AttachmentDescription colorAttachmentResolve{ + .format = swapChainImageFormat, + .samples = vk::SampleCountFlagBits::e1, + .loadOp = vk::AttachmentLoadOp::eDontCare, + .storeOp = vk::AttachmentStoreOp::eStore, + .stencilLoadOp = vk::AttachmentLoadOp::eDontCare, + .stencilStoreOp = vk::AttachmentStoreOp::eDontCare, + .initialLayout = vk::ImageLayout::eUndefined, + .finalLayout = vk::ImageLayout::ePresentSrcKHR}; + + // Subpass references + vk::AttachmentReference colorAttachmentRef{ + .attachment = 0, + .layout = vk::ImageLayout::eColorAttachmentOptimal}; + + vk::AttachmentReference depthAttachmentRef{ + .attachment = 1, + .layout = vk::ImageLayout::eDepthStencilAttachmentOptimal}; + + vk::AttachmentReference colorAttachmentResolveRef{ + .attachment = 2, + .layout = vk::ImageLayout::eColorAttachmentOptimal}; + + // Subpass description + vk::SubpassDescription subpass{ + .pipelineBindPoint = vk::PipelineBindPoint::eGraphics, + .colorAttachmentCount = 1, + .pColorAttachments = &colorAttachmentRef, + .pResolveAttachments = &colorAttachmentResolveRef, + .pDepthStencilAttachment = &depthAttachmentRef}; + + // Dependency to ensure proper image layout transitions + vk::SubpassDependency dependency{ + .srcSubpass = VK_SUBPASS_EXTERNAL, + .dstSubpass = 0, + .srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eEarlyFragmentTests, + .dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eEarlyFragmentTests, + .srcAccessMask = vk::AccessFlagBits::eNone, + .dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite | vk::AccessFlagBits::eDepthStencilAttachmentWrite}; + + // Create the render pass + std::array attachments = {colorAttachment, depthAttachment, colorAttachmentResolve}; + vk::RenderPassCreateInfo renderPassInfo{ + .attachmentCount = static_cast(attachments.size()), + .pAttachments = attachments.data(), + .subpassCount = 1, + .pSubpasses = &subpass, + .dependencyCount = 1, + .pDependencies = &dependency}; + + renderPass = vk::raii::RenderPass(device, renderPassInfo); + } + + void createFramebuffers() + { + if (appInfo.dynamicRenderingSupported) + { + // No framebuffers needed with dynamic rendering + std::cout << "Using dynamic rendering, skipping framebuffer creation\n"; + return; + } + + std::cout << "Creating traditional framebuffers\n"; + + swapChainFramebuffers.clear(); + + for (size_t i = 0; i < swapChainImageViews.size(); i++) + { + std::array attachments = { + *colorImageView, + *depthImageView, + *swapChainImageViews[i]}; + + vk::FramebufferCreateInfo framebufferInfo{ + .renderPass = *renderPass, + .attachmentCount = static_cast(attachments.size()), + .pAttachments = attachments.data(), + .width = swapChainExtent.width, + .height = swapChainExtent.height, + .layers = 1}; + + swapChainFramebuffers.emplace_back(device, framebufferInfo); + } + } + + void createDescriptorSetLayout() + { + std::array bindings = { + vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr), + vk::DescriptorSetLayoutBinding(1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment, nullptr)}; + + vk::DescriptorSetLayoutCreateInfo layoutInfo{.bindingCount = static_cast(bindings.size()), .pBindings = bindings.data()}; + descriptorSetLayout = vk::raii::DescriptorSetLayout(device, layoutInfo); + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; + + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{ + .vertexBindingDescriptionCount = 1, + .pVertexBindingDescriptions = &bindingDescription, + .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), + .pVertexAttributeDescriptions = attributeDescriptions.data()}; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ + .topology = vk::PrimitiveTopology::eTriangleList, + .primitiveRestartEnable = vk::False}; + vk::PipelineViewportStateCreateInfo viewportState{ + .viewportCount = 1, + .scissorCount = 1}; + vk::PipelineRasterizationStateCreateInfo rasterizer{ + .depthClampEnable = vk::False, + .rasterizerDiscardEnable = vk::False, + .polygonMode = vk::PolygonMode::eFill, + .cullMode = vk::CullModeFlagBits::eBack, + .frontFace = vk::FrontFace::eCounterClockwise, + .depthBiasEnable = vk::False}; + rasterizer.lineWidth = 1.0f; + vk::PipelineMultisampleStateCreateInfo multisampling{ + .rasterizationSamples = msaaSamples, + .sampleShadingEnable = vk::False}; + vk::PipelineDepthStencilStateCreateInfo depthStencil{ + .depthTestEnable = vk::True, + .depthWriteEnable = vk::True, + .depthCompareOp = vk::CompareOp::eLess, + .depthBoundsTestEnable = vk::False, + .stencilTestEnable = vk::False}; + vk::PipelineColorBlendAttachmentState colorBlendAttachment; + colorBlendAttachment.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA; + colorBlendAttachment.blendEnable = vk::False; + + vk::PipelineColorBlendStateCreateInfo colorBlending{ + .logicOpEnable = vk::False, + .logicOp = vk::LogicOp::eCopy, + .attachmentCount = 1, + .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0}; + + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::GraphicsPipelineCreateInfo pipelineInfo{ + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pDepthStencilState = &depthStencil, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout}; + + // Configure pipeline based on dynamic rendering support + vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo; + if (appInfo.dynamicRenderingSupported) + { + std::cout << "Configuring pipeline for dynamic rendering\n"; + pipelineRenderingCreateInfo.colorAttachmentCount = 1; + pipelineRenderingCreateInfo.pColorAttachmentFormats = &swapChainImageFormat; + pipelineRenderingCreateInfo.depthAttachmentFormat = findDepthFormat(); + + pipelineInfo.pNext = &pipelineRenderingCreateInfo; + pipelineInfo.renderPass = nullptr; + } + else + { + std::cout << "Configuring pipeline for traditional render pass\n"; + pipelineInfo.pNext = nullptr; + pipelineInfo.renderPass = *renderPass; + pipelineInfo.subpass = 0; + } + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{ + .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex}; + commandPool = vk::raii::CommandPool(device, poolInfo); + } + + void createColorResources() + { + vk::Format colorFormat = swapChainImageFormat; + + createImage(swapChainExtent.width, swapChainExtent.height, 1, msaaSamples, colorFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransientAttachment | vk::ImageUsageFlagBits::eColorAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, colorImage, colorImageMemory); + colorImageView = createImageView(colorImage, colorFormat, vk::ImageAspectFlagBits::eColor, 1); + } + + void createDepthResources() + { + vk::Format depthFormat = findDepthFormat(); + + createImage(swapChainExtent.width, swapChainExtent.height, 1, msaaSamples, depthFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eDepthStencilAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, depthImage, depthImageMemory); + depthImageView = createImageView(depthImage, depthFormat, vk::ImageAspectFlagBits::eDepth, 1); + } + + vk::Format findSupportedFormat(const std::vector &candidates, vk::ImageTiling tiling, vk::FormatFeatureFlags features) const + { + for (const auto format : candidates) + { + vk::FormatProperties props = physicalDevice.getFormatProperties(format); + + if (tiling == vk::ImageTiling::eLinear && (props.linearTilingFeatures & features) == features) + { + return format; + } + if (tiling == vk::ImageTiling::eOptimal && (props.optimalTilingFeatures & features) == features) + { + return format; + } + } + + throw std::runtime_error("failed to find supported format!"); + } + + [[nodiscard]] vk::Format findDepthFormat() const + { + return findSupportedFormat( + {vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, vk::Format::eD24UnormS8Uint}, + vk::ImageTiling::eOptimal, + vk::FormatFeatureFlagBits::eDepthStencilAttachment); + } + + static bool hasStencilComponent(vk::Format format) + { + return format == vk::Format::eD32SfloatS8Uint || format == vk::Format::eD24UnormS8Uint; + } + + void createTextureImage() + { + int texWidth, texHeight, texChannels; + stbi_uc *pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + vk::DeviceSize imageSize = texWidth * texHeight * 4; + mipLevels = static_cast(std::floor(std::log2(std::max(texWidth, texHeight)))) + 1; + + if (!pixels) + { + throw std::runtime_error("failed to load texture image!"); + } + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, imageSize); + memcpy(data, pixels, imageSize); + stagingBufferMemory.unmapMemory(); + + stbi_image_free(pixels); + + createImage(texWidth, texHeight, mipLevels, vk::SampleCountFlagBits::e1, vk::Format::eR8G8B8A8Srgb, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory); + + transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, mipLevels); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); + + generateMipmaps(textureImage, vk::Format::eR8G8B8A8Srgb, texWidth, texHeight, mipLevels); + } + + void generateMipmaps(vk::raii::Image &image, vk::Format imageFormat, int32_t texWidth, int32_t texHeight, uint32_t mipLevels) + { + // Check if image format supports linear blit-ing + vk::FormatProperties formatProperties = physicalDevice.getFormatProperties(imageFormat); + + if (!(formatProperties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eSampledImageFilterLinear)) + { + throw std::runtime_error("texture image format does not support linear blitting!"); + } + + std::unique_ptr commandBuffer = beginSingleTimeCommands(); + + vk::ImageMemoryBarrier barrier = {.srcAccessMask = vk::AccessFlagBits::eTransferWrite, .dstAccessMask = vk::AccessFlagBits::eTransferRead, .oldLayout = vk::ImageLayout::eTransferDstOptimal, .newLayout = vk::ImageLayout::eTransferSrcOptimal, .srcQueueFamilyIndex = vk::QueueFamilyIgnored, .dstQueueFamilyIndex = vk::QueueFamilyIgnored, .image = image}; + barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + barrier.subresourceRange.levelCount = 1; + + int32_t mipWidth = texWidth; + int32_t mipHeight = texHeight; + + for (uint32_t i = 1; i < mipLevels; i++) + { + barrier.subresourceRange.baseMipLevel = i - 1; + barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal; + barrier.newLayout = vk::ImageLayout::eTransferSrcOptimal; + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eTransferRead; + + commandBuffer->pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer, {}, {}, {}, barrier); + + vk::ArrayWrapper1D offsets, dstOffsets; + offsets[0] = vk::Offset3D(0, 0, 0); + offsets[1] = vk::Offset3D(mipWidth, mipHeight, 1); + dstOffsets[0] = vk::Offset3D(0, 0, 0); + dstOffsets[1] = vk::Offset3D(mipWidth > 1 ? mipWidth / 2 : 1, mipHeight > 1 ? mipHeight / 2 : 1, 1); + vk::ImageBlit blit = {.srcSubresource = {}, .srcOffsets = offsets, .dstSubresource = {}, .dstOffsets = dstOffsets}; + blit.srcSubresource = vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, i - 1, 0, 1); + blit.dstSubresource = vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, i, 0, 1); + + commandBuffer->blitImage(image, vk::ImageLayout::eTransferSrcOptimal, image, vk::ImageLayout::eTransferDstOptimal, {blit}, vk::Filter::eLinear); + + barrier.oldLayout = vk::ImageLayout::eTransferSrcOptimal; + barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal; + barrier.srcAccessMask = vk::AccessFlagBits::eTransferRead; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + + commandBuffer->pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {}, barrier); + + if (mipWidth > 1) + mipWidth /= 2; + if (mipHeight > 1) + mipHeight /= 2; + } + + barrier.subresourceRange.baseMipLevel = mipLevels - 1; + barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal; + barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal; + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + + commandBuffer->pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {}, barrier); + + endSingleTimeCommands(*commandBuffer); + } + + vk::SampleCountFlagBits getMaxUsableSampleCount() + { + vk::PhysicalDeviceProperties physicalDeviceProperties = physicalDevice.getProperties(); + + vk::SampleCountFlags counts = physicalDeviceProperties.limits.framebufferColorSampleCounts & physicalDeviceProperties.limits.framebufferDepthSampleCounts; + if (counts & vk::SampleCountFlagBits::e64) + { + return vk::SampleCountFlagBits::e64; + } + if (counts & vk::SampleCountFlagBits::e32) + { + return vk::SampleCountFlagBits::e32; + } + if (counts & vk::SampleCountFlagBits::e16) + { + return vk::SampleCountFlagBits::e16; + } + if (counts & vk::SampleCountFlagBits::e8) + { + return vk::SampleCountFlagBits::e8; + } + if (counts & vk::SampleCountFlagBits::e4) + { + return vk::SampleCountFlagBits::e4; + } + if (counts & vk::SampleCountFlagBits::e2) + { + return vk::SampleCountFlagBits::e2; + } + + return vk::SampleCountFlagBits::e1; + } + + void createTextureImageView() + { + textureImageView = createImageView(textureImage, vk::Format::eR8G8B8A8Srgb, vk::ImageAspectFlagBits::eColor, mipLevels); + } + + void createTextureSampler() + { + vk::PhysicalDeviceProperties properties = physicalDevice.getProperties(); + vk::SamplerCreateInfo samplerInfo{ + .magFilter = vk::Filter::eLinear, + .minFilter = vk::Filter::eLinear, + .mipmapMode = vk::SamplerMipmapMode::eLinear, + .addressModeU = vk::SamplerAddressMode::eRepeat, + .addressModeV = vk::SamplerAddressMode::eRepeat, + .addressModeW = vk::SamplerAddressMode::eRepeat, + .mipLodBias = 0.0f, + .anisotropyEnable = vk::True, + .maxAnisotropy = properties.limits.maxSamplerAnisotropy, + .compareEnable = vk::False, + .compareOp = vk::CompareOp::eAlways}; + textureSampler = vk::raii::Sampler(device, samplerInfo); + } + + [[nodiscard]] vk::raii::ImageView createImageView(const vk::raii::Image &image, vk::Format format, vk::ImageAspectFlags aspectFlags, uint32_t mipLevels) const + { + vk::ImageViewCreateInfo viewInfo{ + .image = image, + .viewType = vk::ImageViewType::e2D, + .format = format, + .subresourceRange = {aspectFlags, 0, mipLevels, 0, 1}}; + return vk::raii::ImageView(device, viewInfo); + } + + void createImage(uint32_t width, uint32_t height, uint32_t mipLevels, vk::SampleCountFlagBits numSamples, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image &image, vk::raii::DeviceMemory &imageMemory) + { + vk::ImageCreateInfo imageInfo{ + .imageType = vk::ImageType::e2D, + .format = format, + .extent = {width, height, 1}, + .mipLevels = mipLevels, + .arrayLayers = 1, + .samples = numSamples, + .tiling = tiling, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive, + .initialLayout = vk::ImageLayout::eUndefined}; + image = vk::raii::Image(device, imageInfo); + + vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{ + .allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + imageMemory = vk::raii::DeviceMemory(device, allocInfo); + image.bindMemory(imageMemory, 0); + } + + void transitionImageLayout(const vk::raii::Image &image, const vk::ImageLayout oldLayout, const vk::ImageLayout newLayout, uint32_t mipLevels) + { + const auto commandBuffer = beginSingleTimeCommands(); + + if (appInfo.synchronization2Supported) + { + // Use Synchronization2 API + vk::ImageMemoryBarrier2 barrier{ + .srcStageMask = vk::PipelineStageFlagBits2::eAllCommands, + .dstStageMask = vk::PipelineStageFlagBits2::eAllCommands, + .oldLayout = oldLayout, + .newLayout = newLayout, + .image = image, + .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, mipLevels, 0, 1}}; + + if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) + { + barrier.srcAccessMask = vk::AccessFlagBits2::eNone; + barrier.dstAccessMask = vk::AccessFlagBits2::eTransferWrite; + barrier.srcStageMask = vk::PipelineStageFlagBits2::eTopOfPipe; + barrier.dstStageMask = vk::PipelineStageFlagBits2::eTransfer; + } + else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) + { + barrier.srcAccessMask = vk::AccessFlagBits2::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits2::eShaderRead; + barrier.srcStageMask = vk::PipelineStageFlagBits2::eTransfer; + barrier.dstStageMask = vk::PipelineStageFlagBits2::eFragmentShader; + } + else + { + throw std::invalid_argument("unsupported layout transition!"); + } + + vk::DependencyInfo dependencyInfo{ + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + + commandBuffer->pipelineBarrier2(dependencyInfo); + } + else + { + // Use traditional synchronization API + vk::ImageMemoryBarrier barrier{ + .oldLayout = oldLayout, + .newLayout = newLayout, + .image = image, + .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, mipLevels, 0, 1}}; + + vk::PipelineStageFlags sourceStage; + vk::PipelineStageFlags destinationStage; + + if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) + { + barrier.srcAccessMask = {}; + barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; + + sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; + destinationStage = vk::PipelineStageFlagBits::eTransfer; + } + else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) + { + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + + sourceStage = vk::PipelineStageFlagBits::eTransfer; + destinationStage = vk::PipelineStageFlagBits::eFragmentShader; + } + else + { + throw std::invalid_argument("unsupported layout transition!"); + } + commandBuffer->pipelineBarrier(sourceStage, destinationStage, {}, {}, nullptr, barrier); + } + + endSingleTimeCommands(*commandBuffer); + } + + void copyBufferToImage(const vk::raii::Buffer &buffer, const vk::raii::Image &image, uint32_t width, uint32_t height) + { + std::unique_ptr commandBuffer = beginSingleTimeCommands(); + vk::BufferImageCopy region{ + .bufferOffset = 0, + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource = {vk::ImageAspectFlagBits::eColor, 0, 0, 1}, + .imageOffset = {0, 0, 0}, + .imageExtent = {width, height, 1}}; + commandBuffer->copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, {region}); + endSingleTimeCommands(*commandBuffer); + } + + void loadModel() + { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn, err; + + if (!LoadObj(&attrib, &shapes, &materials, &warn, &err, MODEL_PATH.c_str())) + { + throw std::runtime_error(warn + err); + } + + std::unordered_map uniqueVertices{}; + + for (const auto &shape : shapes) + { + for (const auto &index : shape.mesh.indices) + { + Vertex vertex{}; + + vertex.pos = { + attrib.vertices[3 * index.vertex_index + 0], + attrib.vertices[3 * index.vertex_index + 1], + attrib.vertices[3 * index.vertex_index + 2]}; + + vertex.texCoord = { + attrib.texcoords[2 * index.texcoord_index + 0], + 1.0f - attrib.texcoords[2 * index.texcoord_index + 1]}; + + vertex.color = {1.0f, 1.0f, 1.0f}; + + if (!uniqueVertices.contains(vertex)) + { + uniqueVertices[vertex] = static_cast(vertices.size()); + vertices.push_back(vertex); + } + + indices.push_back(uniqueVertices[vertex]); + } + } + } + + void createVertexBuffer() + { + vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(dataStaging, vertices.data(), bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + } + + void createIndexBuffer() + { + vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(data, indices.data(), bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); + + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + } + + void createUniformBuffers() + { + uniformBuffers.clear(); + uniformBuffersMemory.clear(); + uniformBuffersMapped.clear(); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DeviceSize bufferSize = sizeof(UniformBufferObject); + vk::raii::Buffer buffer({}); + vk::raii::DeviceMemory bufferMem({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); + uniformBuffers.emplace_back(std::move(buffer)); + uniformBuffersMemory.emplace_back(std::move(bufferMem)); + uniformBuffersMapped.emplace_back(uniformBuffersMemory[i].mapMemory(0, bufferSize)); + } + } + + void createDescriptorPool() + { + std::array poolSize{ + vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT), + vk::DescriptorPoolSize(vk::DescriptorType::eCombinedImageSampler, MAX_FRAMES_IN_FLIGHT)}; + vk::DescriptorPoolCreateInfo poolInfo{ + .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, + .maxSets = MAX_FRAMES_IN_FLIGHT, + .poolSizeCount = static_cast(poolSize.size()), + .pPoolSizes = poolSize.data()}; + descriptorPool = vk::raii::DescriptorPool(device, poolInfo); + } + + void createDescriptorSets() + { + std::vector layouts(MAX_FRAMES_IN_FLIGHT, descriptorSetLayout); + vk::DescriptorSetAllocateInfo allocInfo{ + .descriptorPool = descriptorPool, + .descriptorSetCount = static_cast(layouts.size()), + .pSetLayouts = layouts.data()}; + + descriptorSets.clear(); + descriptorSets = device.allocateDescriptorSets(allocInfo); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DescriptorBufferInfo bufferInfo{ + .buffer = uniformBuffers[i], + .offset = 0, + .range = sizeof(UniformBufferObject)}; + vk::DescriptorImageInfo imageInfo{ + .sampler = textureSampler, + .imageView = textureImageView, + .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal}; + std::array descriptorWrites{ + vk::WriteDescriptorSet{ + .dstSet = descriptorSets[i], + .dstBinding = 0, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eUniformBuffer, + .pBufferInfo = &bufferInfo}, + vk::WriteDescriptorSet{ + .dstSet = descriptorSets[i], + .dstBinding = 1, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .pImageInfo = &imageInfo}}; + device.updateDescriptorSets(descriptorWrites, {}); + } + } + + void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer &buffer, vk::raii::DeviceMemory &bufferMemory) + { + vk::BufferCreateInfo bufferInfo{ + .size = size, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive}; + buffer = vk::raii::Buffer(device, bufferInfo); + vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{ + .allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + bufferMemory = vk::raii::DeviceMemory(device, allocInfo); + buffer.bindMemory(bufferMemory, 0); + } + + std::unique_ptr beginSingleTimeCommands() + { + vk::CommandBufferAllocateInfo allocInfo{ + .commandPool = commandPool, + .level = vk::CommandBufferLevel::ePrimary, + .commandBufferCount = 1}; + std::unique_ptr commandBuffer = std::make_unique(std::move(vk::raii::CommandBuffers(device, allocInfo).front())); + + vk::CommandBufferBeginInfo beginInfo{ + .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; + commandBuffer->begin(beginInfo); + + return commandBuffer; + } + + void endSingleTimeCommands(const vk::raii::CommandBuffer &commandBuffer) const + { + commandBuffer.end(); + + vk::SubmitInfo submitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandBuffer}; + queue.submit(submitInfo, nullptr); + queue.waitIdle(); + } + + void copyBuffer(vk::raii::Buffer &srcBuffer, vk::raii::Buffer &dstBuffer, vk::DeviceSize size) + { + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1}; + vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); + commandCopyBuffer.begin(vk::CommandBufferBeginInfo{.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); + commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy{.size = size}); + commandCopyBuffer.end(); + queue.submit(vk::SubmitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer}, nullptr); + queue.waitIdle(); + } + + uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) + { + vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) + { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) + { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + void createCommandBuffers() + { + commandBuffers.clear(); + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = MAX_FRAMES_IN_FLIGHT}; + commandBuffers = vk::raii::CommandBuffers(device, allocInfo); + } + + void recordCommandBuffer(uint32_t imageIndex) + { + commandBuffers[currentFrame].begin({}); + + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::ClearValue clearDepth = vk::ClearDepthStencilValue(1.0f, 0); + std::array clearValues = {clearColor, clearDepth}; + + if (appInfo.dynamicRenderingSupported) + { + // Transition attachments to the correct layout + if (appInfo.synchronization2Supported) + { + // Use Synchronization2 API for image transitions + vk::ImageMemoryBarrier2 colorBarrier{ + .srcStageMask = vk::PipelineStageFlagBits2::eTopOfPipe, + .srcAccessMask = vk::AccessFlagBits2::eNone, + .dstStageMask = vk::PipelineStageFlagBits2::eColorAttachmentOutput, + .dstAccessMask = vk::AccessFlagBits2::eColorAttachmentWrite, + .oldLayout = vk::ImageLayout::eUndefined, + .newLayout = vk::ImageLayout::eColorAttachmentOptimal, + .image = *colorImage, + .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + + vk::ImageMemoryBarrier2 depthBarrier{ + .srcStageMask = vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, + .srcAccessMask = vk::AccessFlagBits2::eNone, + .dstStageMask = vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, + .dstAccessMask = vk::AccessFlagBits2::eDepthStencilAttachmentWrite, + .oldLayout = vk::ImageLayout::eUndefined, + .newLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal, + .image = *depthImage, + .subresourceRange = {vk::ImageAspectFlagBits::eDepth, 0, 1, 0, 1}}; + + vk::ImageMemoryBarrier2 swapchainBarrier{ + .srcStageMask = vk::PipelineStageFlagBits2::eTopOfPipe, + .srcAccessMask = vk::AccessFlagBits2::eNone, + .dstStageMask = vk::PipelineStageFlagBits2::eColorAttachmentOutput, + .dstAccessMask = vk::AccessFlagBits2::eColorAttachmentWrite, + .oldLayout = vk::ImageLayout::eUndefined, + .newLayout = vk::ImageLayout::eColorAttachmentOptimal, + .image = swapChainImages[imageIndex], + .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + + std::array barriers = {colorBarrier, depthBarrier, swapchainBarrier}; + vk::DependencyInfo dependencyInfo{ + .imageMemoryBarrierCount = static_cast(barriers.size()), + .pImageMemoryBarriers = barriers.data()}; + + commandBuffers[currentFrame].pipelineBarrier2(dependencyInfo); + } + else + { + // Use traditional synchronization API + vk::ImageMemoryBarrier colorBarrier{ + .srcAccessMask = vk::AccessFlagBits::eNone, + .dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite, + .oldLayout = vk::ImageLayout::eUndefined, + .newLayout = vk::ImageLayout::eColorAttachmentOptimal, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = *colorImage, + .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + + vk::ImageMemoryBarrier depthBarrier{ + .srcAccessMask = vk::AccessFlagBits::eNone, + .dstAccessMask = vk::AccessFlagBits::eDepthStencilAttachmentWrite, + .oldLayout = vk::ImageLayout::eUndefined, + .newLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = *depthImage, + .subresourceRange = {vk::ImageAspectFlagBits::eDepth, 0, 1, 0, 1}}; + + vk::ImageMemoryBarrier swapchainBarrier{ + .srcAccessMask = vk::AccessFlagBits::eNone, + .dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite, + .oldLayout = vk::ImageLayout::eUndefined, + .newLayout = vk::ImageLayout::eColorAttachmentOptimal, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + + std::array barriers = {colorBarrier, depthBarrier, swapchainBarrier}; + commandBuffers[currentFrame].pipelineBarrier( + vk::PipelineStageFlagBits::eTopOfPipe, + vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eEarlyFragmentTests, + vk::DependencyFlagBits::eByRegion, + {}, + {}, + barriers); + } + + // Setup rendering attachments + vk::RenderingAttachmentInfo colorAttachment{ + .imageView = *colorImageView, + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .resolveMode = vk::ResolveModeFlagBits::eAverage, + .resolveImageView = *swapChainImageViews[imageIndex], + .resolveImageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor}; + + vk::RenderingAttachmentInfo depthAttachment{ + .imageView = *depthImageView, + .imageLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eDontCare, + .clearValue = clearDepth}; + + vk::RenderingInfo renderingInfo{ + .renderArea = {{0, 0}, swapChainExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &colorAttachment, + .pDepthAttachment = &depthAttachment}; + + commandBuffers[currentFrame].beginRendering(renderingInfo); + } + else + { + // Use traditional render pass + std::cout << "Recording command buffer with traditional render pass\n"; + + vk::RenderPassBeginInfo renderPassInfo{ + .renderPass = *renderPass, + .framebuffer = *swapChainFramebuffers[imageIndex], + .renderArea = {{0, 0}, swapChainExtent}, + .clearValueCount = static_cast(clearValues.size()), + .pClearValues = clearValues.data()}; + + commandBuffers[currentFrame].beginRenderPass(renderPassInfo, vk::SubpassContents::eInline); + } + + // Common rendering commands + commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint32); + commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); + commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); + + if (appInfo.dynamicRenderingSupported) + { + commandBuffers[currentFrame].endRendering(); + + // Transition swapchain image to present layout + if (appInfo.synchronization2Supported) + { + vk::ImageMemoryBarrier2 barrier{ + .srcStageMask = vk::PipelineStageFlagBits2::eColorAttachmentOutput, + .srcAccessMask = vk::AccessFlagBits2::eColorAttachmentWrite, + .dstStageMask = vk::PipelineStageFlagBits2::eBottomOfPipe, + .dstAccessMask = vk::AccessFlagBits2::eNone, + .oldLayout = vk::ImageLayout::eColorAttachmentOptimal, + .newLayout = vk::ImageLayout::ePresentSrcKHR, + .image = swapChainImages[imageIndex], + .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + + vk::DependencyInfo dependencyInfo{ + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + + commandBuffers[currentFrame].pipelineBarrier2(dependencyInfo); + } + else + { + vk::ImageMemoryBarrier barrier{ + .srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite, + .dstAccessMask = vk::AccessFlagBits::eNone, + .oldLayout = vk::ImageLayout::eColorAttachmentOptimal, + .newLayout = vk::ImageLayout::ePresentSrcKHR, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + + commandBuffers[currentFrame].pipelineBarrier( + vk::PipelineStageFlagBits::eColorAttachmentOutput, + vk::PipelineStageFlagBits::eBottomOfPipe, + vk::DependencyFlagBits::eByRegion, + {}, + {}, + {barrier}); + } + } + else + { + commandBuffers[currentFrame].endRenderPass(); + } + + commandBuffers[currentFrame].end(); + } + + void createSyncObjects() + { + presentCompleteSemaphore.clear(); + renderFinishedSemaphore.clear(); + inFlightFences.clear(); + + if (appInfo.timelineSemaphoresSupported) + { + // Create timeline semaphore + std::cout << "Creating timeline semaphores\n"; + vk::SemaphoreTypeCreateInfo timelineCreateInfo{ + .semaphoreType = vk::SemaphoreType::eTimeline, + .initialValue = 0}; + + vk::SemaphoreCreateInfo semaphoreInfo{ + .pNext = &timelineCreateInfo}; + + timelineSemaphore = vk::raii::Semaphore(device, semaphoreInfo); + + // Still need binary semaphores for swapchain operations + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + } + } + else + { + // Create binary semaphores and fences + std::cout << "Creating binary semaphores and fences\n"; + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + } + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); + } + } + + void updateUniformBuffer(uint32_t currentImage) const + { + static auto startTime = std::chrono::high_resolution_clock::now(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + UniformBufferObject ubo{}; + ubo.model = rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.view = lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); + ubo.proj[1][1] *= -1; + + memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); + } + + void drawFrame() + { + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, FenceTimeout)) + ; + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[currentFrame], nullptr); + + if (result == vk::Result::eErrorOutOfDateKHR) + { + recreateSwapChain(); + return; + } + if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) + { + throw std::runtime_error("failed to acquire swap chain image!"); + } + updateUniformBuffer(currentFrame); + + device.resetFences(*inFlightFences[currentFrame]); + commandBuffers[currentFrame].reset(); + recordCommandBuffer(imageIndex); + + if (appInfo.timelineSemaphoresSupported) + { + // Use timeline semaphores for GPU synchronization + uint64_t waitValue = timelineValue; + uint64_t signalValue = ++timelineValue; + + vk::TimelineSemaphoreSubmitInfo timelineInfo{ + .waitSemaphoreValueCount = 0, // We'll still use binary semaphore for swapchain + .signalSemaphoreValueCount = 1, + .pSignalSemaphoreValues = &signalValue}; + + std::array waitSemaphores = {*presentCompleteSemaphore[currentFrame], *timelineSemaphore}; + std::array waitStages = {vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::PipelineStageFlagBits::eVertexInput}; + std::array waitValues = {0, waitValue}; // Binary semaphore value is ignored + + std::array signalSemaphores = {*renderFinishedSemaphore[currentFrame], *timelineSemaphore}; + std::array signalValues = {0, signalValue}; // Binary semaphore value is ignored + + timelineInfo.waitSemaphoreValueCount = 1; // Only for the timeline semaphore + timelineInfo.pWaitSemaphoreValues = &waitValues[1]; + timelineInfo.signalSemaphoreValueCount = 1; // Only for the timeline semaphore + timelineInfo.pSignalSemaphoreValues = &signalValues[1]; + + vk::SubmitInfo submitInfo{ + .pNext = &timelineInfo, + .waitSemaphoreCount = 1, // Only wait on the binary semaphore + .pWaitSemaphores = &waitSemaphores[0], + .pWaitDstStageMask = &waitStages[0], + .commandBufferCount = 1, + .pCommandBuffers = &*commandBuffers[currentFrame], + .signalSemaphoreCount = 2, // Signal both semaphores + .pSignalSemaphores = signalSemaphores.data()}; + + queue.submit(submitInfo, *inFlightFences[currentFrame]); + } + else + { + // Use traditional binary semaphores + vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); + const vk::SubmitInfo submitInfo{ + .waitSemaphoreCount = 1, + .pWaitSemaphores = &*presentCompleteSemaphore[currentFrame], + .pWaitDstStageMask = &waitDestinationStageMask, + .commandBufferCount = 1, + .pCommandBuffers = &*commandBuffers[currentFrame], + .signalSemaphoreCount = 1, + .pSignalSemaphores = &*renderFinishedSemaphore[currentFrame]}; + queue.submit(submitInfo, *inFlightFences[currentFrame]); + } + + const vk::PresentInfoKHR presentInfoKHR{ + .waitSemaphoreCount = 1, + .pWaitSemaphores = &*renderFinishedSemaphore[currentFrame], + .swapchainCount = 1, + .pSwapchains = &*swapChain, + .pImageIndices = &imageIndex}; + result = queue.presentKHR(presentInfoKHR); + if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) + { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != vk::Result::eSuccess) + { + throw std::runtime_error("failed to present swap chain image!"); + } + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size(), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static vk::Format chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + return (availableFormats[0].format == vk::Format::eUndefined) ? vk::Format::eB8G8R8A8Unorm : availableFormats[0].format; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + vk::PresentModeKHR::eFifo; + } + + [[nodiscard]] vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR &capabilities) const + { + 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)}; + } + + [[nodiscard]] std::vector getRequiredExtensions() const + { + // Get the required extensions from GLFW + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + // Check if the debug utils extension is available + std::vector props = context.enumerateInstanceExtensionProperties(); + bool debugUtilsAvailable = std::ranges::any_of(props, + [](vk::ExtensionProperties const &ep) { + return strcmp(ep.extensionName, vk::EXTDebugUtilsExtensionName) == 0; + }); + + // Always include the debug utils extension if available + // This allows validation layers to be enabled via vulkanconfig + if (debugUtilsAvailable) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + else + { + std::cout << "VK_EXT_debug_utils extension not available. Validation layers may not work." << std::endl; + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + 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; + } }; -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/33_vulkan_profiles.cpp b/attachments/33_vulkan_profiles.cpp index b50975fb..4a93f4b9 100644 --- a/attachments/33_vulkan_profiles.cpp +++ b/attachments/33_vulkan_profiles.cpp @@ -1,25 +1,25 @@ -#include -#include -#include -#include -#include -#include -#include #include -#include #include #include +#include +#include +#include +#include +#include +#include #include +#include +#include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include #define GLM_FORCE_RADIANS @@ -35,1676 +35,1737 @@ import vulkan_hpp; #define TINYOBJLOADER_IMPLEMENTATION #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -constexpr uint64_t FenceTimeout = 100000000; -const std::string MODEL_PATH = "models/viking_room.obj"; -const std::string TEXTURE_PATH = "textures/viking_room.png"; -constexpr int MAX_FRAMES_IN_FLIGHT = 2; +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; +constexpr uint64_t FenceTimeout = 100000000; +const std::string MODEL_PATH = "models/viking_room.obj"; +const std::string TEXTURE_PATH = "textures/viking_room.png"; +constexpr int MAX_FRAMES_IN_FLIGHT = 2; // Application info structure to store profile support flags -struct AppInfo { - bool profileSupported = false; - VpProfileProperties profile; +struct AppInfo +{ + bool profileSupported = false; + VpProfileProperties profile; }; // Moved struct definitions inside the class -struct Vertex { - glm::vec3 pos; - glm::vec3 color; - glm::vec2 texCoord; - - static vk::VertexInputBindingDescription getBindingDescription() { - return { 0, sizeof(Vertex), vk::VertexInputRate::eVertex }; - } - - static std::array getAttributeDescriptions() { - return { - vk::VertexInputAttributeDescription( 0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, pos) ), - vk::VertexInputAttributeDescription( 1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color) ), - vk::VertexInputAttributeDescription( 2, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, texCoord) ) - }; - } - - bool operator==(const Vertex& other) const { - return pos == other.pos && color == other.color && texCoord == other.texCoord; - } +struct Vertex +{ + glm::vec3 pos; + glm::vec3 color; + glm::vec2 texCoord; + + static vk::VertexInputBindingDescription getBindingDescription() + { + return {0, sizeof(Vertex), vk::VertexInputRate::eVertex}; + } + + static std::array getAttributeDescriptions() + { + return { + vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, pos)), + vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color)), + vk::VertexInputAttributeDescription(2, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, texCoord))}; + } + + bool operator==(const Vertex &other) const + { + return pos == other.pos && color == other.color && texCoord == other.texCoord; + } }; -template<> struct std::hash { - size_t operator()(Vertex const& vertex) const noexcept { - return ((hash()(vertex.pos) ^ (hash()(vertex.color) << 1)) >> 1) ^ (hash()(vertex.texCoord) << 1); - } - }; +template <> +struct std::hash +{ + size_t operator()(Vertex const &vertex) const noexcept + { + return ((hash()(vertex.pos) ^ (hash()(vertex.color) << 1)) >> 1) ^ (hash()(vertex.texCoord) << 1); + } +}; -struct UniformBufferObject { - alignas(16) glm::mat4 model; - alignas(16) glm::mat4 view; - alignas(16) glm::mat4 proj; +struct UniformBufferObject +{ + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; }; -class HelloTriangleApplication { -public: - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } - -private: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::Format swapChainImageFormat = {}; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - vk::raii::RenderPass renderPass = nullptr; - vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - std::vector swapChainFramebuffers; - vk::raii::CommandPool commandPool = nullptr; - std::vector commandBuffers; - std::vector imageAvailableSemaphores; - std::vector renderFinishedSemaphores; - std::vector inFlightFences; - std::vector presentCompleteSemaphore; - uint32_t currentFrame = 0; - bool framebufferResized = false; - vk::raii::Buffer vertexBuffer = nullptr; - vk::raii::DeviceMemory vertexBufferMemory = nullptr; - vk::raii::Buffer indexBuffer = nullptr; - vk::raii::DeviceMemory indexBufferMemory = nullptr; - std::vector uniformBuffers; - std::vector uniformBuffersMemory; - std::vector uniformBuffersMapped; - vk::raii::DescriptorPool descriptorPool = nullptr; - std::vector descriptorSets; - vk::raii::Image textureImage = nullptr; - vk::raii::DeviceMemory textureImageMemory = nullptr; - vk::raii::ImageView textureImageView = nullptr; - vk::raii::Sampler textureSampler = nullptr; - vk::raii::Image depthImage = nullptr; - vk::raii::DeviceMemory depthImageMemory = nullptr; - vk::raii::ImageView depthImageView = nullptr; - std::vector vertices; - std::vector indices; - vk::SampleCountFlagBits msaaSamples = vk::SampleCountFlagBits::e1; - vk::raii::Image colorImage = nullptr; - vk::raii::DeviceMemory colorImageMemory = nullptr; - vk::raii::ImageView colorImageView = nullptr; - - // Application info to store profile support - AppInfo appInfo = {}; - - struct SwapChainSupportDetails { - vk::SurfaceCapabilitiesKHR capabilities; - std::vector formats; - std::vector presentModes; - }; - - const std::vector requiredDeviceExtension = { - VK_KHR_SWAPCHAIN_EXTENSION_NAME - }; - - void initWindow() { - glfwInit(); - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan Profiles Demo", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); - } - - static void framebufferResizeCallback(GLFWwindow* window, int, int) { - auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); - app->framebufferResized = true; - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - createSurface(); - pickPhysicalDevice(); - checkFeatureSupport(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - - // Create render pass only if not using dynamic rendering - if (!appInfo.profileSupported) { - createRenderPass(); - } - - createDescriptorSetLayout(); - createGraphicsPipeline(); - - // Create framebuffers only if not using dynamic rendering - if (!appInfo.profileSupported) { - createFramebuffers(); - } - - createCommandPool(); - createColorResources(); - createDepthResources(); - createTextureImage(); - createTextureImageView(); - createTextureSampler(); - loadModel(); - createVertexBuffer(); - createIndexBuffer(); - createUniformBuffers(); - createDescriptorPool(); - createDescriptorSets(); - createCommandBuffers(); - createSyncObjects(); - } - - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - drawFrame(); - } - - device.waitIdle(); - } - - void cleanupSwapChain() { - colorImageView = nullptr; - colorImage = nullptr; - colorImageMemory = nullptr; - - depthImageView = nullptr; - depthImage = nullptr; - depthImageMemory = nullptr; - - for (auto& framebuffer : swapChainFramebuffers) { - framebuffer = nullptr; - } - - for (auto& imageView : swapChainImageViews) { - imageView = nullptr; - } - - swapChain = nullptr; - } - - void cleanup() { - glfwDestroyWindow(window); - glfwTerminate(); - } - - void recreateSwapChain() { - int width = 0, height = 0; - glfwGetFramebufferSize(window, &width, &height); - while (width == 0 || height == 0) { - glfwGetFramebufferSize(window, &width, &height); - glfwWaitEvents(); - } - - device.waitIdle(); - - cleanupSwapChain(); - - createSwapChain(); - createImageViews(); - - // Recreate traditional render pass and framebuffers if not using profiles - if (!appInfo.profileSupported) { - createRenderPass(); - createFramebuffers(); - } - - createColorResources(); - createDepthResources(); - } - - void createInstance() { - - constexpr vk::ApplicationInfo appInfo{ - .pApplicationName = "Vulkan Profiles Demo", - .applicationVersion = VK_MAKE_VERSION(1, 0, 0), - .pEngineName = "No Engine", - .engineVersion = VK_MAKE_VERSION(1, 0, 0), - .apiVersion = vk::ApiVersion14 - }; - - auto extensions = getRequiredExtensions(); - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledExtensionCount = static_cast(extensions.size()), - .ppEnabledExtensionNames = extensions.data() - }; - - instance = vk::raii::Instance(context, createInfo); - - } - - void setupDebugMessenger() { - // Always set up the debug messenger - // It will only be used if validation layers are enabled via vulkanconfig - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( - vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | - vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | - vk::DebugUtilsMessageSeverityFlagBitsEXT::eError - ); - - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( - vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | - vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | - vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation - ); - - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - - try { - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } catch (vk::SystemError& err) { - // If the debug utils extension is not available, this will fail - // That's okay, it just means validation layers aren't enabled - std::cout << "Debug messenger not available. Validation layers may not be enabled." << std::endl; - } - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&](auto const & device) - { - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of(queueFamilies, [](auto const & qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of(requiredDeviceExtension, - [&availableDeviceExtensions](auto const & requiredDeviceExtension) - { - return std::ranges::any_of(availableDeviceExtensions, - [requiredDeviceExtension](auto const & availableDeviceExtension) - { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); - }); - - return supportsGraphics && supportsAllRequiredExtensions; - }); - - if (devIter != devices.end()) { - physicalDevice = *devIter; - msaaSamples = getMaxUsableSampleCount(); - - // Print device information - vk::PhysicalDeviceProperties deviceProperties = physicalDevice.getProperties(); - std::cout << "Selected GPU: " << deviceProperties.deviceName << std::endl; - std::cout << "API Version: " << VK_VERSION_MAJOR(deviceProperties.apiVersion) << "." - << VK_VERSION_MINOR(deviceProperties.apiVersion) << "." - << VK_VERSION_PATCH(deviceProperties.apiVersion) << std::endl; - } else { - throw std::runtime_error("failed to find a suitable GPU!"); - } - } - - void checkFeatureSupport() { - // Define the KHR roadmap 2022 profile - more widely supported than 2024 - appInfo.profile = { - VP_KHR_ROADMAP_2022_NAME, - VP_KHR_ROADMAP_2022_SPEC_VERSION - }; - - // Check if the profile is supported - VkBool32 supported = VK_FALSE; - VkResult result = vpGetPhysicalDeviceProfileSupport( +class HelloTriangleApplication +{ + public: + void run() + { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + + private: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::Format swapChainImageFormat = {}; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + vk::raii::RenderPass renderPass = nullptr; + vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + std::vector swapChainFramebuffers; + vk::raii::CommandPool commandPool = nullptr; + std::vector commandBuffers; + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + std::vector inFlightFences; + std::vector presentCompleteSemaphore; + uint32_t currentFrame = 0; + bool framebufferResized = false; + vk::raii::Buffer vertexBuffer = nullptr; + vk::raii::DeviceMemory vertexBufferMemory = nullptr; + vk::raii::Buffer indexBuffer = nullptr; + vk::raii::DeviceMemory indexBufferMemory = nullptr; + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + std::vector uniformBuffersMapped; + vk::raii::DescriptorPool descriptorPool = nullptr; + std::vector descriptorSets; + vk::raii::Image textureImage = nullptr; + vk::raii::DeviceMemory textureImageMemory = nullptr; + vk::raii::ImageView textureImageView = nullptr; + vk::raii::Sampler textureSampler = nullptr; + vk::raii::Image depthImage = nullptr; + vk::raii::DeviceMemory depthImageMemory = nullptr; + vk::raii::ImageView depthImageView = nullptr; + std::vector vertices; + std::vector indices; + vk::SampleCountFlagBits msaaSamples = vk::SampleCountFlagBits::e1; + vk::raii::Image colorImage = nullptr; + vk::raii::DeviceMemory colorImageMemory = nullptr; + vk::raii::ImageView colorImageView = nullptr; + + // Application info to store profile support + AppInfo appInfo = {}; + + struct SwapChainSupportDetails + { + vk::SurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; + }; + + const std::vector requiredDeviceExtension = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME}; + + void initWindow() + { + glfwInit(); + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan Profiles Demo", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow *window, int, int) + { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + checkFeatureSupport(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + + // Create render pass only if not using dynamic rendering + if (!appInfo.profileSupported) + { + createRenderPass(); + } + + createDescriptorSetLayout(); + createGraphicsPipeline(); + + // Create framebuffers only if not using dynamic rendering + if (!appInfo.profileSupported) + { + createFramebuffers(); + } + + createCommandPool(); + createColorResources(); + createDepthResources(); + createTextureImage(); + createTextureImageView(); + createTextureSampler(); + loadModel(); + createVertexBuffer(); + createIndexBuffer(); + createUniformBuffers(); + createDescriptorPool(); + createDescriptorSets(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() + { + while (!glfwWindowShouldClose(window)) + { + glfwPollEvents(); + drawFrame(); + } + + device.waitIdle(); + } + + void cleanupSwapChain() + { + colorImageView = nullptr; + colorImage = nullptr; + colorImageMemory = nullptr; + + depthImageView = nullptr; + depthImage = nullptr; + depthImageMemory = nullptr; + + for (auto &framebuffer : swapChainFramebuffers) + { + framebuffer = nullptr; + } + + for (auto &imageView : swapChainImageViews) + { + imageView = nullptr; + } + + swapChain = nullptr; + } + + void cleanup() + { + glfwDestroyWindow(window); + glfwTerminate(); + } + + void recreateSwapChain() + { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) + { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + device.waitIdle(); + + cleanupSwapChain(); + + createSwapChain(); + createImageViews(); + + // Recreate traditional render pass and framebuffers if not using profiles + if (!appInfo.profileSupported) + { + createRenderPass(); + createFramebuffers(); + } + + createColorResources(); + createDepthResources(); + } + + void createInstance() + { + constexpr vk::ApplicationInfo appInfo{ + .pApplicationName = "Vulkan Profiles Demo", + .applicationVersion = VK_MAKE_VERSION(1, 0, 0), + .pEngineName = "No Engine", + .engineVersion = VK_MAKE_VERSION(1, 0, 0), + .apiVersion = vk::ApiVersion14}; + + auto extensions = getRequiredExtensions(); + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledExtensionCount = static_cast(extensions.size()), + .ppEnabledExtensionNames = extensions.data()}; + + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + // Always set up the debug messenger + // It will only be used if validation layers are enabled via vulkanconfig + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( + vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | + vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | + vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( + vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | + vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | + vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + + try + { + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + catch (vk::SystemError &err) + { + // If the debug utils extension is not available, this will fail + // That's okay, it just means validation layers aren't enabled + std::cout << "Debug messenger not available. Validation layers may not be enabled." << std::endl; + } + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + return supportsGraphics && supportsAllRequiredExtensions; + }); + + if (devIter != devices.end()) + { + physicalDevice = *devIter; + msaaSamples = getMaxUsableSampleCount(); + + // Print device information + vk::PhysicalDeviceProperties deviceProperties = physicalDevice.getProperties(); + std::cout << "Selected GPU: " << deviceProperties.deviceName << std::endl; + std::cout << "API Version: " << VK_VERSION_MAJOR(deviceProperties.apiVersion) << "." + << VK_VERSION_MINOR(deviceProperties.apiVersion) << "." + << VK_VERSION_PATCH(deviceProperties.apiVersion) << std::endl; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void checkFeatureSupport() + { + // Define the KHR roadmap 2022 profile - more widely supported than 2024 + appInfo.profile = { + VP_KHR_ROADMAP_2022_NAME, + VP_KHR_ROADMAP_2022_SPEC_VERSION}; + + // Check if the profile is supported + VkBool32 supported = VK_FALSE; + VkResult result = vpGetPhysicalDeviceProfileSupport( *instance, *physicalDevice, &appInfo.profile, - &supported - ); - - if (result == VK_SUCCESS && supported == VK_TRUE) { - appInfo.profileSupported = true; - std::cout << "Using KHR roadmap 2022 profile" << std::endl; - } else { - appInfo.profileSupported = false; - std::cout << "Falling back to traditional rendering (profile not supported)" << std::endl; - - // If we wanted to implement fallback, we would call detectFeatureSupport() here - // But for this example, we'll just use traditional rendering if the profile isn't supported - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - float queuePriority = 1.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - - if (appInfo.profileSupported) { - // Create device with Best Practices profile - - // Enable required features - vk::PhysicalDeviceFeatures2 features2; - vk::PhysicalDeviceFeatures deviceFeatures{}; - deviceFeatures.samplerAnisotropy = VK_TRUE; - deviceFeatures.sampleRateShading = VK_TRUE; - features2.features = deviceFeatures; - - // Enable dynamic rendering - vk::PhysicalDeviceDynamicRenderingFeatures dynamicRenderingFeatures; - dynamicRenderingFeatures.dynamicRendering = VK_TRUE; - features2.pNext = &dynamicRenderingFeatures; - - // Create a vk::DeviceCreateInfo with the required features - vk::DeviceCreateInfo vkDeviceCreateInfo{ - .pNext = &features2, - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() - }; - - // Create the device with the vk::DeviceCreateInfo - device = vk::raii::Device(physicalDevice, vkDeviceCreateInfo); - - std::cout << "Created logical device using KHR roadmap 2022 profile" << std::endl; - } else { - // Fallback to manual device creation - vk::PhysicalDeviceFeatures deviceFeatures{}; - deviceFeatures.samplerAnisotropy = VK_TRUE; - deviceFeatures.sampleRateShading = VK_TRUE; - - vk::DeviceCreateInfo createInfo{ - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data(), - .pEnabledFeatures = &deviceFeatures - }; - - device = vk::raii::Device(physicalDevice, createInfo); - - std::cout << "Created logical device using manual feature selection" << std::endl; - } - - queue = device.getQueue(queueIndex, 0); - } - - void createSwapChain() { - SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); - - vk::SurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); - vk::PresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); - vk::Extent2D extent = chooseSwapExtent(swapChainSupport.capabilities); - - uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; - if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { - imageCount = swapChainSupport.capabilities.maxImageCount; - } - - vk::SwapchainCreateInfoKHR createInfo{ - .surface = *surface, - .minImageCount = imageCount, - .imageFormat = surfaceFormat.format, - .imageColorSpace = surfaceFormat.colorSpace, - .imageExtent = extent, - .imageArrayLayers = 1, - .imageUsage = vk::ImageUsageFlagBits::eColorAttachment, - .imageSharingMode = vk::SharingMode::eExclusive - }; - - createInfo.preTransform = swapChainSupport.capabilities.currentTransform; - createInfo.compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque; - createInfo.presentMode = presentMode; - createInfo.clipped = VK_TRUE; - - swapChain = device.createSwapchainKHR(createInfo); - swapChainImages = swapChain.getImages(); - swapChainImageFormat = surfaceFormat.format; - swapChainExtent = extent; - } - - void createImageViews() { - swapChainImageViews.reserve(swapChainImages.size()); - - for (const auto& image : swapChainImages) { - swapChainImageViews.push_back(createImageView(image, swapChainImageFormat, vk::ImageAspectFlagBits::eColor, 1)); - } - } - - void createRenderPass() { - // This is only called if the Best Practices profile is not supported - // or if dynamic rendering is not available - vk::AttachmentDescription colorAttachment{ - .format = swapChainImageFormat, - .samples = msaaSamples, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .stencilLoadOp = vk::AttachmentLoadOp::eDontCare, - .stencilStoreOp = vk::AttachmentStoreOp::eDontCare, - .initialLayout = vk::ImageLayout::eUndefined, - .finalLayout = vk::ImageLayout::eColorAttachmentOptimal - }; - - vk::AttachmentDescription depthAttachment{ - .format = findDepthFormat(), - .samples = msaaSamples, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eDontCare, - .stencilLoadOp = vk::AttachmentLoadOp::eDontCare, - .stencilStoreOp = vk::AttachmentStoreOp::eDontCare, - .initialLayout = vk::ImageLayout::eUndefined, - .finalLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal - }; - - vk::AttachmentDescription colorAttachmentResolve{ - .format = swapChainImageFormat, - .samples = vk::SampleCountFlagBits::e1, - .loadOp = vk::AttachmentLoadOp::eDontCare, - .storeOp = vk::AttachmentStoreOp::eStore, - .stencilLoadOp = vk::AttachmentLoadOp::eDontCare, - .stencilStoreOp = vk::AttachmentStoreOp::eDontCare, - .initialLayout = vk::ImageLayout::eUndefined, - .finalLayout = vk::ImageLayout::ePresentSrcKHR - }; - - vk::AttachmentReference colorAttachmentRef{ - .attachment = 0, - .layout = vk::ImageLayout::eColorAttachmentOptimal - }; - - vk::AttachmentReference depthAttachmentRef{ - .attachment = 1, - .layout = vk::ImageLayout::eDepthStencilAttachmentOptimal - }; - - vk::AttachmentReference colorAttachmentResolveRef{ - .attachment = 2, - .layout = vk::ImageLayout::eColorAttachmentOptimal - }; - - vk::SubpassDescription subpass{ - .pipelineBindPoint = vk::PipelineBindPoint::eGraphics, - .colorAttachmentCount = 1, - .pColorAttachments = &colorAttachmentRef, - .pResolveAttachments = &colorAttachmentResolveRef, - .pDepthStencilAttachment = &depthAttachmentRef - }; - - vk::SubpassDependency dependency{ - .srcSubpass = VK_SUBPASS_EXTERNAL, - .dstSubpass = 0, - .srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eEarlyFragmentTests, - .dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eEarlyFragmentTests, - .srcAccessMask = vk::AccessFlagBits::eNone, - .dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite | vk::AccessFlagBits::eDepthStencilAttachmentWrite - }; - - std::array attachments = {colorAttachment, depthAttachment, colorAttachmentResolve}; - vk::RenderPassCreateInfo renderPassInfo{ - .attachmentCount = static_cast(attachments.size()), - .pAttachments = attachments.data(), - .subpassCount = 1, - .pSubpasses = &subpass, - .dependencyCount = 1, - .pDependencies = &dependency - }; - - renderPass = device.createRenderPass(renderPassInfo); - } - - void createDescriptorSetLayout() { - vk::DescriptorSetLayoutBinding uboLayoutBinding{ - .binding = 0, - .descriptorType = vk::DescriptorType::eUniformBuffer, - .descriptorCount = 1, - .stageFlags = vk::ShaderStageFlagBits::eVertex - }; - - vk::DescriptorSetLayoutBinding samplerLayoutBinding{ - .binding = 1, - .descriptorType = vk::DescriptorType::eCombinedImageSampler, - .descriptorCount = 1, - .stageFlags = vk::ShaderStageFlagBits::eFragment - }; - - std::array bindings = {uboLayoutBinding, samplerLayoutBinding}; - vk::DescriptorSetLayoutCreateInfo layoutInfo{ - .bindingCount = static_cast(bindings.size()), - .pBindings = bindings.data() - }; - - descriptorSetLayout = device.createDescriptorSetLayout(layoutInfo); - } - - void createGraphicsPipeline() { - auto vertShaderCode = readFile("shaders/vert.spv"); - auto fragShaderCode = readFile("shaders/frag.spv"); - - vk::raii::ShaderModule vertShaderModule = createShaderModule(vertShaderCode); - vk::raii::ShaderModule fragShaderModule = createShaderModule(fragShaderCode); - - vk::PipelineShaderStageCreateInfo vertShaderStageInfo{ - .stage = vk::ShaderStageFlagBits::eVertex, - .module = *vertShaderModule, - .pName = "main" - }; - - vk::PipelineShaderStageCreateInfo fragShaderStageInfo{ - .stage = vk::ShaderStageFlagBits::eFragment, - .module = *fragShaderModule, - .pName = "main" - }; - - vk::PipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; - - auto bindingDescription = Vertex::getBindingDescription(); - auto attributeDescriptions = Vertex::getAttributeDescriptions(); - - vk::PipelineVertexInputStateCreateInfo vertexInputInfo{ - .vertexBindingDescriptionCount = 1, - .pVertexBindingDescriptions = &bindingDescription, - .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), - .pVertexAttributeDescriptions = attributeDescriptions.data() - }; - - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ - .topology = vk::PrimitiveTopology::eTriangleList, - .primitiveRestartEnable = VK_FALSE - }; - - vk::PipelineViewportStateCreateInfo viewportState{ - .viewportCount = 1, - .scissorCount = 1 - }; - - vk::PipelineRasterizationStateCreateInfo rasterizer{ - .depthClampEnable = VK_FALSE, - .rasterizerDiscardEnable = VK_FALSE, - .polygonMode = vk::PolygonMode::eFill, - .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eCounterClockwise, - .depthBiasEnable = VK_FALSE, - .lineWidth = 1.0f - }; - - vk::PipelineMultisampleStateCreateInfo multisampling{ - .rasterizationSamples = msaaSamples, - .sampleShadingEnable = VK_TRUE, - .minSampleShading = 0.2f - }; - - vk::PipelineDepthStencilStateCreateInfo depthStencil{ - .depthTestEnable = VK_TRUE, - .depthWriteEnable = VK_TRUE, - .depthCompareOp = vk::CompareOp::eLess, - .depthBoundsTestEnable = VK_FALSE, - .stencilTestEnable = VK_FALSE - }; - - vk::PipelineColorBlendAttachmentState colorBlendAttachment{ - .blendEnable = VK_FALSE, - .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA - }; - - vk::PipelineColorBlendStateCreateInfo colorBlending{ - .logicOpEnable = VK_FALSE, - .logicOp = vk::LogicOp::eCopy, - .attachmentCount = 1, - .pAttachments = &colorBlendAttachment - }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - - vk::PipelineDynamicStateCreateInfo dynamicState{ - .dynamicStateCount = static_cast(dynamicStates.size()), - .pDynamicStates = dynamicStates.data() - }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ - .setLayoutCount = 1, - .pSetLayouts = &*descriptorSetLayout - }; - - pipelineLayout = device.createPipelineLayout(pipelineLayoutInfo); - - vk::GraphicsPipelineCreateInfo pipelineInfo{ - .stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pDepthStencilState = &depthStencil, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = *pipelineLayout - }; - - // Configure pipeline based on whether we're using the KHR roadmap 2022 profile - if (appInfo.profileSupported) { - // With the KHR roadmap 2022 profile, we can use dynamic rendering - vk::Format colorFormat = swapChainImageFormat; - vk::Format depthFormat = findDepthFormat(); - - vk::PipelineRenderingCreateInfo renderingInfo{ - .colorAttachmentCount = 1, - .pColorAttachmentFormats = &colorFormat, - .depthAttachmentFormat = depthFormat - }; - - pipelineInfo.pNext = &renderingInfo; - pipelineInfo.renderPass = nullptr; - - std::cout << "Creating pipeline with dynamic rendering (KHR roadmap 2022 profile)" << std::endl; - } else { - // Without the profile, use traditional render pass if dynamic rendering is not available - pipelineInfo.pNext = nullptr; - pipelineInfo.renderPass = *renderPass; - pipelineInfo.subpass = 0; - - std::cout << "Creating pipeline with traditional render pass (fallback)" << std::endl; - } - - graphicsPipeline = device.createGraphicsPipeline(nullptr, pipelineInfo); - } - - void createFramebuffers() { - // This is only called if the Best Practices profile is not supported - // or if dynamic rendering is not available - swapChainFramebuffers.reserve(swapChainImageViews.size()); - - for (size_t i = 0; i < swapChainImageViews.size(); i++) { - std::array attachments = { - *colorImageView, - *depthImageView, - *swapChainImageViews[i] - }; - - vk::FramebufferCreateInfo framebufferInfo{ - .renderPass = *renderPass, - .attachmentCount = static_cast(attachments.size()), - .pAttachments = attachments.data(), - .width = swapChainExtent.width, - .height = swapChainExtent.height, - .layers = 1 - }; - - swapChainFramebuffers.push_back(device.createFramebuffer(framebufferInfo)); - } - } - - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{ - .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex - }; - - commandPool = device.createCommandPool(poolInfo); - } - - void createColorResources() { - vk::Format colorFormat = swapChainImageFormat; - - createImage(swapChainExtent.width, swapChainExtent.height, 1, msaaSamples, colorFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransientAttachment | vk::ImageUsageFlagBits::eColorAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, colorImage, colorImageMemory); - colorImageView = createImageView(*colorImage, colorFormat, vk::ImageAspectFlagBits::eColor, 1); - } - - void createDepthResources() { - vk::Format depthFormat = findDepthFormat(); - - createImage(swapChainExtent.width, swapChainExtent.height, 1, msaaSamples, depthFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eDepthStencilAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, depthImage, depthImageMemory); - depthImageView = createImageView(*depthImage, depthFormat, vk::ImageAspectFlagBits::eDepth, 1); - } - - vk::Format findSupportedFormat(const std::vector& candidates, vk::ImageTiling tiling, vk::FormatFeatureFlags features) { - for (vk::Format format : candidates) { - vk::FormatProperties props = physicalDevice.getFormatProperties(format); - - if (tiling == vk::ImageTiling::eLinear && (props.linearTilingFeatures & features) == features) { - return format; - } else if (tiling == vk::ImageTiling::eOptimal && (props.optimalTilingFeatures & features) == features) { - return format; - } - } - - throw std::runtime_error("failed to find supported format!"); - } - - vk::Format findDepthFormat() { - return findSupportedFormat( - {vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, vk::Format::eD24UnormS8Uint}, - vk::ImageTiling::eOptimal, - vk::FormatFeatureFlagBits::eDepthStencilAttachment - ); - } - - bool hasStencilComponent(vk::Format format) { - return format == vk::Format::eD32SfloatS8Uint || format == vk::Format::eD24UnormS8Uint; - } - - void createTextureImage() { - int texWidth, texHeight, texChannels; - stbi_uc* pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); - vk::DeviceSize imageSize = texWidth * texHeight * 4; - uint32_t mipLevels = static_cast(std::floor(std::log2(std::max(texWidth, texHeight)))) + 1; - - if (!pixels) { - throw std::runtime_error("failed to load texture image!"); - } - - vk::raii::Buffer stagingBuffer = nullptr; - vk::raii::DeviceMemory stagingBufferMemory = nullptr; - - createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, imageSize); - memcpy(data, pixels, static_cast(imageSize)); - stagingBufferMemory.unmapMemory(); - - stbi_image_free(pixels); - - createImage(texWidth, texHeight, mipLevels, vk::SampleCountFlagBits::e1, vk::Format::eR8G8B8A8Srgb, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory); - - transitionImageLayout(*textureImage, vk::Format::eR8G8B8A8Srgb, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, mipLevels); - copyBufferToImage(*stagingBuffer, *textureImage, static_cast(texWidth), static_cast(texHeight)); - - generateMipmaps(*textureImage, vk::Format::eR8G8B8A8Srgb, texWidth, texHeight, mipLevels); - } - - void generateMipmaps(vk::Image image, vk::Format imageFormat, int32_t texWidth, int32_t texHeight, uint32_t mipLevels) { - vk::FormatProperties formatProperties = physicalDevice.getFormatProperties(imageFormat); - - if (!(formatProperties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eSampledImageFilterLinear)) { - throw std::runtime_error("texture image format does not support linear blitting!"); - } - - vk::raii::CommandBuffer commandBuffer = beginSingleTimeCommands(); - - vk::ImageMemoryBarrier barrier{ - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = image, - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1, - } - }; - - int32_t mipWidth = texWidth; - int32_t mipHeight = texHeight; - - for (uint32_t i = 1; i < mipLevels; i++) { - barrier.subresourceRange.baseMipLevel = i - 1; - barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal; - barrier.newLayout = vk::ImageLayout::eTransferSrcOptimal; - barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; - barrier.dstAccessMask = vk::AccessFlagBits::eTransferRead; - - commandBuffer.pipelineBarrier( - vk::PipelineStageFlagBits::eTransfer, - vk::PipelineStageFlagBits::eTransfer, - {}, - std::array{}, - std::array{}, - std::array{barrier}); - - vk::ImageBlit blit{ - .srcSubresource = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .mipLevel = i - 1, - .baseArrayLayer = 0, - .layerCount = 1 - }, - .srcOffsets = std::array{ - vk::Offset3D{0, 0, 0}, - vk::Offset3D{mipWidth, mipHeight, 1} - }, - .dstSubresource = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .mipLevel = i, - .baseArrayLayer = 0, - .layerCount = 1 - }, - .dstOffsets = std::array{ - vk::Offset3D{0, 0, 0}, - vk::Offset3D{mipWidth > 1 ? mipWidth / 2 : 1, mipHeight > 1 ? mipHeight / 2 : 1, 1} - } - }; - - commandBuffer.blitImage( - image, vk::ImageLayout::eTransferSrcOptimal, - image, vk::ImageLayout::eTransferDstOptimal, - std::array{blit}, - vk::Filter::eLinear); - - barrier.oldLayout = vk::ImageLayout::eTransferSrcOptimal; - barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal; - barrier.srcAccessMask = vk::AccessFlagBits::eTransferRead; - barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - - commandBuffer.pipelineBarrier( - vk::PipelineStageFlagBits::eTransfer, - vk::PipelineStageFlagBits::eFragmentShader, - {}, - std::array{}, - std::array{}, - std::array{barrier}); - - if (mipWidth > 1) mipWidth /= 2; - if (mipHeight > 1) mipHeight /= 2; - } - - barrier.subresourceRange.baseMipLevel = mipLevels - 1; - barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal; - barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal; - barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; - barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - - commandBuffer.pipelineBarrier( - vk::PipelineStageFlagBits::eTransfer, - vk::PipelineStageFlagBits::eFragmentShader, - {}, - std::array{}, - std::array{}, - std::array{barrier}); - - endSingleTimeCommands(commandBuffer); - } - - vk::raii::ImageView createImageView(vk::Image image, vk::Format format, vk::ImageAspectFlags aspectFlags, uint32_t mipLevels) { - vk::ImageViewCreateInfo viewInfo{ - .image = image, - .viewType = vk::ImageViewType::e2D, - .format = format, - .subresourceRange = { - .aspectMask = aspectFlags, - .baseMipLevel = 0, - .levelCount = mipLevels, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - - return device.createImageView(viewInfo); - } - - void createTextureImageView() { - textureImageView = createImageView(*textureImage, vk::Format::eR8G8B8A8Srgb, vk::ImageAspectFlagBits::eColor, 1); - } - - void createTextureSampler() { - vk::PhysicalDeviceProperties properties = physicalDevice.getProperties(); - - vk::SamplerCreateInfo samplerInfo{ - .magFilter = vk::Filter::eLinear, - .minFilter = vk::Filter::eLinear, - .mipmapMode = vk::SamplerMipmapMode::eLinear, - .addressModeU = vk::SamplerAddressMode::eRepeat, - .addressModeV = vk::SamplerAddressMode::eRepeat, - .addressModeW = vk::SamplerAddressMode::eRepeat, - .mipLodBias = 0.0f, - .anisotropyEnable = VK_TRUE, - .maxAnisotropy = properties.limits.maxSamplerAnisotropy, - .compareEnable = VK_FALSE, - .compareOp = vk::CompareOp::eAlways, - .minLod = 0.0f, - .maxLod = 0.0f, - .borderColor = vk::BorderColor::eIntOpaqueBlack, - .unnormalizedCoordinates = VK_FALSE - }; - - textureSampler = device.createSampler(samplerInfo); - } - - void createVertexBuffer() { - vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); - - vk::raii::Buffer stagingBuffer = nullptr; - vk::raii::DeviceMemory stagingBufferMemory = nullptr; - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(data, vertices.data(), (size_t) bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); - - copyBuffer(*stagingBuffer, *vertexBuffer, bufferSize); - } - - void createIndexBuffer() { - vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); - - vk::raii::Buffer stagingBuffer = nullptr; - vk::raii::DeviceMemory stagingBufferMemory = nullptr; - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(data, indices.data(), (size_t) bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); - - copyBuffer(*stagingBuffer, *indexBuffer, bufferSize); - } - - void createUniformBuffers() { - vk::DeviceSize bufferSize = sizeof(UniformBufferObject); - - // Reserve space but don't resize, as RAII objects can't be default-constructed - uniformBuffers.reserve(MAX_FRAMES_IN_FLIGHT); - uniformBuffersMemory.reserve(MAX_FRAMES_IN_FLIGHT); - uniformBuffersMapped.resize(MAX_FRAMES_IN_FLIGHT); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::raii::Buffer buffer = nullptr; - vk::raii::DeviceMemory bufferMemory = nullptr; - createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMemory); - - uniformBuffers.push_back(std::move(buffer)); - uniformBuffersMemory.push_back(std::move(bufferMemory)); - uniformBuffersMapped[i] = uniformBuffersMemory[i].mapMemory(0, bufferSize); - } - } - - void createDescriptorPool() { - std::array poolSizes{}; - poolSizes[0].type = vk::DescriptorType::eUniformBuffer; - poolSizes[0].descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT); - poolSizes[1].type = vk::DescriptorType::eCombinedImageSampler; - poolSizes[1].descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT); - - vk::DescriptorPoolCreateInfo poolInfo{ - .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, - .maxSets = static_cast(MAX_FRAMES_IN_FLIGHT), - .poolSizeCount = static_cast(poolSizes.size()), - .pPoolSizes = poolSizes.data() - }; - - descriptorPool = device.createDescriptorPool(poolInfo); - } - - void createDescriptorSets() { - std::vector layouts(MAX_FRAMES_IN_FLIGHT, *descriptorSetLayout); - vk::DescriptorSetAllocateInfo allocInfo{ - .descriptorPool = *descriptorPool, - .descriptorSetCount = static_cast(MAX_FRAMES_IN_FLIGHT), - .pSetLayouts = layouts.data() - }; - - descriptorSets = device.allocateDescriptorSets(allocInfo); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DescriptorBufferInfo bufferInfo{ - .buffer = *uniformBuffers[i], - .offset = 0, - .range = sizeof(UniformBufferObject) - }; - - vk::DescriptorImageInfo imageInfo{ - .sampler = *textureSampler, - .imageView = *textureImageView, - .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal - }; - - std::array descriptorWrites{}; - - descriptorWrites[0].dstSet = *descriptorSets[i]; - descriptorWrites[0].dstBinding = 0; - descriptorWrites[0].dstArrayElement = 0; - descriptorWrites[0].descriptorType = vk::DescriptorType::eUniformBuffer; - descriptorWrites[0].descriptorCount = 1; - descriptorWrites[0].pBufferInfo = &bufferInfo; - - descriptorWrites[1].dstSet = *descriptorSets[i]; - descriptorWrites[1].dstBinding = 1; - descriptorWrites[1].dstArrayElement = 0; - descriptorWrites[1].descriptorType = vk::DescriptorType::eCombinedImageSampler; - descriptorWrites[1].descriptorCount = 1; - descriptorWrites[1].pImageInfo = &imageInfo; - - device.updateDescriptorSets(descriptorWrites, nullptr); - } - } - - void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer& buffer, vk::raii::DeviceMemory& bufferMemory) { - vk::BufferCreateInfo bufferInfo{ - .size = size, - .usage = usage, - .sharingMode = vk::SharingMode::eExclusive - }; - - buffer = device.createBuffer(bufferInfo); - - vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); - - vk::MemoryAllocateInfo allocInfo{ - .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) - }; - - bufferMemory = device.allocateMemory(allocInfo); - buffer.bindMemory(*bufferMemory, 0); - } - - void copyBuffer(vk::Buffer srcBuffer, vk::Buffer dstBuffer, vk::DeviceSize size) { - vk::raii::CommandBuffer commandBuffer = beginSingleTimeCommands(); - - vk::BufferCopy copyRegion{ - .size = size - }; - commandBuffer.copyBuffer(srcBuffer, dstBuffer, copyRegion); - - endSingleTimeCommands(commandBuffer); - } - - void copyBufferToImage(vk::Buffer buffer, vk::Image image, uint32_t width, uint32_t height) { - vk::raii::CommandBuffer commandBuffer = beginSingleTimeCommands(); - - vk::BufferImageCopy region{ - .bufferOffset = 0, - .bufferRowLength = 0, - .bufferImageHeight = 0, - .imageSubresource = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .mipLevel = 0, - .baseArrayLayer = 0, - .layerCount = 1 - }, - .imageOffset = {0, 0, 0}, - .imageExtent = { - width, - height, - 1 - } - }; - - commandBuffer.copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, region); - - endSingleTimeCommands(commandBuffer); - } - - void createImage(uint32_t width, uint32_t height, uint32_t mipLevels, vk::SampleCountFlagBits numSamples, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image& image, vk::raii::DeviceMemory& imageMemory) { - vk::ImageCreateInfo imageInfo{ - .imageType = vk::ImageType::e2D, - .format = format, - .extent = { - .width = width, - .height = height, - .depth = 1 - }, - .mipLevels = mipLevels, - .arrayLayers = 1, - .samples = numSamples, - .tiling = tiling, - .usage = usage, - .sharingMode = vk::SharingMode::eExclusive, - .initialLayout = vk::ImageLayout::eUndefined - }; - - image = device.createImage(imageInfo); - - vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); - - vk::MemoryAllocateInfo allocInfo{ - .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) - }; - - imageMemory = device.allocateMemory(allocInfo); - image.bindMemory(*imageMemory, 0); - } - - void transitionImageLayout(vk::Image image, vk::Format format, vk::ImageLayout oldLayout, vk::ImageLayout newLayout, uint32_t mipLevels) { - vk::raii::CommandBuffer commandBuffer = beginSingleTimeCommands(); - - vk::ImageMemoryBarrier barrier{ - .oldLayout = oldLayout, - .newLayout = newLayout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = image, - .subresourceRange = { - .baseMipLevel = 0, - .levelCount = mipLevels, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - - if (newLayout == vk::ImageLayout::eDepthStencilAttachmentOptimal) { - barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eDepth; - - if (hasStencilComponent(format)) { - barrier.subresourceRange.aspectMask |= vk::ImageAspectFlagBits::eStencil; - } - } else { - barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; - } - - vk::PipelineStageFlags sourceStage; - vk::PipelineStageFlags destinationStage; - - if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) { - barrier.srcAccessMask = vk::AccessFlagBits::eNone; - barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; - - sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; - destinationStage = vk::PipelineStageFlagBits::eTransfer; - } else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) { - barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; - barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - - sourceStage = vk::PipelineStageFlagBits::eTransfer; - destinationStage = vk::PipelineStageFlagBits::eFragmentShader; - } else if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eDepthStencilAttachmentOptimal) { - barrier.srcAccessMask = vk::AccessFlagBits::eNone; - barrier.dstAccessMask = vk::AccessFlagBits::eDepthStencilAttachmentRead | vk::AccessFlagBits::eDepthStencilAttachmentWrite; - - sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; - destinationStage = vk::PipelineStageFlagBits::eEarlyFragmentTests; - } else { - throw std::invalid_argument("unsupported layout transition!"); - } - - commandBuffer.pipelineBarrier( - sourceStage, - destinationStage, - {}, - std::array{}, - std::array{}, - std::array{barrier} - ); - - endSingleTimeCommands(commandBuffer); - } - - vk::raii::CommandBuffer beginSingleTimeCommands() { - vk::CommandBufferAllocateInfo allocInfo{ - .commandPool = *commandPool, - .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = 1 - }; - - vk::raii::CommandBuffer commandBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); - - vk::CommandBufferBeginInfo beginInfo{ - .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit - }; - - commandBuffer.begin(beginInfo); - - return commandBuffer; - } - - void endSingleTimeCommands(vk::raii::CommandBuffer& commandBuffer) { - commandBuffer.end(); - - vk::SubmitInfo submitInfo{ - .commandBufferCount = 1, - .pCommandBuffers = &*commandBuffer - }; - - queue.submit(submitInfo, nullptr); - queue.waitIdle(); - } - - void loadModel() { - tinyobj::attrib_t attrib; - std::vector shapes; - std::vector materials; - std::string warn, err; - - if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, MODEL_PATH.c_str())) { - throw std::runtime_error(warn + err); - } - - std::unordered_map uniqueVertices{}; - - for (const auto& shape : shapes) { - for (const auto& index : shape.mesh.indices) { - Vertex vertex{}; - - vertex.pos = { - attrib.vertices[3 * index.vertex_index + 0], - attrib.vertices[3 * index.vertex_index + 1], - attrib.vertices[3 * index.vertex_index + 2] - }; - - vertex.texCoord = { - attrib.texcoords[2 * index.texcoord_index + 0], - 1.0f - attrib.texcoords[2 * index.texcoord_index + 1] - }; - - vertex.color = {1.0f, 1.0f, 1.0f}; - - if (uniqueVertices.count(vertex) == 0) { - uniqueVertices[vertex] = static_cast(vertices.size()); - vertices.push_back(vertex); - } - - indices.push_back(uniqueVertices[vertex]); - } - } - } - - void createCommandBuffers() { - commandBuffers.reserve(MAX_FRAMES_IN_FLIGHT); - - vk::CommandBufferAllocateInfo allocInfo{ - .commandPool = *commandPool, - .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = static_cast(MAX_FRAMES_IN_FLIGHT) - }; - - commandBuffers = device.allocateCommandBuffers(allocInfo); - } - - void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin({}); - - // Transition the swapchain image to the correct layout for rendering - vk::ImageMemoryBarrier imageBarrier{ - .srcAccessMask = vk::AccessFlagBits::eNone, - .dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite, - .oldLayout = vk::ImageLayout::eUndefined, - .newLayout = vk::ImageLayout::eColorAttachmentOptimal, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - - commandBuffers[currentFrame].pipelineBarrier( - vk::PipelineStageFlagBits::eTopOfPipe, - vk::PipelineStageFlagBits::eColorAttachmentOutput, - vk::DependencyFlagBits::eByRegion, - std::array{}, - std::array{}, - std::array{imageBarrier} - ); - - // Clear values for color and depth - vk::ClearValue clearColor{}; - clearColor.color = vk::ClearColorValue(std::array{0.0f, 0.0f, 0.0f, 1.0f}); - - vk::ClearValue clearDepth{}; - clearDepth.depthStencil = vk::ClearDepthStencilValue{1.0f, 0}; - - std::array clearValues = {clearColor, clearDepth}; - - // Use different rendering approach based on profile support - if (appInfo.profileSupported) { - // Use dynamic rendering with the KHR roadmap 2022 profile - vk::RenderingAttachmentInfo colorAttachment{ - .imageView = *colorImageView, - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .resolveMode = vk::ResolveModeFlagBits::eAverage, - .resolveImageView = *swapChainImageViews[imageIndex], - .resolveImageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor - }; - - vk::RenderingAttachmentInfo depthAttachment{ - .imageView = *depthImageView, - .imageLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eDontCare, - .clearValue = clearDepth - }; - - vk::RenderingInfo renderingInfo{ - .renderArea = {{0, 0}, swapChainExtent}, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &colorAttachment, - .pDepthAttachment = &depthAttachment - }; - - commandBuffers[currentFrame].beginRendering(renderingInfo); - - } else { - // Use traditional render pass if not using the KHR roadmap 2022 profile - vk::RenderPassBeginInfo renderPassInfo{ - .renderPass = *renderPass, - .framebuffer = *swapChainFramebuffers[imageIndex], - .renderArea = {{0, 0}, swapChainExtent}, - .clearValueCount = static_cast(clearValues.size()), - .pClearValues = clearValues.data() - }; - - commandBuffers[currentFrame].beginRenderPass(renderPassInfo, vk::SubpassContents::eInline); - - } - - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - - vk::Viewport viewport{ - .x = 0.0f, - .y = 0.0f, - .width = static_cast(swapChainExtent.width), - .height = static_cast(swapChainExtent.height), - .minDepth = 0.0f, - .maxDepth = 1.0f - }; - commandBuffers[currentFrame].setViewport(0, viewport); - - vk::Rect2D scissor{ - .offset = {0, 0}, - .extent = swapChainExtent - }; - commandBuffers[currentFrame].setScissor(0, scissor); - - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint32); - commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, *pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); - commandBuffers[currentFrame].drawIndexed(static_cast(indices.size()), 1, 0, 0, 0); - - if (appInfo.profileSupported) { - commandBuffers[currentFrame].endRendering(); - - // Transition the swapchain image to the correct layout for presentation - vk::ImageMemoryBarrier barrier{ - .srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite, - .dstAccessMask = vk::AccessFlagBits::eNone, - .oldLayout = vk::ImageLayout::eColorAttachmentOptimal, - .newLayout = vk::ImageLayout::ePresentSrcKHR, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - - commandBuffers[currentFrame].pipelineBarrier( - vk::PipelineStageFlagBits::eColorAttachmentOutput, - vk::PipelineStageFlagBits::eBottomOfPipe, - vk::DependencyFlagBits::eByRegion, - std::array{}, - std::array{}, - std::array{barrier} - ); - } else { - commandBuffers[currentFrame].endRenderPass(); - // Traditional render pass already transitions the image to the correct layout - } - - commandBuffers[currentFrame].end(); - } - - void createSyncObjects() { - imageAvailableSemaphores.reserve(MAX_FRAMES_IN_FLIGHT); - renderFinishedSemaphores.reserve(MAX_FRAMES_IN_FLIGHT); - inFlightFences.reserve(MAX_FRAMES_IN_FLIGHT); - presentCompleteSemaphore.reserve(swapChainImages.size()); - - vk::SemaphoreCreateInfo semaphoreInfo{}; - vk::FenceCreateInfo fenceInfo{ - .flags = vk::FenceCreateFlagBits::eSignaled - }; - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - imageAvailableSemaphores.push_back(device.createSemaphore(semaphoreInfo)); - renderFinishedSemaphores.push_back(device.createSemaphore(semaphoreInfo)); - inFlightFences.push_back(device.createFence(fenceInfo)); - } - - for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.push_back(device.createSemaphore(semaphoreInfo)); - } - } - - void updateUniformBuffer(uint32_t currentImage) { - static auto startTime = std::chrono::high_resolution_clock::now(); - - auto currentTime = std::chrono::high_resolution_clock::now(); - float time = std::chrono::duration(currentTime - startTime).count(); - - UniformBufferObject ubo{}; - ubo.model = glm::rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.proj = glm::perspective(glm::radians(45.0f), swapChainExtent.width / (float) swapChainExtent.height, 0.1f, 10.0f); - ubo.proj[1][1] *= -1; - - memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); - } - - void drawFrame() { - static_cast(device.waitForFences({*inFlightFences[currentFrame]}, VK_TRUE, FenceTimeout)); - - uint32_t imageIndex; - try { - auto [result, idx] = swapChain.acquireNextImage(FenceTimeout, *imageAvailableSemaphores[currentFrame]); - imageIndex = idx; - } catch (vk::OutOfDateKHRError&) { - recreateSwapChain(); - return; - } - - updateUniformBuffer(currentFrame); - - device.resetFences({*inFlightFences[currentFrame]}); - - commandBuffers[currentFrame].reset(); - recordCommandBuffer(imageIndex); - - vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); - const vk::SubmitInfo submitInfo{ - .waitSemaphoreCount = 1, - .pWaitSemaphores = &*imageAvailableSemaphores[currentFrame], - .pWaitDstStageMask = &waitDestinationStageMask, - .commandBufferCount = 1, - .pCommandBuffers = &*commandBuffers[currentFrame], - .signalSemaphoreCount = 1, - .pSignalSemaphores = &*presentCompleteSemaphore[imageIndex] - }; - queue.submit(submitInfo, *inFlightFences[currentFrame]); - - const vk::PresentInfoKHR presentInfoKHR{ - .waitSemaphoreCount = 1, - .pWaitSemaphores = &*presentCompleteSemaphore[imageIndex], - .swapchainCount = 1, - .pSwapchains = &*swapChain, - .pImageIndices = &imageIndex - }; - - vk::Result result; - try { - result = queue.presentKHR(presentInfoKHR); - } catch (vk::OutOfDateKHRError&) { - result = vk::Result::eErrorOutOfDateKHR; - } - - if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { - framebufferResized = false; - recreateSwapChain(); - } else if (result != vk::Result::eSuccess) { - throw std::runtime_error("failed to present swap chain image!"); - } - - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; - } - - vk::SampleCountFlagBits getMaxUsableSampleCount() { - vk::PhysicalDeviceProperties physicalDeviceProperties = physicalDevice.getProperties(); - - vk::SampleCountFlags counts = physicalDeviceProperties.limits.framebufferColorSampleCounts & physicalDeviceProperties.limits.framebufferDepthSampleCounts; - if (counts & vk::SampleCountFlagBits::e64) { return vk::SampleCountFlagBits::e64; } - if (counts & vk::SampleCountFlagBits::e32) { return vk::SampleCountFlagBits::e32; } - if (counts & vk::SampleCountFlagBits::e16) { return vk::SampleCountFlagBits::e16; } - if (counts & vk::SampleCountFlagBits::e8) { return vk::SampleCountFlagBits::e8; } - if (counts & vk::SampleCountFlagBits::e4) { return vk::SampleCountFlagBits::e4; } - if (counts & vk::SampleCountFlagBits::e2) { return vk::SampleCountFlagBits::e2; } - - return vk::SampleCountFlagBits::e1; - } - - uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) { - vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); - - for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { - if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { - return i; - } - } - - throw std::runtime_error("failed to find suitable memory type!"); - } - - vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { - return (availableFormats[0].format == vk::Format::eUndefined) - ? vk::SurfaceFormatKHR{vk::Format::eB8G8R8A8Unorm, availableFormats[0].colorSpace} - : availableFormats[0]; - } - - vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : 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) - }; - } - - std::vector getRequiredExtensions() { - // Get the required extensions from GLFW - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - - // Check if the debug utils extension is available - std::vector props = context.enumerateInstanceExtensionProperties(); - bool debugUtilsAvailable = std::ranges::any_of(props, - [](vk::ExtensionProperties const & ep) { - return strcmp(ep.extensionName, vk::EXTDebugUtilsExtensionName) == 0; - }); - - // Always include the debug utils extension if available - // This allows validation layers to be enabled via vulkanconfig - if (debugUtilsAvailable) { - extensions.push_back(vk::EXTDebugUtilsExtensionName); - } else { - std::cout << "VK_EXT_debug_utils extension not available. Validation layers may not work." << std::endl; - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - vk::raii::ShaderModule createShaderModule(const std::vector& code) { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size(), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - 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; - } - - SwapChainSupportDetails querySwapChainSupport(vk::raii::PhysicalDevice device) { - SwapChainSupportDetails details; - details.capabilities = device.getSurfaceCapabilitiesKHR(*surface); - details.formats = device.getSurfaceFormatsKHR(*surface); - details.presentModes = device.getSurfacePresentModesKHR(*surface); - - return details; - } + &supported); + + if (result == VK_SUCCESS && supported == VK_TRUE) + { + appInfo.profileSupported = true; + std::cout << "Using KHR roadmap 2022 profile" << std::endl; + } + else + { + appInfo.profileSupported = false; + std::cout << "Falling back to traditional rendering (profile not supported)" << std::endl; + + // If we wanted to implement fallback, we would call detectFeatureSupport() here + // But for this example, we'll just use traditional rendering if the profile isn't supported + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + float queuePriority = 1.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + + if (appInfo.profileSupported) + { + // Create device with Best Practices profile + + // Enable required features + vk::PhysicalDeviceFeatures2 features2; + vk::PhysicalDeviceFeatures deviceFeatures{}; + deviceFeatures.samplerAnisotropy = VK_TRUE; + deviceFeatures.sampleRateShading = VK_TRUE; + features2.features = deviceFeatures; + + // Enable dynamic rendering + vk::PhysicalDeviceDynamicRenderingFeatures dynamicRenderingFeatures; + dynamicRenderingFeatures.dynamicRendering = VK_TRUE; + features2.pNext = &dynamicRenderingFeatures; + + // Create a vk::DeviceCreateInfo with the required features + vk::DeviceCreateInfo vkDeviceCreateInfo{ + .pNext = &features2, + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + // Create the device with the vk::DeviceCreateInfo + device = vk::raii::Device(physicalDevice, vkDeviceCreateInfo); + + std::cout << "Created logical device using KHR roadmap 2022 profile" << std::endl; + } + else + { + // Fallback to manual device creation + vk::PhysicalDeviceFeatures deviceFeatures{}; + deviceFeatures.samplerAnisotropy = VK_TRUE; + deviceFeatures.sampleRateShading = VK_TRUE; + + vk::DeviceCreateInfo createInfo{ + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data(), + .pEnabledFeatures = &deviceFeatures}; + + device = vk::raii::Device(physicalDevice, createInfo); + + std::cout << "Created logical device using manual feature selection" << std::endl; + } + + queue = device.getQueue(queueIndex, 0); + } + + void createSwapChain() + { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + vk::SurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + vk::PresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + vk::Extent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) + { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + vk::SwapchainCreateInfoKHR createInfo{ + .surface = *surface, + .minImageCount = imageCount, + .imageFormat = surfaceFormat.format, + .imageColorSpace = surfaceFormat.colorSpace, + .imageExtent = extent, + .imageArrayLayers = 1, + .imageUsage = vk::ImageUsageFlagBits::eColorAttachment, + .imageSharingMode = vk::SharingMode::eExclusive}; + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + + swapChain = device.createSwapchainKHR(createInfo); + swapChainImages = swapChain.getImages(); + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + void createImageViews() + { + swapChainImageViews.reserve(swapChainImages.size()); + + for (const auto &image : swapChainImages) + { + swapChainImageViews.push_back(createImageView(image, swapChainImageFormat, vk::ImageAspectFlagBits::eColor, 1)); + } + } + + void createRenderPass() + { + // This is only called if the Best Practices profile is not supported + // or if dynamic rendering is not available + vk::AttachmentDescription colorAttachment{ + .format = swapChainImageFormat, + .samples = msaaSamples, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .stencilLoadOp = vk::AttachmentLoadOp::eDontCare, + .stencilStoreOp = vk::AttachmentStoreOp::eDontCare, + .initialLayout = vk::ImageLayout::eUndefined, + .finalLayout = vk::ImageLayout::eColorAttachmentOptimal}; + + vk::AttachmentDescription depthAttachment{ + .format = findDepthFormat(), + .samples = msaaSamples, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eDontCare, + .stencilLoadOp = vk::AttachmentLoadOp::eDontCare, + .stencilStoreOp = vk::AttachmentStoreOp::eDontCare, + .initialLayout = vk::ImageLayout::eUndefined, + .finalLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal}; + + vk::AttachmentDescription colorAttachmentResolve{ + .format = swapChainImageFormat, + .samples = vk::SampleCountFlagBits::e1, + .loadOp = vk::AttachmentLoadOp::eDontCare, + .storeOp = vk::AttachmentStoreOp::eStore, + .stencilLoadOp = vk::AttachmentLoadOp::eDontCare, + .stencilStoreOp = vk::AttachmentStoreOp::eDontCare, + .initialLayout = vk::ImageLayout::eUndefined, + .finalLayout = vk::ImageLayout::ePresentSrcKHR}; + + vk::AttachmentReference colorAttachmentRef{ + .attachment = 0, + .layout = vk::ImageLayout::eColorAttachmentOptimal}; + + vk::AttachmentReference depthAttachmentRef{ + .attachment = 1, + .layout = vk::ImageLayout::eDepthStencilAttachmentOptimal}; + + vk::AttachmentReference colorAttachmentResolveRef{ + .attachment = 2, + .layout = vk::ImageLayout::eColorAttachmentOptimal}; + + vk::SubpassDescription subpass{ + .pipelineBindPoint = vk::PipelineBindPoint::eGraphics, + .colorAttachmentCount = 1, + .pColorAttachments = &colorAttachmentRef, + .pResolveAttachments = &colorAttachmentResolveRef, + .pDepthStencilAttachment = &depthAttachmentRef}; + + vk::SubpassDependency dependency{ + .srcSubpass = VK_SUBPASS_EXTERNAL, + .dstSubpass = 0, + .srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eEarlyFragmentTests, + .dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eEarlyFragmentTests, + .srcAccessMask = vk::AccessFlagBits::eNone, + .dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite | vk::AccessFlagBits::eDepthStencilAttachmentWrite}; + + std::array attachments = {colorAttachment, depthAttachment, colorAttachmentResolve}; + vk::RenderPassCreateInfo renderPassInfo{ + .attachmentCount = static_cast(attachments.size()), + .pAttachments = attachments.data(), + .subpassCount = 1, + .pSubpasses = &subpass, + .dependencyCount = 1, + .pDependencies = &dependency}; + + renderPass = device.createRenderPass(renderPassInfo); + } + + void createDescriptorSetLayout() + { + vk::DescriptorSetLayoutBinding uboLayoutBinding{ + .binding = 0, + .descriptorType = vk::DescriptorType::eUniformBuffer, + .descriptorCount = 1, + .stageFlags = vk::ShaderStageFlagBits::eVertex}; + + vk::DescriptorSetLayoutBinding samplerLayoutBinding{ + .binding = 1, + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .descriptorCount = 1, + .stageFlags = vk::ShaderStageFlagBits::eFragment}; + + std::array bindings = {uboLayoutBinding, samplerLayoutBinding}; + vk::DescriptorSetLayoutCreateInfo layoutInfo{ + .bindingCount = static_cast(bindings.size()), + .pBindings = bindings.data()}; + + descriptorSetLayout = device.createDescriptorSetLayout(layoutInfo); + } + + void createGraphicsPipeline() + { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); + + vk::raii::ShaderModule vertShaderModule = createShaderModule(vertShaderCode); + vk::raii::ShaderModule fragShaderModule = createShaderModule(fragShaderCode); + + vk::PipelineShaderStageCreateInfo vertShaderStageInfo{ + .stage = vk::ShaderStageFlagBits::eVertex, + .module = *vertShaderModule, + .pName = "main"}; + + vk::PipelineShaderStageCreateInfo fragShaderStageInfo{ + .stage = vk::ShaderStageFlagBits::eFragment, + .module = *fragShaderModule, + .pName = "main"}; + + vk::PipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; + + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{ + .vertexBindingDescriptionCount = 1, + .pVertexBindingDescriptions = &bindingDescription, + .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), + .pVertexAttributeDescriptions = attributeDescriptions.data()}; + + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ + .topology = vk::PrimitiveTopology::eTriangleList, + .primitiveRestartEnable = VK_FALSE}; + + vk::PipelineViewportStateCreateInfo viewportState{ + .viewportCount = 1, + .scissorCount = 1}; + + vk::PipelineRasterizationStateCreateInfo rasterizer{ + .depthClampEnable = VK_FALSE, + .rasterizerDiscardEnable = VK_FALSE, + .polygonMode = vk::PolygonMode::eFill, + .cullMode = vk::CullModeFlagBits::eBack, + .frontFace = vk::FrontFace::eCounterClockwise, + .depthBiasEnable = VK_FALSE, + .lineWidth = 1.0f}; + + vk::PipelineMultisampleStateCreateInfo multisampling{ + .rasterizationSamples = msaaSamples, + .sampleShadingEnable = VK_TRUE, + .minSampleShading = 0.2f}; + + vk::PipelineDepthStencilStateCreateInfo depthStencil{ + .depthTestEnable = VK_TRUE, + .depthWriteEnable = VK_TRUE, + .depthCompareOp = vk::CompareOp::eLess, + .depthBoundsTestEnable = VK_FALSE, + .stencilTestEnable = VK_FALSE}; + + vk::PipelineColorBlendAttachmentState colorBlendAttachment{ + .blendEnable = VK_FALSE, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA}; + + vk::PipelineColorBlendStateCreateInfo colorBlending{ + .logicOpEnable = VK_FALSE, + .logicOp = vk::LogicOp::eCopy, + .attachmentCount = 1, + .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + + vk::PipelineDynamicStateCreateInfo dynamicState{ + .dynamicStateCount = static_cast(dynamicStates.size()), + .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ + .setLayoutCount = 1, + .pSetLayouts = &*descriptorSetLayout}; + + pipelineLayout = device.createPipelineLayout(pipelineLayoutInfo); + + vk::GraphicsPipelineCreateInfo pipelineInfo{ + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pDepthStencilState = &depthStencil, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = *pipelineLayout}; + + // Configure pipeline based on whether we're using the KHR roadmap 2022 profile + if (appInfo.profileSupported) + { + // With the KHR roadmap 2022 profile, we can use dynamic rendering + vk::Format colorFormat = swapChainImageFormat; + vk::Format depthFormat = findDepthFormat(); + + vk::PipelineRenderingCreateInfo renderingInfo{ + .colorAttachmentCount = 1, + .pColorAttachmentFormats = &colorFormat, + .depthAttachmentFormat = depthFormat}; + + pipelineInfo.pNext = &renderingInfo; + pipelineInfo.renderPass = nullptr; + + std::cout << "Creating pipeline with dynamic rendering (KHR roadmap 2022 profile)" << std::endl; + } + else + { + // Without the profile, use traditional render pass if dynamic rendering is not available + pipelineInfo.pNext = nullptr; + pipelineInfo.renderPass = *renderPass; + pipelineInfo.subpass = 0; + + std::cout << "Creating pipeline with traditional render pass (fallback)" << std::endl; + } + + graphicsPipeline = device.createGraphicsPipeline(nullptr, pipelineInfo); + } + + void createFramebuffers() + { + // This is only called if the Best Practices profile is not supported + // or if dynamic rendering is not available + swapChainFramebuffers.reserve(swapChainImageViews.size()); + + for (size_t i = 0; i < swapChainImageViews.size(); i++) + { + std::array attachments = { + *colorImageView, + *depthImageView, + *swapChainImageViews[i]}; + + vk::FramebufferCreateInfo framebufferInfo{ + .renderPass = *renderPass, + .attachmentCount = static_cast(attachments.size()), + .pAttachments = attachments.data(), + .width = swapChainExtent.width, + .height = swapChainExtent.height, + .layers = 1}; + + swapChainFramebuffers.push_back(device.createFramebuffer(framebufferInfo)); + } + } + + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{ + .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex}; + + commandPool = device.createCommandPool(poolInfo); + } + + void createColorResources() + { + vk::Format colorFormat = swapChainImageFormat; + + createImage(swapChainExtent.width, swapChainExtent.height, 1, msaaSamples, colorFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransientAttachment | vk::ImageUsageFlagBits::eColorAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, colorImage, colorImageMemory); + colorImageView = createImageView(*colorImage, colorFormat, vk::ImageAspectFlagBits::eColor, 1); + } + + void createDepthResources() + { + vk::Format depthFormat = findDepthFormat(); + + createImage(swapChainExtent.width, swapChainExtent.height, 1, msaaSamples, depthFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eDepthStencilAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, depthImage, depthImageMemory); + depthImageView = createImageView(*depthImage, depthFormat, vk::ImageAspectFlagBits::eDepth, 1); + } + + vk::Format findSupportedFormat(const std::vector &candidates, vk::ImageTiling tiling, vk::FormatFeatureFlags features) + { + for (vk::Format format : candidates) + { + vk::FormatProperties props = physicalDevice.getFormatProperties(format); + + if (tiling == vk::ImageTiling::eLinear && (props.linearTilingFeatures & features) == features) + { + return format; + } + else if (tiling == vk::ImageTiling::eOptimal && (props.optimalTilingFeatures & features) == features) + { + return format; + } + } + + throw std::runtime_error("failed to find supported format!"); + } + + vk::Format findDepthFormat() + { + return findSupportedFormat( + {vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, vk::Format::eD24UnormS8Uint}, + vk::ImageTiling::eOptimal, + vk::FormatFeatureFlagBits::eDepthStencilAttachment); + } + + bool hasStencilComponent(vk::Format format) + { + return format == vk::Format::eD32SfloatS8Uint || format == vk::Format::eD24UnormS8Uint; + } + + void createTextureImage() + { + int texWidth, texHeight, texChannels; + stbi_uc *pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + vk::DeviceSize imageSize = texWidth * texHeight * 4; + uint32_t mipLevels = static_cast(std::floor(std::log2(std::max(texWidth, texHeight)))) + 1; + + if (!pixels) + { + throw std::runtime_error("failed to load texture image!"); + } + + vk::raii::Buffer stagingBuffer = nullptr; + vk::raii::DeviceMemory stagingBufferMemory = nullptr; + + createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, imageSize); + memcpy(data, pixels, static_cast(imageSize)); + stagingBufferMemory.unmapMemory(); + + stbi_image_free(pixels); + + createImage(texWidth, texHeight, mipLevels, vk::SampleCountFlagBits::e1, vk::Format::eR8G8B8A8Srgb, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory); + + transitionImageLayout(*textureImage, vk::Format::eR8G8B8A8Srgb, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, mipLevels); + copyBufferToImage(*stagingBuffer, *textureImage, static_cast(texWidth), static_cast(texHeight)); + + generateMipmaps(*textureImage, vk::Format::eR8G8B8A8Srgb, texWidth, texHeight, mipLevels); + } + + void generateMipmaps(vk::Image image, vk::Format imageFormat, int32_t texWidth, int32_t texHeight, uint32_t mipLevels) + { + vk::FormatProperties formatProperties = physicalDevice.getFormatProperties(imageFormat); + + if (!(formatProperties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eSampledImageFilterLinear)) + { + throw std::runtime_error("texture image format does not support linear blitting!"); + } + + vk::raii::CommandBuffer commandBuffer = beginSingleTimeCommands(); + + vk::ImageMemoryBarrier barrier{ + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = image, + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }}; + + int32_t mipWidth = texWidth; + int32_t mipHeight = texHeight; + + for (uint32_t i = 1; i < mipLevels; i++) + { + barrier.subresourceRange.baseMipLevel = i - 1; + barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal; + barrier.newLayout = vk::ImageLayout::eTransferSrcOptimal; + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eTransferRead; + + commandBuffer.pipelineBarrier( + vk::PipelineStageFlagBits::eTransfer, + vk::PipelineStageFlagBits::eTransfer, + {}, + std::array{}, + std::array{}, + std::array{barrier}); + + vk::ImageBlit blit{ + .srcSubresource = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .mipLevel = i - 1, + .baseArrayLayer = 0, + .layerCount = 1}, + .srcOffsets = std::array{vk::Offset3D{0, 0, 0}, vk::Offset3D{mipWidth, mipHeight, 1}}, + .dstSubresource = {.aspectMask = vk::ImageAspectFlagBits::eColor, .mipLevel = i, .baseArrayLayer = 0, .layerCount = 1}, + .dstOffsets = std::array{vk::Offset3D{0, 0, 0}, vk::Offset3D{mipWidth > 1 ? mipWidth / 2 : 1, mipHeight > 1 ? mipHeight / 2 : 1, 1}}}; + + commandBuffer.blitImage( + image, vk::ImageLayout::eTransferSrcOptimal, + image, vk::ImageLayout::eTransferDstOptimal, + std::array{blit}, + vk::Filter::eLinear); + + barrier.oldLayout = vk::ImageLayout::eTransferSrcOptimal; + barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal; + barrier.srcAccessMask = vk::AccessFlagBits::eTransferRead; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + + commandBuffer.pipelineBarrier( + vk::PipelineStageFlagBits::eTransfer, + vk::PipelineStageFlagBits::eFragmentShader, + {}, + std::array{}, + std::array{}, + std::array{barrier}); + + if (mipWidth > 1) + mipWidth /= 2; + if (mipHeight > 1) + mipHeight /= 2; + } + + barrier.subresourceRange.baseMipLevel = mipLevels - 1; + barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal; + barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal; + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + + commandBuffer.pipelineBarrier( + vk::PipelineStageFlagBits::eTransfer, + vk::PipelineStageFlagBits::eFragmentShader, + {}, + std::array{}, + std::array{}, + std::array{barrier}); + + endSingleTimeCommands(commandBuffer); + } + + vk::raii::ImageView createImageView(vk::Image image, vk::Format format, vk::ImageAspectFlags aspectFlags, uint32_t mipLevels) + { + vk::ImageViewCreateInfo viewInfo{ + .image = image, + .viewType = vk::ImageViewType::e2D, + .format = format, + .subresourceRange = { + .aspectMask = aspectFlags, + .baseMipLevel = 0, + .levelCount = mipLevels, + .baseArrayLayer = 0, + .layerCount = 1}}; + + return device.createImageView(viewInfo); + } + + void createTextureImageView() + { + textureImageView = createImageView(*textureImage, vk::Format::eR8G8B8A8Srgb, vk::ImageAspectFlagBits::eColor, 1); + } + + void createTextureSampler() + { + vk::PhysicalDeviceProperties properties = physicalDevice.getProperties(); + + vk::SamplerCreateInfo samplerInfo{ + .magFilter = vk::Filter::eLinear, + .minFilter = vk::Filter::eLinear, + .mipmapMode = vk::SamplerMipmapMode::eLinear, + .addressModeU = vk::SamplerAddressMode::eRepeat, + .addressModeV = vk::SamplerAddressMode::eRepeat, + .addressModeW = vk::SamplerAddressMode::eRepeat, + .mipLodBias = 0.0f, + .anisotropyEnable = VK_TRUE, + .maxAnisotropy = properties.limits.maxSamplerAnisotropy, + .compareEnable = VK_FALSE, + .compareOp = vk::CompareOp::eAlways, + .minLod = 0.0f, + .maxLod = 0.0f, + .borderColor = vk::BorderColor::eIntOpaqueBlack, + .unnormalizedCoordinates = VK_FALSE}; + + textureSampler = device.createSampler(samplerInfo); + } + + void createVertexBuffer() + { + vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + + vk::raii::Buffer stagingBuffer = nullptr; + vk::raii::DeviceMemory stagingBufferMemory = nullptr; + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(data, vertices.data(), (size_t) bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); + + copyBuffer(*stagingBuffer, *vertexBuffer, bufferSize); + } + + void createIndexBuffer() + { + vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + + vk::raii::Buffer stagingBuffer = nullptr; + vk::raii::DeviceMemory stagingBufferMemory = nullptr; + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(data, indices.data(), (size_t) bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); + + copyBuffer(*stagingBuffer, *indexBuffer, bufferSize); + } + + void createUniformBuffers() + { + vk::DeviceSize bufferSize = sizeof(UniformBufferObject); + + // Reserve space but don't resize, as RAII objects can't be default-constructed + uniformBuffers.reserve(MAX_FRAMES_IN_FLIGHT); + uniformBuffersMemory.reserve(MAX_FRAMES_IN_FLIGHT); + uniformBuffersMapped.resize(MAX_FRAMES_IN_FLIGHT); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::raii::Buffer buffer = nullptr; + vk::raii::DeviceMemory bufferMemory = nullptr; + createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMemory); + + uniformBuffers.push_back(std::move(buffer)); + uniformBuffersMemory.push_back(std::move(bufferMemory)); + uniformBuffersMapped[i] = uniformBuffersMemory[i].mapMemory(0, bufferSize); + } + } + + void createDescriptorPool() + { + std::array poolSizes{}; + poolSizes[0].type = vk::DescriptorType::eUniformBuffer; + poolSizes[0].descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT); + poolSizes[1].type = vk::DescriptorType::eCombinedImageSampler; + poolSizes[1].descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT); + + vk::DescriptorPoolCreateInfo poolInfo{ + .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, + .maxSets = static_cast(MAX_FRAMES_IN_FLIGHT), + .poolSizeCount = static_cast(poolSizes.size()), + .pPoolSizes = poolSizes.data()}; + + descriptorPool = device.createDescriptorPool(poolInfo); + } + + void createDescriptorSets() + { + std::vector layouts(MAX_FRAMES_IN_FLIGHT, *descriptorSetLayout); + vk::DescriptorSetAllocateInfo allocInfo{ + .descriptorPool = *descriptorPool, + .descriptorSetCount = static_cast(MAX_FRAMES_IN_FLIGHT), + .pSetLayouts = layouts.data()}; + + descriptorSets = device.allocateDescriptorSets(allocInfo); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DescriptorBufferInfo bufferInfo{ + .buffer = *uniformBuffers[i], + .offset = 0, + .range = sizeof(UniformBufferObject)}; + + vk::DescriptorImageInfo imageInfo{ + .sampler = *textureSampler, + .imageView = *textureImageView, + .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal}; + + std::array descriptorWrites{}; + + descriptorWrites[0].dstSet = *descriptorSets[i]; + descriptorWrites[0].dstBinding = 0; + descriptorWrites[0].dstArrayElement = 0; + descriptorWrites[0].descriptorType = vk::DescriptorType::eUniformBuffer; + descriptorWrites[0].descriptorCount = 1; + descriptorWrites[0].pBufferInfo = &bufferInfo; + + descriptorWrites[1].dstSet = *descriptorSets[i]; + descriptorWrites[1].dstBinding = 1; + descriptorWrites[1].dstArrayElement = 0; + descriptorWrites[1].descriptorType = vk::DescriptorType::eCombinedImageSampler; + descriptorWrites[1].descriptorCount = 1; + descriptorWrites[1].pImageInfo = &imageInfo; + + device.updateDescriptorSets(descriptorWrites, nullptr); + } + } + + void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer &buffer, vk::raii::DeviceMemory &bufferMemory) + { + vk::BufferCreateInfo bufferInfo{ + .size = size, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive}; + + buffer = device.createBuffer(bufferInfo); + + vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); + + vk::MemoryAllocateInfo allocInfo{ + .allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + + bufferMemory = device.allocateMemory(allocInfo); + buffer.bindMemory(*bufferMemory, 0); + } + + void copyBuffer(vk::Buffer srcBuffer, vk::Buffer dstBuffer, vk::DeviceSize size) + { + vk::raii::CommandBuffer commandBuffer = beginSingleTimeCommands(); + + vk::BufferCopy copyRegion{ + .size = size}; + commandBuffer.copyBuffer(srcBuffer, dstBuffer, copyRegion); + + endSingleTimeCommands(commandBuffer); + } + + void copyBufferToImage(vk::Buffer buffer, vk::Image image, uint32_t width, uint32_t height) + { + vk::raii::CommandBuffer commandBuffer = beginSingleTimeCommands(); + + vk::BufferImageCopy region{ + .bufferOffset = 0, + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .mipLevel = 0, + .baseArrayLayer = 0, + .layerCount = 1}, + .imageOffset = {0, 0, 0}, + .imageExtent = {width, height, 1}}; + + commandBuffer.copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, region); + + endSingleTimeCommands(commandBuffer); + } + + void createImage(uint32_t width, uint32_t height, uint32_t mipLevels, vk::SampleCountFlagBits numSamples, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image &image, vk::raii::DeviceMemory &imageMemory) + { + vk::ImageCreateInfo imageInfo{ + .imageType = vk::ImageType::e2D, + .format = format, + .extent = { + .width = width, + .height = height, + .depth = 1}, + .mipLevels = mipLevels, + .arrayLayers = 1, + .samples = numSamples, + .tiling = tiling, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive, + .initialLayout = vk::ImageLayout::eUndefined}; + + image = device.createImage(imageInfo); + + vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); + + vk::MemoryAllocateInfo allocInfo{ + .allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + + imageMemory = device.allocateMemory(allocInfo); + image.bindMemory(*imageMemory, 0); + } + + void transitionImageLayout(vk::Image image, vk::Format format, vk::ImageLayout oldLayout, vk::ImageLayout newLayout, uint32_t mipLevels) + { + vk::raii::CommandBuffer commandBuffer = beginSingleTimeCommands(); + + vk::ImageMemoryBarrier barrier{ + .oldLayout = oldLayout, + .newLayout = newLayout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = image, + .subresourceRange = { + .baseMipLevel = 0, + .levelCount = mipLevels, + .baseArrayLayer = 0, + .layerCount = 1}}; + + if (newLayout == vk::ImageLayout::eDepthStencilAttachmentOptimal) + { + barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eDepth; + + if (hasStencilComponent(format)) + { + barrier.subresourceRange.aspectMask |= vk::ImageAspectFlagBits::eStencil; + } + } + else + { + barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; + } + + vk::PipelineStageFlags sourceStage; + vk::PipelineStageFlags destinationStage; + + if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) + { + barrier.srcAccessMask = vk::AccessFlagBits::eNone; + barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; + + sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; + destinationStage = vk::PipelineStageFlagBits::eTransfer; + } + else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) + { + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + + sourceStage = vk::PipelineStageFlagBits::eTransfer; + destinationStage = vk::PipelineStageFlagBits::eFragmentShader; + } + else if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eDepthStencilAttachmentOptimal) + { + barrier.srcAccessMask = vk::AccessFlagBits::eNone; + barrier.dstAccessMask = vk::AccessFlagBits::eDepthStencilAttachmentRead | vk::AccessFlagBits::eDepthStencilAttachmentWrite; + + sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; + destinationStage = vk::PipelineStageFlagBits::eEarlyFragmentTests; + } + else + { + throw std::invalid_argument("unsupported layout transition!"); + } + + commandBuffer.pipelineBarrier( + sourceStage, + destinationStage, + {}, + std::array{}, + std::array{}, + std::array{barrier}); + + endSingleTimeCommands(commandBuffer); + } + + vk::raii::CommandBuffer beginSingleTimeCommands() + { + vk::CommandBufferAllocateInfo allocInfo{ + .commandPool = *commandPool, + .level = vk::CommandBufferLevel::ePrimary, + .commandBufferCount = 1}; + + vk::raii::CommandBuffer commandBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); + + vk::CommandBufferBeginInfo beginInfo{ + .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; + + commandBuffer.begin(beginInfo); + + return commandBuffer; + } + + void endSingleTimeCommands(vk::raii::CommandBuffer &commandBuffer) + { + commandBuffer.end(); + + vk::SubmitInfo submitInfo{ + .commandBufferCount = 1, + .pCommandBuffers = &*commandBuffer}; + + queue.submit(submitInfo, nullptr); + queue.waitIdle(); + } + + void loadModel() + { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn, err; + + if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, MODEL_PATH.c_str())) + { + throw std::runtime_error(warn + err); + } + + std::unordered_map uniqueVertices{}; + + for (const auto &shape : shapes) + { + for (const auto &index : shape.mesh.indices) + { + Vertex vertex{}; + + vertex.pos = { + attrib.vertices[3 * index.vertex_index + 0], + attrib.vertices[3 * index.vertex_index + 1], + attrib.vertices[3 * index.vertex_index + 2]}; + + vertex.texCoord = { + attrib.texcoords[2 * index.texcoord_index + 0], + 1.0f - attrib.texcoords[2 * index.texcoord_index + 1]}; + + vertex.color = {1.0f, 1.0f, 1.0f}; + + if (uniqueVertices.count(vertex) == 0) + { + uniqueVertices[vertex] = static_cast(vertices.size()); + vertices.push_back(vertex); + } + + indices.push_back(uniqueVertices[vertex]); + } + } + } + + void createCommandBuffers() + { + commandBuffers.reserve(MAX_FRAMES_IN_FLIGHT); + + vk::CommandBufferAllocateInfo allocInfo{ + .commandPool = *commandPool, + .level = vk::CommandBufferLevel::ePrimary, + .commandBufferCount = static_cast(MAX_FRAMES_IN_FLIGHT)}; + + commandBuffers = device.allocateCommandBuffers(allocInfo); + } + + void recordCommandBuffer(uint32_t imageIndex) + { + commandBuffers[currentFrame].begin({}); + + // Transition the swapchain image to the correct layout for rendering + vk::ImageMemoryBarrier imageBarrier{ + .srcAccessMask = vk::AccessFlagBits::eNone, + .dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite, + .oldLayout = vk::ImageLayout::eUndefined, + .newLayout = vk::ImageLayout::eColorAttachmentOptimal, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + + commandBuffers[currentFrame].pipelineBarrier( + vk::PipelineStageFlagBits::eTopOfPipe, + vk::PipelineStageFlagBits::eColorAttachmentOutput, + vk::DependencyFlagBits::eByRegion, + std::array{}, + std::array{}, + std::array{imageBarrier}); + + // Clear values for color and depth + vk::ClearValue clearColor{}; + clearColor.color = vk::ClearColorValue(std::array{0.0f, 0.0f, 0.0f, 1.0f}); + + vk::ClearValue clearDepth{}; + clearDepth.depthStencil = vk::ClearDepthStencilValue{1.0f, 0}; + + std::array clearValues = {clearColor, clearDepth}; + + // Use different rendering approach based on profile support + if (appInfo.profileSupported) + { + // Use dynamic rendering with the KHR roadmap 2022 profile + vk::RenderingAttachmentInfo colorAttachment{ + .imageView = *colorImageView, + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .resolveMode = vk::ResolveModeFlagBits::eAverage, + .resolveImageView = *swapChainImageViews[imageIndex], + .resolveImageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor}; + + vk::RenderingAttachmentInfo depthAttachment{ + .imageView = *depthImageView, + .imageLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eDontCare, + .clearValue = clearDepth}; + + vk::RenderingInfo renderingInfo{ + .renderArea = {{0, 0}, swapChainExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &colorAttachment, + .pDepthAttachment = &depthAttachment}; + + commandBuffers[currentFrame].beginRendering(renderingInfo); + } + else + { + // Use traditional render pass if not using the KHR roadmap 2022 profile + vk::RenderPassBeginInfo renderPassInfo{ + .renderPass = *renderPass, + .framebuffer = *swapChainFramebuffers[imageIndex], + .renderArea = {{0, 0}, swapChainExtent}, + .clearValueCount = static_cast(clearValues.size()), + .pClearValues = clearValues.data()}; + + commandBuffers[currentFrame].beginRenderPass(renderPassInfo, vk::SubpassContents::eInline); + } + + commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + + vk::Viewport viewport{ + .x = 0.0f, + .y = 0.0f, + .width = static_cast(swapChainExtent.width), + .height = static_cast(swapChainExtent.height), + .minDepth = 0.0f, + .maxDepth = 1.0f}; + commandBuffers[currentFrame].setViewport(0, viewport); + + vk::Rect2D scissor{ + .offset = {0, 0}, + .extent = swapChainExtent}; + commandBuffers[currentFrame].setScissor(0, scissor); + + commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint32); + commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, *pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); + commandBuffers[currentFrame].drawIndexed(static_cast(indices.size()), 1, 0, 0, 0); + + if (appInfo.profileSupported) + { + commandBuffers[currentFrame].endRendering(); + + // Transition the swapchain image to the correct layout for presentation + vk::ImageMemoryBarrier barrier{ + .srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite, + .dstAccessMask = vk::AccessFlagBits::eNone, + .oldLayout = vk::ImageLayout::eColorAttachmentOptimal, + .newLayout = vk::ImageLayout::ePresentSrcKHR, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + + commandBuffers[currentFrame].pipelineBarrier( + vk::PipelineStageFlagBits::eColorAttachmentOutput, + vk::PipelineStageFlagBits::eBottomOfPipe, + vk::DependencyFlagBits::eByRegion, + std::array{}, + std::array{}, + std::array{barrier}); + } + else + { + commandBuffers[currentFrame].endRenderPass(); + // Traditional render pass already transitions the image to the correct layout + } + + commandBuffers[currentFrame].end(); + } + + void createSyncObjects() + { + imageAvailableSemaphores.reserve(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.reserve(MAX_FRAMES_IN_FLIGHT); + inFlightFences.reserve(MAX_FRAMES_IN_FLIGHT); + presentCompleteSemaphore.reserve(swapChainImages.size()); + + vk::SemaphoreCreateInfo semaphoreInfo{}; + vk::FenceCreateInfo fenceInfo{ + .flags = vk::FenceCreateFlagBits::eSignaled}; + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + imageAvailableSemaphores.push_back(device.createSemaphore(semaphoreInfo)); + renderFinishedSemaphores.push_back(device.createSemaphore(semaphoreInfo)); + inFlightFences.push_back(device.createFence(fenceInfo)); + } + + for (size_t i = 0; i < swapChainImages.size(); i++) + { + presentCompleteSemaphore.push_back(device.createSemaphore(semaphoreInfo)); + } + } + + void updateUniformBuffer(uint32_t currentImage) + { + static auto startTime = std::chrono::high_resolution_clock::now(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + UniformBufferObject ubo{}; + ubo.model = glm::rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.proj = glm::perspective(glm::radians(45.0f), swapChainExtent.width / (float) swapChainExtent.height, 0.1f, 10.0f); + ubo.proj[1][1] *= -1; + + memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); + } + + void drawFrame() + { + static_cast(device.waitForFences({*inFlightFences[currentFrame]}, VK_TRUE, FenceTimeout)); + + uint32_t imageIndex; + try + { + auto [result, idx] = swapChain.acquireNextImage(FenceTimeout, *imageAvailableSemaphores[currentFrame]); + imageIndex = idx; + } + catch (vk::OutOfDateKHRError &) + { + recreateSwapChain(); + return; + } + + updateUniformBuffer(currentFrame); + + device.resetFences({*inFlightFences[currentFrame]}); + + commandBuffers[currentFrame].reset(); + recordCommandBuffer(imageIndex); + + vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); + const vk::SubmitInfo submitInfo{ + .waitSemaphoreCount = 1, + .pWaitSemaphores = &*imageAvailableSemaphores[currentFrame], + .pWaitDstStageMask = &waitDestinationStageMask, + .commandBufferCount = 1, + .pCommandBuffers = &*commandBuffers[currentFrame], + .signalSemaphoreCount = 1, + .pSignalSemaphores = &*presentCompleteSemaphore[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[currentFrame]); + + const vk::PresentInfoKHR presentInfoKHR{ + .waitSemaphoreCount = 1, + .pWaitSemaphores = &*presentCompleteSemaphore[imageIndex], + .swapchainCount = 1, + .pSwapchains = &*swapChain, + .pImageIndices = &imageIndex}; + + vk::Result result; + try + { + result = queue.presentKHR(presentInfoKHR); + } + catch (vk::OutOfDateKHRError &) + { + result = vk::Result::eErrorOutOfDateKHR; + } + + if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) + { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != vk::Result::eSuccess) + { + throw std::runtime_error("failed to present swap chain image!"); + } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + vk::SampleCountFlagBits getMaxUsableSampleCount() + { + vk::PhysicalDeviceProperties physicalDeviceProperties = physicalDevice.getProperties(); + + vk::SampleCountFlags counts = physicalDeviceProperties.limits.framebufferColorSampleCounts & physicalDeviceProperties.limits.framebufferDepthSampleCounts; + if (counts & vk::SampleCountFlagBits::e64) + { + return vk::SampleCountFlagBits::e64; + } + if (counts & vk::SampleCountFlagBits::e32) + { + return vk::SampleCountFlagBits::e32; + } + if (counts & vk::SampleCountFlagBits::e16) + { + return vk::SampleCountFlagBits::e16; + } + if (counts & vk::SampleCountFlagBits::e8) + { + return vk::SampleCountFlagBits::e8; + } + if (counts & vk::SampleCountFlagBits::e4) + { + return vk::SampleCountFlagBits::e4; + } + if (counts & vk::SampleCountFlagBits::e2) + { + return vk::SampleCountFlagBits::e2; + } + + return vk::SampleCountFlagBits::e1; + } + + uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) + { + vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) + { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) + { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + return (availableFormats[0].format == vk::Format::eUndefined) ? vk::SurfaceFormatKHR{vk::Format::eB8G8R8A8Unorm, availableFormats[0].colorSpace} : availableFormats[0]; + } + + vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + 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)}; + } + + std::vector getRequiredExtensions() + { + // Get the required extensions from GLFW + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + // Check if the debug utils extension is available + std::vector props = context.enumerateInstanceExtensionProperties(); + bool debugUtilsAvailable = std::ranges::any_of(props, + [](vk::ExtensionProperties const &ep) { + return strcmp(ep.extensionName, vk::EXTDebugUtilsExtensionName) == 0; + }); + + // Always include the debug utils extension if available + // This allows validation layers to be enabled via vulkanconfig + if (debugUtilsAvailable) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + else + { + std::cout << "VK_EXT_debug_utils extension not available. Validation layers may not work." << std::endl; + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + vk::raii::ShaderModule createShaderModule(const std::vector &code) + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size(), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + 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; + } + + SwapChainSupportDetails querySwapChainSupport(vk::raii::PhysicalDevice device) + { + SwapChainSupportDetails details; + details.capabilities = device.getSurfaceCapabilitiesKHR(*surface); + details.formats = device.getSurfaceFormatsKHR(*surface); + details.presentModes = device.getSurfacePresentModesKHR(*surface); + + return details; + } }; -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/34_android.cpp b/attachments/34_android.cpp index 977c8575..3f8dc6c6 100644 --- a/attachments/34_android.cpp +++ b/attachments/34_android.cpp @@ -1,36 +1,35 @@ -#include -#include -#include -#include -#include -#include -#include #include -#include #include #include +#include +#include +#include +#include +#include +#include #include +#include +#include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include #if defined(__ANDROID__) -#include -#include +# include +# include #endif #include // Platform detection #if defined(__ANDROID__) - #define PLATFORM_ANDROID 1 +# define PLATFORM_ANDROID 1 #else - #define PLATFORM_DESKTOP 1 +# define PLATFORM_DESKTOP 1 #endif - #define STB_IMAGE_IMPLEMENTATION #include @@ -39,40 +38,47 @@ import vulkan_hpp; // Platform-specific includes #if PLATFORM_ANDROID - // Android-specific includes - #include - #include - #include - #include - - // Declare and implement app_dummy function from native_app_glue - extern "C" void app_dummy() { - // This is a dummy function that does nothing - // It's used to prevent the linker from stripping out the native_app_glue code - } - - // Define AAssetManager type for Android - typedef AAssetManager AssetManagerType; - - // Define logging macros for Android - #define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "VulkanTutorial", __VA_ARGS__)) - #define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "VulkanTutorial", __VA_ARGS__)) - #define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "VulkanTutorial", __VA_ARGS__)) - #define LOG_INFO(msg) LOGI("%s", msg) - #define LOG_ERROR(msg) LOGE("%s", msg) +// Android-specific includes +# include +# include +# include +# include + +// Declare and implement app_dummy function from native_app_glue +extern "C" void app_dummy() +{ + // This is a dummy function that does nothing + // It's used to prevent the linker from stripping out the native_app_glue code +} + +// Define AAssetManager type for Android +typedef AAssetManager AssetManagerType; + +// Define logging macros for Android +# define LOGI(...) ((void) __android_log_print(ANDROID_LOG_INFO, "VulkanTutorial", __VA_ARGS__)) +# define LOGW(...) ((void) __android_log_print(ANDROID_LOG_WARN, "VulkanTutorial", __VA_ARGS__)) +# define LOGE(...) ((void) __android_log_print(ANDROID_LOG_ERROR, "VulkanTutorial", __VA_ARGS__)) +# define LOG_INFO(msg) LOGI("%s", msg) +# define LOG_ERROR(msg) LOGE("%s", msg) #else - // Define AAssetManager type for non-Android platforms - typedef void AssetManagerType; - // Desktop-specific includes - #define GLFW_INCLUDE_VULKAN - #include - - // Define logging macros for Desktop - #define LOGI(...) printf(__VA_ARGS__); printf("\n") - #define LOGW(...) printf(__VA_ARGS__); printf("\n") - #define LOGE(...) fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n") - #define LOG_INFO(msg) std::cout << msg << std::endl - #define LOG_ERROR(msg) std::cerr << msg << std::endl +// Define AAssetManager type for non-Android platforms +typedef void AssetManagerType; +// Desktop-specific includes +# define GLFW_INCLUDE_VULKAN +# include + +// Define logging macros for Desktop +# define LOGI(...) \ + printf(__VA_ARGS__); \ + printf("\n") +# define LOGW(...) \ + printf(__VA_ARGS__); \ + printf("\n") +# define LOGE(...) \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "\n") +# define LOG_INFO(msg) std::cout << msg << std::endl +# define LOG_ERROR(msg) std::cerr << msg << std::endl #endif #define GLM_FORCE_RADIANS @@ -83,1642 +89,1682 @@ import vulkan_hpp; #include #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -constexpr uint64_t FenceTimeout = 100000000; -const std::string MODEL_PATH = "models/viking_room.obj"; -const std::string TEXTURE_PATH = "textures/viking_room.png"; -constexpr int MAX_FRAMES_IN_FLIGHT = 2; +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; +constexpr uint64_t FenceTimeout = 100000000; +const std::string MODEL_PATH = "models/viking_room.obj"; +const std::string TEXTURE_PATH = "textures/viking_room.png"; +constexpr int MAX_FRAMES_IN_FLIGHT = 2; #if PLATFORM_ANDROID // Define VpProfileProperties structure if not already defined -#ifndef VP_PROFILE_PROPERTIES_DEFINED -#define VP_PROFILE_PROPERTIES_DEFINED -struct VpProfileProperties { - char name[256]; - uint32_t specVersion; +# ifndef VP_PROFILE_PROPERTIES_DEFINED +# define VP_PROFILE_PROPERTIES_DEFINED +struct VpProfileProperties +{ + char name[256]; + uint32_t specVersion; }; -#endif +# endif // Define Vulkan Profile constants -#ifndef VP_KHR_ROADMAP_2022_NAME -#define VP_KHR_ROADMAP_2022_NAME "VP_KHR_roadmap_2022" -#endif +# ifndef VP_KHR_ROADMAP_2022_NAME +# define VP_KHR_ROADMAP_2022_NAME "VP_KHR_roadmap_2022" +# endif -#ifndef VP_KHR_ROADMAP_2022_SPEC_VERSION -#define VP_KHR_ROADMAP_2022_SPEC_VERSION 1 -#endif +# ifndef VP_KHR_ROADMAP_2022_SPEC_VERSION +# define VP_KHR_ROADMAP_2022_SPEC_VERSION 1 +# endif #endif // Application info structure to store profile support flags -struct AppInfo { - bool profileSupported = false; - VpProfileProperties profile; +struct AppInfo +{ + bool profileSupported = false; + VpProfileProperties profile; }; -struct Vertex { - glm::vec3 pos; - glm::vec3 color; - glm::vec2 texCoord; - - static vk::VertexInputBindingDescription getBindingDescription() { - return { 0, sizeof(Vertex), vk::VertexInputRate::eVertex }; - } - - static std::array getAttributeDescriptions() { - return { - vk::VertexInputAttributeDescription( 0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, pos) ), - vk::VertexInputAttributeDescription( 1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color) ), - vk::VertexInputAttributeDescription( 2, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, texCoord) ) - }; - } - - bool operator==(const Vertex& other) const { - return pos == other.pos && color == other.color && texCoord == other.texCoord; - } +struct Vertex +{ + glm::vec3 pos; + glm::vec3 color; + glm::vec2 texCoord; + + static vk::VertexInputBindingDescription getBindingDescription() + { + return {0, sizeof(Vertex), vk::VertexInputRate::eVertex}; + } + + static std::array getAttributeDescriptions() + { + return { + vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, pos)), + vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color)), + vk::VertexInputAttributeDescription(2, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, texCoord))}; + } + + bool operator==(const Vertex &other) const + { + return pos == other.pos && color == other.color && texCoord == other.texCoord; + } }; -template<> struct std::hash { - size_t operator()(Vertex const& vertex) const noexcept { - return ((hash()(vertex.pos) ^ (hash()(vertex.color) << 1)) >> 1) ^ (hash()(vertex.texCoord) << 1); - } +template <> +struct std::hash +{ + size_t operator()(Vertex const &vertex) const noexcept + { + return ((hash()(vertex.pos) ^ (hash()(vertex.color) << 1)) >> 1) ^ (hash()(vertex.texCoord) << 1); + } }; -struct UniformBufferObject { - alignas(16) glm::mat4 model; - alignas(16) glm::mat4 view; - alignas(16) glm::mat4 proj; +struct UniformBufferObject +{ + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; }; // Cross-platform file reading function -std::vector readFile(const std::string& filename, std::optional assetManager = std::nullopt) { +std::vector readFile(const std::string &filename, std::optional assetManager = std::nullopt) +{ #if PLATFORM_ANDROID - // On Android, use asset manager if provided - if (assetManager.has_value() && *assetManager != nullptr) { - // Open the asset - AAsset* asset = AAssetManager_open(*assetManager, filename.c_str(), AASSET_MODE_BUFFER); - if (!asset) { - LOGE("Failed to open asset: %s", filename.c_str()); - throw std::runtime_error("Failed to open file: " + filename); - } - - // Get the file size - off_t fileSize = AAsset_getLength(asset); - std::vector buffer(fileSize); - - // Read the file data - AAsset_read(asset, buffer.data(), fileSize); - - // Close the asset - AAsset_close(asset); - - return buffer; - } + // On Android, use asset manager if provided + if (assetManager.has_value() && *assetManager != nullptr) + { + // Open the asset + AAsset *asset = AAssetManager_open(*assetManager, filename.c_str(), AASSET_MODE_BUFFER); + if (!asset) + { + LOGE("Failed to open asset: %s", filename.c_str()); + throw std::runtime_error("Failed to open file: " + filename); + } + + // Get the file size + off_t fileSize = AAsset_getLength(asset); + std::vector buffer(fileSize); + + // Read the file data + AAsset_read(asset, buffer.data(), fileSize); + + // Close the asset + AAsset_close(asset); + + return buffer; + } #endif - // Desktop version or Android fallback to filesystem - std::ifstream file(filename, std::ios::ate | std::ios::binary); + // Desktop version or Android fallback to filesystem + std::ifstream file(filename, std::ios::ate | std::ios::binary); - if (!file.is_open()) { - throw std::runtime_error("Failed to open file: " + filename); - } + if (!file.is_open()) + { + throw std::runtime_error("Failed to open file: " + filename); + } - size_t fileSize = static_cast(file.tellg()); - std::vector buffer(fileSize); + size_t fileSize = static_cast(file.tellg()); + std::vector buffer(fileSize); - file.seekg(0); - file.read(buffer.data(), fileSize); - file.close(); + file.seekg(0); + file.read(buffer.data(), fileSize); + file.close(); - return buffer; + return buffer; } // Cross-platform application class -class HelloTriangleApplication { -public: +class HelloTriangleApplication +{ + public: #if PLATFORM_DESKTOP - // Desktop constructor - HelloTriangleApplication() { - // No Android-specific initialization needed - } + // Desktop constructor + HelloTriangleApplication() + { + // No Android-specific initialization needed + } #else - // Android constructor - HelloTriangleApplication(android_app* app) : androidApp(app) { - androidApp->userData = this; - androidApp->onAppCmd = handleAppCommand; - // Note: onInputEvent is no longer a member of android_app in the current NDK version - // Input events are now handled differently - - // Get the asset manager - assetManager = androidApp->activity->assetManager; - } + // Android constructor + HelloTriangleApplication(android_app *app) : + androidApp(app) + { + androidApp->userData = this; + androidApp->onAppCmd = handleAppCommand; + // Note: onInputEvent is no longer a member of android_app in the current NDK version + // Input events are now handled differently + + // Get the asset manager + assetManager = androidApp->activity->assetManager; + } #endif - void run() { + void run() + { #if PLATFORM_DESKTOP - // Desktop main loop - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); + // Desktop main loop + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); #else - // Android main loop is handled by Android - while (!initialized) { - // Wait for app to initialize - int events; - android_poll_source* source; - if (ALooper_pollOnce(0, nullptr, &events, (void**)&source) >= 0) { - if (source != nullptr) { - source->process(androidApp, source); - } - } - } + // Android main loop is handled by Android + while (!initialized) + { + // Wait for app to initialize + int events; + android_poll_source *source; + if (ALooper_pollOnce(0, nullptr, &events, (void **) &source) >= 0) + { + if (source != nullptr) + { + source->process(androidApp, source); + } + } + } #endif - } + } #if PLATFORM_DESKTOP - // Initialize window (Desktop only) - void initWindow() { - glfwInit(); - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan Cross-Platform", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); - - LOG_INFO("Desktop window created"); - } - - // Desktop main loop - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - drawFrame(); - } - - device.waitIdle(); - } - - // Desktop framebuffer resize callback - static void framebufferResizeCallback(GLFWwindow* window, int, int) { - auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); - app->framebufferResized = true; - } + // Initialize window (Desktop only) + void initWindow() + { + glfwInit(); + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan Cross-Platform", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + + LOG_INFO("Desktop window created"); + } + + // Desktop main loop + void mainLoop() + { + while (!glfwWindowShouldClose(window)) + { + glfwPollEvents(); + drawFrame(); + } + + device.waitIdle(); + } + + // Desktop framebuffer resize callback + static void framebufferResizeCallback(GLFWwindow *window, int, int) + { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } #endif - void cleanup() { - if (initialized) { - // Wait for device to finish operations - if (*device) { - device.waitIdle(); - } + void cleanup() + { + if (initialized) + { + // Wait for device to finish operations + if (*device) + { + device.waitIdle(); + } - // Cleanup resources - cleanupSwapChain(); + // Cleanup resources + cleanupSwapChain(); - initialized = false; - } - } + initialized = false; + } + } -private: + private: #if PLATFORM_ANDROID - // Android-specific members - android_app* androidApp = nullptr; - AssetManagerType* assetManager = nullptr; + // Android-specific members + android_app *androidApp = nullptr; + AssetManagerType *assetManager = nullptr; #else - // Desktop-specific members - GLFWwindow* window = nullptr; + // Desktop-specific members + GLFWwindow *window = nullptr; #endif - bool initialized = false; - bool framebufferResized = false; - - // Vulkan objects - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::Format swapChainImageFormat = {}; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - vk::raii::RenderPass renderPass = nullptr; - vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - std::vector swapChainFramebuffers; - vk::raii::CommandPool commandPool = nullptr; - std::vector commandBuffers; - vk::raii::Buffer vertexBuffer = nullptr; - vk::raii::DeviceMemory vertexBufferMemory = nullptr; - vk::raii::Buffer indexBuffer = nullptr; - vk::raii::DeviceMemory indexBufferMemory = nullptr; - vk::raii::Image textureImage = nullptr; - vk::raii::DeviceMemory textureImageMemory = nullptr; - vk::raii::ImageView textureImageView = nullptr; - vk::raii::Sampler textureSampler = nullptr; - std::vector uniformBuffers; - std::vector uniformBuffersMemory; - vk::raii::DescriptorPool descriptorPool = nullptr; - std::vector descriptorSets; - std::vector imageAvailableSemaphores; - std::vector renderFinishedSemaphores; - std::vector inFlightFences; - uint32_t currentFrame = 0; - - // Application info - AppInfo appInfo; - - // Model data - std::vector vertices; - std::vector indices; - - // Swap chain support details - struct SwapChainSupportDetails { - vk::SurfaceCapabilitiesKHR capabilities; - std::vector formats; - std::vector presentModes; - }; - - // Required device extensions - const std::vector deviceExtensions = { - VK_KHR_SWAPCHAIN_EXTENSION_NAME - }; - - // Initialize Vulkan - void initVulkan() { - createInstance(); - createSurface(); - pickPhysicalDevice(); - checkFeatureSupport(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createRenderPass(); - createDescriptorSetLayout(); - createGraphicsPipeline(); - createFramebuffers(); - createCommandPool(); - createTextureImage(); - createTextureImageView(); - createTextureSampler(); - loadModel(); - createVertexBuffer(); - createIndexBuffer(); - createUniformBuffers(); - createDescriptorPool(); - createDescriptorSets(); - createCommandBuffers(); - createSyncObjects(); - - initialized = true; - } - - // Create Vulkan instance - void createInstance() { - // Application info - vk::ApplicationInfo appInfo{ - .pApplicationName = "Vulkan Android", - .applicationVersion = VK_MAKE_VERSION(1, 0, 0), - .pEngineName = "No Engine", - .engineVersion = VK_MAKE_VERSION(1, 0, 0), - .apiVersion = VK_API_VERSION_1_3 - }; - - // Get required extensions - std::vector extensions = getRequiredExtensions(); - - // Create instance - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledExtensionCount = static_cast(extensions.size()), - .ppEnabledExtensionNames = extensions.data() - }; - - instance = vk::raii::Instance(context, createInfo); - LOGI("Vulkan instance created"); - } - - // Create platform-specific surface - void createSurface() { - VkSurfaceKHR _surface; + bool initialized = false; + bool framebufferResized = false; + + // Vulkan objects + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::Format swapChainImageFormat = {}; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + vk::raii::RenderPass renderPass = nullptr; + vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + std::vector swapChainFramebuffers; + vk::raii::CommandPool commandPool = nullptr; + std::vector commandBuffers; + vk::raii::Buffer vertexBuffer = nullptr; + vk::raii::DeviceMemory vertexBufferMemory = nullptr; + vk::raii::Buffer indexBuffer = nullptr; + vk::raii::DeviceMemory indexBufferMemory = nullptr; + vk::raii::Image textureImage = nullptr; + vk::raii::DeviceMemory textureImageMemory = nullptr; + vk::raii::ImageView textureImageView = nullptr; + vk::raii::Sampler textureSampler = nullptr; + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + vk::raii::DescriptorPool descriptorPool = nullptr; + std::vector descriptorSets; + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + std::vector inFlightFences; + uint32_t currentFrame = 0; + + // Application info + AppInfo appInfo; + + // Model data + std::vector vertices; + std::vector indices; + + // Swap chain support details + struct SwapChainSupportDetails + { + vk::SurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; + }; + + // Required device extensions + const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME}; + + // Initialize Vulkan + void initVulkan() + { + createInstance(); + createSurface(); + pickPhysicalDevice(); + checkFeatureSupport(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createDescriptorSetLayout(); + createGraphicsPipeline(); + createFramebuffers(); + createCommandPool(); + createTextureImage(); + createTextureImageView(); + createTextureSampler(); + loadModel(); + createVertexBuffer(); + createIndexBuffer(); + createUniformBuffers(); + createDescriptorPool(); + createDescriptorSets(); + createCommandBuffers(); + createSyncObjects(); + + initialized = true; + } + + // Create Vulkan instance + void createInstance() + { + // Application info + vk::ApplicationInfo appInfo{ + .pApplicationName = "Vulkan Android", + .applicationVersion = VK_MAKE_VERSION(1, 0, 0), + .pEngineName = "No Engine", + .engineVersion = VK_MAKE_VERSION(1, 0, 0), + .apiVersion = VK_API_VERSION_1_3}; + + // Get required extensions + std::vector extensions = getRequiredExtensions(); + + // Create instance + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledExtensionCount = static_cast(extensions.size()), + .ppEnabledExtensionNames = extensions.data()}; + + instance = vk::raii::Instance(context, createInfo); + LOGI("Vulkan instance created"); + } + + // Create platform-specific surface + void createSurface() + { + VkSurfaceKHR _surface; #if PLATFORM_ANDROID - // Create Android surface - VkAndroidSurfaceCreateInfoKHR createInfo = { - .sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR, - .pNext = nullptr, - .flags = 0, - .window = androidApp->window - }; - - VkResult result = vkCreateAndroidSurfaceKHR( - *instance, - &createInfo, - nullptr, - &_surface - ); - - if (result != VK_SUCCESS) { - throw std::runtime_error("Failed to create Android surface"); - } - - LOG_INFO("Android surface created"); + // Create Android surface + VkAndroidSurfaceCreateInfoKHR createInfo = { + .sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR, + .pNext = nullptr, + .flags = 0, + .window = androidApp->window}; + + VkResult result = vkCreateAndroidSurfaceKHR( + *instance, + &createInfo, + nullptr, + &_surface); + + if (result != VK_SUCCESS) + { + throw std::runtime_error("Failed to create Android surface"); + } + + LOG_INFO("Android surface created"); #else - // Create desktop surface using GLFW - if (glfwCreateWindowSurface(*instance, window, nullptr, &_surface) != 0) { - throw std::runtime_error("Failed to create window surface"); - } + // Create desktop surface using GLFW + if (glfwCreateWindowSurface(*instance, window, nullptr, &_surface) != 0) + { + throw std::runtime_error("Failed to create window surface"); + } - LOG_INFO("Desktop surface created"); + LOG_INFO("Desktop surface created"); #endif - surface = vk::raii::SurfaceKHR(instance, _surface); - } + surface = vk::raii::SurfaceKHR(instance, _surface); + } - // Pick physical device - void pickPhysicalDevice() { - std::vector devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( + // Pick physical device + void pickPhysicalDevice() + { + std::vector devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( devices, - [&](auto const& device) { + [&](auto const &device) { // Check if any of the queue families support graphics operations auto queueFamilies = device.getQueueFamilyProperties(); bool supportsGraphics = - std::ranges::any_of(queueFamilies, [](auto const& qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); // Check if all required device extensions are available auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); bool supportsAllRequiredExtensions = std::ranges::all_of(deviceExtensions, - [&availableDeviceExtensions](auto const& requiredDeviceExtension) { - return std::ranges::any_of(availableDeviceExtensions, - [requiredDeviceExtension](auto const& availableDeviceExtension) { - return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; - }); - }); + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { + return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; + }); + }); return supportsGraphics && supportsAllRequiredExtensions; }); - if (devIter != devices.end()) { - physicalDevice = *devIter; - - // Print device information - vk::PhysicalDeviceProperties deviceProperties = physicalDevice.getProperties(); - LOGI("Selected GPU: %s", deviceProperties.deviceName.data()); - } else { - throw std::runtime_error("Failed to find a suitable GPU"); - } - } - - // Check feature support - void checkFeatureSupport() { - // Define the KHR roadmap 2022 profile - appInfo.profile = { - VP_KHR_ROADMAP_2022_NAME, - VP_KHR_ROADMAP_2022_SPEC_VERSION - }; - - // Check if the profile is supported - VkBool32 supported = VK_FALSE; + if (devIter != devices.end()) + { + physicalDevice = *devIter; + + // Print device information + vk::PhysicalDeviceProperties deviceProperties = physicalDevice.getProperties(); + LOGI("Selected GPU: %s", deviceProperties.deviceName.data()); + } + else + { + throw std::runtime_error("Failed to find a suitable GPU"); + } + } + + // Check feature support + void checkFeatureSupport() + { + // Define the KHR roadmap 2022 profile + appInfo.profile = { + VP_KHR_ROADMAP_2022_NAME, + VP_KHR_ROADMAP_2022_SPEC_VERSION}; + + // Check if the profile is supported + VkBool32 supported = VK_FALSE; #ifdef PLATFORM_ANDROID - // Create a vp::ProfileDesc from our VpProfileProperties - vp::ProfileDesc profileDesc = { - appInfo.profile.name, - appInfo.profile.specVersion - }; - - // Use vp::GetProfileSupport instead of vpGetPhysicalDeviceProfileSupport - bool result = vp::GetProfileSupport( - *physicalDevice, // Pass the physical device directly - &profileDesc, // Pass the profile description - &supported // Output parameter for support status - ); + // Create a vp::ProfileDesc from our VpProfileProperties + vp::ProfileDesc profileDesc = { + appInfo.profile.name, + appInfo.profile.specVersion}; + + // Use vp::GetProfileSupport instead of vpGetPhysicalDeviceProfileSupport + bool result = vp::GetProfileSupport( + *physicalDevice, // Pass the physical device directly + &profileDesc, // Pass the profile description + &supported // Output parameter for support status + ); #else - VkResult vk_result = vpGetPhysicalDeviceProfileSupport( - *instance, - *physicalDevice, - &appInfo.profile, - &supported - ); - bool result = vk_result == VK_SUCCESS; + VkResult vk_result = vpGetPhysicalDeviceProfileSupport( + *instance, + *physicalDevice, + &appInfo.profile, + &supported); + bool result = vk_result == VK_SUCCESS; #endif - if (result && supported == VK_TRUE) { - appInfo.profileSupported = true; - LOGI("Using KHR roadmap 2022 profile"); - } else { - appInfo.profileSupported = false; - LOGI("Falling back to traditional rendering (profile not supported)"); - } - } - - // Create logical device - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - float queuePriority = 1.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - - if (appInfo.profileSupported) { - // Enable required features - vk::PhysicalDeviceFeatures2 features2; - vk::PhysicalDeviceFeatures deviceFeatures{}; - deviceFeatures.samplerAnisotropy = VK_TRUE; - deviceFeatures.sampleRateShading = VK_TRUE; - features2.features = deviceFeatures; - - // Enable dynamic rendering - vk::PhysicalDeviceDynamicRenderingFeatures dynamicRenderingFeatures; - dynamicRenderingFeatures.dynamicRendering = VK_TRUE; - features2.pNext = &dynamicRenderingFeatures; - - // Create a vk::DeviceCreateInfo with the required features - vk::DeviceCreateInfo vkDeviceCreateInfo{ - .pNext = &features2, - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(deviceExtensions.size()), - .ppEnabledExtensionNames = deviceExtensions.data() - }; - - // Create the device with the vk::DeviceCreateInfo - device = vk::raii::Device(physicalDevice, vkDeviceCreateInfo); - } else { - // Fallback to manual device creation - vk::PhysicalDeviceFeatures deviceFeatures{}; - deviceFeatures.samplerAnisotropy = VK_TRUE; - deviceFeatures.sampleRateShading = VK_TRUE; - - vk::DeviceCreateInfo createInfo{ - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(deviceExtensions.size()), - .ppEnabledExtensionNames = deviceExtensions.data(), - .pEnabledFeatures = &deviceFeatures - }; - - device = vk::raii::Device(physicalDevice, createInfo); - } - - queue = device.getQueue(queueIndex, 0); - } - - // Create swap chain - void createSwapChain() { - SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); - - vk::SurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); - vk::PresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); - vk::Extent2D extent = chooseSwapExtent(swapChainSupport.capabilities); - - uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; - if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { - imageCount = swapChainSupport.capabilities.maxImageCount; - } - - vk::SwapchainCreateInfoKHR createInfo{ - .surface = *surface, - .minImageCount = imageCount, - .imageFormat = surfaceFormat.format, - .imageColorSpace = surfaceFormat.colorSpace, - .imageExtent = extent, - .imageArrayLayers = 1, - .imageUsage = vk::ImageUsageFlagBits::eColorAttachment, - .imageSharingMode = vk::SharingMode::eExclusive - }; - - createInfo.preTransform = swapChainSupport.capabilities.currentTransform; - createInfo.compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque; - createInfo.presentMode = presentMode; - createInfo.clipped = VK_TRUE; - - swapChain = device.createSwapchainKHR(createInfo); - swapChainImages = swapChain.getImages(); - swapChainImageFormat = surfaceFormat.format; - swapChainExtent = extent; - } - - // Create image views - void createImageViews() { - swapChainImageViews.reserve(swapChainImages.size()); - - for (const auto& image : swapChainImages) { - vk::ImageViewCreateInfo createInfo{ - .image = image, - .viewType = vk::ImageViewType::e2D, - .format = swapChainImageFormat, - .components = { - .r = vk::ComponentSwizzle::eIdentity, - .g = vk::ComponentSwizzle::eIdentity, - .b = vk::ComponentSwizzle::eIdentity, - .a = vk::ComponentSwizzle::eIdentity - }, - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - - swapChainImageViews.push_back(device.createImageView(createInfo)); - } - } - - // Create render pass - void createRenderPass() { - vk::AttachmentDescription colorAttachment{ - .format = swapChainImageFormat, - .samples = vk::SampleCountFlagBits::e1, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .stencilLoadOp = vk::AttachmentLoadOp::eDontCare, - .stencilStoreOp = vk::AttachmentStoreOp::eDontCare, - .initialLayout = vk::ImageLayout::eUndefined, - .finalLayout = vk::ImageLayout::ePresentSrcKHR - }; - - vk::AttachmentReference colorAttachmentRef{ - .attachment = 0, - .layout = vk::ImageLayout::eColorAttachmentOptimal - }; - - vk::SubpassDescription subpass{ - .pipelineBindPoint = vk::PipelineBindPoint::eGraphics, - .colorAttachmentCount = 1, - .pColorAttachments = &colorAttachmentRef - }; - - vk::SubpassDependency dependency{ - .srcSubpass = VK_SUBPASS_EXTERNAL, - .dstSubpass = 0, - .srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput, - .dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput, - .srcAccessMask = vk::AccessFlagBits::eNone, - .dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite - }; - - vk::RenderPassCreateInfo renderPassInfo{ - .attachmentCount = 1, - .pAttachments = &colorAttachment, - .subpassCount = 1, - .pSubpasses = &subpass, - .dependencyCount = 1, - .pDependencies = &dependency - }; - - renderPass = device.createRenderPass(renderPassInfo); - } - - // Create descriptor set layout - void createDescriptorSetLayout() { - vk::DescriptorSetLayoutBinding uboLayoutBinding{ - .binding = 0, - .descriptorType = vk::DescriptorType::eUniformBuffer, - .descriptorCount = 1, - .stageFlags = vk::ShaderStageFlagBits::eVertex - }; - - vk::DescriptorSetLayoutBinding samplerLayoutBinding{ - .binding = 1, - .descriptorType = vk::DescriptorType::eCombinedImageSampler, - .descriptorCount = 1, - .stageFlags = vk::ShaderStageFlagBits::eFragment - }; - - std::array bindings = {uboLayoutBinding, samplerLayoutBinding}; - - vk::DescriptorSetLayoutCreateInfo layoutInfo{ - .bindingCount = static_cast(bindings.size()), - .pBindings = bindings.data() - }; - - descriptorSetLayout = device.createDescriptorSetLayout(layoutInfo); - } - - // Create graphics pipeline - void createGraphicsPipeline() { - // Load shader code from asset files - LOGI("Loading shaders from assets"); - - // Load shader files using cross-platform function + if (result && supported == VK_TRUE) + { + appInfo.profileSupported = true; + LOGI("Using KHR roadmap 2022 profile"); + } + else + { + appInfo.profileSupported = false; + LOGI("Falling back to traditional rendering (profile not supported)"); + } + } + + // Create logical device + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + float queuePriority = 1.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + + if (appInfo.profileSupported) + { + // Enable required features + vk::PhysicalDeviceFeatures2 features2; + vk::PhysicalDeviceFeatures deviceFeatures{}; + deviceFeatures.samplerAnisotropy = VK_TRUE; + deviceFeatures.sampleRateShading = VK_TRUE; + features2.features = deviceFeatures; + + // Enable dynamic rendering + vk::PhysicalDeviceDynamicRenderingFeatures dynamicRenderingFeatures; + dynamicRenderingFeatures.dynamicRendering = VK_TRUE; + features2.pNext = &dynamicRenderingFeatures; + + // Create a vk::DeviceCreateInfo with the required features + vk::DeviceCreateInfo vkDeviceCreateInfo{ + .pNext = &features2, + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(deviceExtensions.size()), + .ppEnabledExtensionNames = deviceExtensions.data()}; + + // Create the device with the vk::DeviceCreateInfo + device = vk::raii::Device(physicalDevice, vkDeviceCreateInfo); + } + else + { + // Fallback to manual device creation + vk::PhysicalDeviceFeatures deviceFeatures{}; + deviceFeatures.samplerAnisotropy = VK_TRUE; + deviceFeatures.sampleRateShading = VK_TRUE; + + vk::DeviceCreateInfo createInfo{ + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(deviceExtensions.size()), + .ppEnabledExtensionNames = deviceExtensions.data(), + .pEnabledFeatures = &deviceFeatures}; + + device = vk::raii::Device(physicalDevice, createInfo); + } + + queue = device.getQueue(queueIndex, 0); + } + + // Create swap chain + void createSwapChain() + { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + vk::SurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + vk::PresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + vk::Extent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) + { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + vk::SwapchainCreateInfoKHR createInfo{ + .surface = *surface, + .minImageCount = imageCount, + .imageFormat = surfaceFormat.format, + .imageColorSpace = surfaceFormat.colorSpace, + .imageExtent = extent, + .imageArrayLayers = 1, + .imageUsage = vk::ImageUsageFlagBits::eColorAttachment, + .imageSharingMode = vk::SharingMode::eExclusive}; + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + + swapChain = device.createSwapchainKHR(createInfo); + swapChainImages = swapChain.getImages(); + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + // Create image views + void createImageViews() + { + swapChainImageViews.reserve(swapChainImages.size()); + + for (const auto &image : swapChainImages) + { + vk::ImageViewCreateInfo createInfo{ + .image = image, + .viewType = vk::ImageViewType::e2D, + .format = swapChainImageFormat, + .components = { + .r = vk::ComponentSwizzle::eIdentity, + .g = vk::ComponentSwizzle::eIdentity, + .b = vk::ComponentSwizzle::eIdentity, + .a = vk::ComponentSwizzle::eIdentity}, + .subresourceRange = {.aspectMask = vk::ImageAspectFlagBits::eColor, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1}}; + + swapChainImageViews.push_back(device.createImageView(createInfo)); + } + } + + // Create render pass + void createRenderPass() + { + vk::AttachmentDescription colorAttachment{ + .format = swapChainImageFormat, + .samples = vk::SampleCountFlagBits::e1, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .stencilLoadOp = vk::AttachmentLoadOp::eDontCare, + .stencilStoreOp = vk::AttachmentStoreOp::eDontCare, + .initialLayout = vk::ImageLayout::eUndefined, + .finalLayout = vk::ImageLayout::ePresentSrcKHR}; + + vk::AttachmentReference colorAttachmentRef{ + .attachment = 0, + .layout = vk::ImageLayout::eColorAttachmentOptimal}; + + vk::SubpassDescription subpass{ + .pipelineBindPoint = vk::PipelineBindPoint::eGraphics, + .colorAttachmentCount = 1, + .pColorAttachments = &colorAttachmentRef}; + + vk::SubpassDependency dependency{ + .srcSubpass = VK_SUBPASS_EXTERNAL, + .dstSubpass = 0, + .srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput, + .dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput, + .srcAccessMask = vk::AccessFlagBits::eNone, + .dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite}; + + vk::RenderPassCreateInfo renderPassInfo{ + .attachmentCount = 1, + .pAttachments = &colorAttachment, + .subpassCount = 1, + .pSubpasses = &subpass, + .dependencyCount = 1, + .pDependencies = &dependency}; + + renderPass = device.createRenderPass(renderPassInfo); + } + + // Create descriptor set layout + void createDescriptorSetLayout() + { + vk::DescriptorSetLayoutBinding uboLayoutBinding{ + .binding = 0, + .descriptorType = vk::DescriptorType::eUniformBuffer, + .descriptorCount = 1, + .stageFlags = vk::ShaderStageFlagBits::eVertex}; + + vk::DescriptorSetLayoutBinding samplerLayoutBinding{ + .binding = 1, + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .descriptorCount = 1, + .stageFlags = vk::ShaderStageFlagBits::eFragment}; + + std::array bindings = {uboLayoutBinding, samplerLayoutBinding}; + + vk::DescriptorSetLayoutCreateInfo layoutInfo{ + .bindingCount = static_cast(bindings.size()), + .pBindings = bindings.data()}; + + descriptorSetLayout = device.createDescriptorSetLayout(layoutInfo); + } + + // Create graphics pipeline + void createGraphicsPipeline() + { + // Load shader code from asset files + LOGI("Loading shaders from assets"); + + // Load shader files using cross-platform function #if PLATFORM_ANDROID - std::optional optionalAssetManager = assetManager; + std::optional optionalAssetManager = assetManager; #else - std::optional optionalAssetManager = std::nullopt; + std::optional optionalAssetManager = std::nullopt; #endif - std::vector vertShaderCode = readFile("shaders/vert.spv", optionalAssetManager); - std::vector fragShaderCode = readFile("shaders/frag.spv", optionalAssetManager); - - LOGI("Shaders loaded successfully"); - - // Create shader modules - vk::ShaderModuleCreateInfo vertShaderModuleInfo{ - .codeSize = vertShaderCode.size(), - .pCode = reinterpret_cast(vertShaderCode.data()) - }; - vk::raii::ShaderModule vertShaderModule = device.createShaderModule(vertShaderModuleInfo); - - vk::ShaderModuleCreateInfo fragShaderModuleInfo{ - .codeSize = fragShaderCode.size(), - .pCode = reinterpret_cast(fragShaderCode.data()) - }; - vk::raii::ShaderModule fragShaderModule = device.createShaderModule(fragShaderModuleInfo); - - // Create shader stages - vk::PipelineShaderStageCreateInfo shaderStages[] = { - { - .stage = vk::ShaderStageFlagBits::eVertex, - .module = *vertShaderModule, - .pName = "main" - }, - { - .stage = vk::ShaderStageFlagBits::eFragment, - .module = *fragShaderModule, - .pName = "main" - } - }; - - // Vertex input - auto bindingDescription = Vertex::getBindingDescription(); - auto attributeDescriptions = Vertex::getAttributeDescriptions(); - - vk::PipelineVertexInputStateCreateInfo vertexInputInfo{ - .vertexBindingDescriptionCount = 1, - .pVertexBindingDescriptions = &bindingDescription, - .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), - .pVertexAttributeDescriptions = attributeDescriptions.data() - }; - - // Input assembly - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ - .topology = vk::PrimitiveTopology::eTriangleList, - .primitiveRestartEnable = VK_FALSE - }; - - // Viewport and scissor - vk::PipelineViewportStateCreateInfo viewportState{ - .viewportCount = 1, - .scissorCount = 1 - }; - - // Rasterization - vk::PipelineRasterizationStateCreateInfo rasterizer{ - .depthClampEnable = VK_FALSE, - .rasterizerDiscardEnable = VK_FALSE, - .polygonMode = vk::PolygonMode::eFill, - .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eCounterClockwise, - .depthBiasEnable = VK_FALSE, - .lineWidth = 1.0f - }; - - // Multisampling - vk::PipelineMultisampleStateCreateInfo multisampling{ - .rasterizationSamples = vk::SampleCountFlagBits::e1, - .sampleShadingEnable = VK_FALSE - }; - - // Color blending - vk::PipelineColorBlendAttachmentState colorBlendAttachment{ - .blendEnable = VK_FALSE, - .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA - }; - - vk::PipelineColorBlendStateCreateInfo colorBlending{ - .logicOpEnable = VK_FALSE, - .logicOp = vk::LogicOp::eCopy, - .attachmentCount = 1, - .pAttachments = &colorBlendAttachment - }; - - // Dynamic states - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - - vk::PipelineDynamicStateCreateInfo dynamicState{ - .dynamicStateCount = static_cast(dynamicStates.size()), - .pDynamicStates = dynamicStates.data() - }; - - // Pipeline layout - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ - .setLayoutCount = 1, - .pSetLayouts = &*descriptorSetLayout - }; - - pipelineLayout = device.createPipelineLayout(pipelineLayoutInfo); - - // Create the graphics pipeline - vk::GraphicsPipelineCreateInfo pipelineInfo{ - .stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pDepthStencilState = nullptr, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = *pipelineLayout, - .renderPass = *renderPass, - .subpass = 0 - }; - - // Create the pipeline - graphicsPipeline = device.createGraphicsPipeline(nullptr, pipelineInfo); - } - - // Create framebuffers - void createFramebuffers() { - swapChainFramebuffers.reserve(swapChainImageViews.size()); - - for (size_t i = 0; i < swapChainImageViews.size(); i++) { - vk::ImageView attachments[] = { - *swapChainImageViews[i] - }; - - vk::FramebufferCreateInfo framebufferInfo{ - .renderPass = *renderPass, - .attachmentCount = 1, - .pAttachments = attachments, - .width = swapChainExtent.width, - .height = swapChainExtent.height, - .layers = 1 - }; - - swapChainFramebuffers.push_back(device.createFramebuffer(framebufferInfo)); - } - } - - // Create command pool - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{ - .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex - }; - - commandPool = device.createCommandPool(poolInfo); - } - - // Create texture image - void createTextureImage() { - // Load texture image - int texWidth, texHeight, texChannels; - stbi_uc* pixels = nullptr; + std::vector vertShaderCode = readFile("shaders/vert.spv", optionalAssetManager); + std::vector fragShaderCode = readFile("shaders/frag.spv", optionalAssetManager); + + LOGI("Shaders loaded successfully"); + + // Create shader modules + vk::ShaderModuleCreateInfo vertShaderModuleInfo{ + .codeSize = vertShaderCode.size(), + .pCode = reinterpret_cast(vertShaderCode.data())}; + vk::raii::ShaderModule vertShaderModule = device.createShaderModule(vertShaderModuleInfo); + + vk::ShaderModuleCreateInfo fragShaderModuleInfo{ + .codeSize = fragShaderCode.size(), + .pCode = reinterpret_cast(fragShaderCode.data())}; + vk::raii::ShaderModule fragShaderModule = device.createShaderModule(fragShaderModuleInfo); + + // Create shader stages + vk::PipelineShaderStageCreateInfo shaderStages[] = { + {.stage = vk::ShaderStageFlagBits::eVertex, + .module = *vertShaderModule, + .pName = "main"}, + {.stage = vk::ShaderStageFlagBits::eFragment, + .module = *fragShaderModule, + .pName = "main"}}; + + // Vertex input + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{ + .vertexBindingDescriptionCount = 1, + .pVertexBindingDescriptions = &bindingDescription, + .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), + .pVertexAttributeDescriptions = attributeDescriptions.data()}; + + // Input assembly + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ + .topology = vk::PrimitiveTopology::eTriangleList, + .primitiveRestartEnable = VK_FALSE}; + + // Viewport and scissor + vk::PipelineViewportStateCreateInfo viewportState{ + .viewportCount = 1, + .scissorCount = 1}; + + // Rasterization + vk::PipelineRasterizationStateCreateInfo rasterizer{ + .depthClampEnable = VK_FALSE, + .rasterizerDiscardEnable = VK_FALSE, + .polygonMode = vk::PolygonMode::eFill, + .cullMode = vk::CullModeFlagBits::eBack, + .frontFace = vk::FrontFace::eCounterClockwise, + .depthBiasEnable = VK_FALSE, + .lineWidth = 1.0f}; + + // Multisampling + vk::PipelineMultisampleStateCreateInfo multisampling{ + .rasterizationSamples = vk::SampleCountFlagBits::e1, + .sampleShadingEnable = VK_FALSE}; + + // Color blending + vk::PipelineColorBlendAttachmentState colorBlendAttachment{ + .blendEnable = VK_FALSE, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA}; + + vk::PipelineColorBlendStateCreateInfo colorBlending{ + .logicOpEnable = VK_FALSE, + .logicOp = vk::LogicOp::eCopy, + .attachmentCount = 1, + .pAttachments = &colorBlendAttachment}; + + // Dynamic states + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + + vk::PipelineDynamicStateCreateInfo dynamicState{ + .dynamicStateCount = static_cast(dynamicStates.size()), + .pDynamicStates = dynamicStates.data()}; + + // Pipeline layout + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ + .setLayoutCount = 1, + .pSetLayouts = &*descriptorSetLayout}; + + pipelineLayout = device.createPipelineLayout(pipelineLayoutInfo); + + // Create the graphics pipeline + vk::GraphicsPipelineCreateInfo pipelineInfo{ + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pDepthStencilState = nullptr, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = *pipelineLayout, + .renderPass = *renderPass, + .subpass = 0}; + + // Create the pipeline + graphicsPipeline = device.createGraphicsPipeline(nullptr, pipelineInfo); + } + + // Create framebuffers + void createFramebuffers() + { + swapChainFramebuffers.reserve(swapChainImageViews.size()); + + for (size_t i = 0; i < swapChainImageViews.size(); i++) + { + vk::ImageView attachments[] = { + *swapChainImageViews[i]}; + + vk::FramebufferCreateInfo framebufferInfo{ + .renderPass = *renderPass, + .attachmentCount = 1, + .pAttachments = attachments, + .width = swapChainExtent.width, + .height = swapChainExtent.height, + .layers = 1}; + + swapChainFramebuffers.push_back(device.createFramebuffer(framebufferInfo)); + } + } + + // Create command pool + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{ + .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex}; + + commandPool = device.createCommandPool(poolInfo); + } + + // Create texture image + void createTextureImage() + { + // Load texture image + int texWidth, texHeight, texChannels; + stbi_uc *pixels = nullptr; #if PLATFORM_ANDROID - // Load image from Android assets - std::optional optionalAssetManager = assetManager; - std::vector imageData = readFile(TEXTURE_PATH, optionalAssetManager); - pixels = stbi_load_from_memory( - reinterpret_cast(imageData.data()), + // Load image from Android assets + std::optional optionalAssetManager = assetManager; + std::vector imageData = readFile(TEXTURE_PATH, optionalAssetManager); + pixels = stbi_load_from_memory( + reinterpret_cast(imageData.data()), static_cast(imageData.size()), - &texWidth, &texHeight, &texChannels, STBI_rgb_alpha - ); + &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); #else - // Load image from filesystem - pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + // Load image from filesystem + pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); #endif - if (!pixels) { - throw std::runtime_error("Failed to load texture image: " + TEXTURE_PATH); - } - - LOG_INFO("Texture loaded successfully"); - - vk::DeviceSize imageSize = texWidth * texHeight * 4; - - // Create staging buffer - vk::raii::Buffer stagingBuffer = nullptr; - vk::raii::DeviceMemory stagingBufferMemory = nullptr; - - createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - // Copy pixel data to staging buffer - void* data; - data = stagingBufferMemory.mapMemory(0, imageSize); - memcpy(data, pixels, static_cast(imageSize)); - stagingBufferMemory.unmapMemory(); - - // Free the pixel data - if (pixels != nullptr) { - stbi_image_free(pixels); - } - - // Create image - vk::ImageCreateInfo imageInfo{ - .imageType = vk::ImageType::e2D, - .format = vk::Format::eR8G8B8A8Srgb, - .extent = { - .width = static_cast(texWidth), - .height = static_cast(texHeight), - .depth = 1 - }, - .mipLevels = 1, - .arrayLayers = 1, - .samples = vk::SampleCountFlagBits::e1, - .tiling = vk::ImageTiling::eOptimal, - .usage = vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, - .sharingMode = vk::SharingMode::eExclusive, - .initialLayout = vk::ImageLayout::eUndefined - }; - - textureImage = device.createImage(imageInfo); - - // Allocate memory for the image - vk::MemoryRequirements memRequirements = textureImage.getMemoryRequirements(); - - vk::MemoryAllocateInfo allocInfo{ - .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, vk::MemoryPropertyFlagBits::eDeviceLocal) - }; - - textureImageMemory = device.allocateMemory(allocInfo); - textureImage.bindMemory(*textureImageMemory, 0); - - // Transition image layout and copy buffer to image - transitionImageLayout(textureImage, vk::Format::eR8G8B8A8Srgb, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal); - copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); - transitionImageLayout(textureImage, vk::Format::eR8G8B8A8Srgb, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal); - } - - // Create texture image view - void createTextureImageView() { - textureImageView = createImageView(textureImage, vk::Format::eR8G8B8A8Srgb); - } - - // Create texture sampler - void createTextureSampler() { - vk::SamplerCreateInfo samplerInfo{ - .magFilter = vk::Filter::eLinear, - .minFilter = vk::Filter::eLinear, - .mipmapMode = vk::SamplerMipmapMode::eLinear, - .addressModeU = vk::SamplerAddressMode::eRepeat, - .addressModeV = vk::SamplerAddressMode::eRepeat, - .addressModeW = vk::SamplerAddressMode::eRepeat, - .anisotropyEnable = VK_TRUE, - .maxAnisotropy = 16.0f, - .compareEnable = VK_FALSE, - .compareOp = vk::CompareOp::eAlways, - .borderColor = vk::BorderColor::eIntOpaqueBlack, - .unnormalizedCoordinates = VK_FALSE - }; - - textureSampler = device.createSampler(samplerInfo); - } - - // Load model - void loadModel() { - tinyobj::attrib_t attrib; - std::vector shapes; - std::vector materials; - std::string warn, err; + if (!pixels) + { + throw std::runtime_error("Failed to load texture image: " + TEXTURE_PATH); + } + + LOG_INFO("Texture loaded successfully"); + + vk::DeviceSize imageSize = texWidth * texHeight * 4; + + // Create staging buffer + vk::raii::Buffer stagingBuffer = nullptr; + vk::raii::DeviceMemory stagingBufferMemory = nullptr; + + createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + // Copy pixel data to staging buffer + void *data; + data = stagingBufferMemory.mapMemory(0, imageSize); + memcpy(data, pixels, static_cast(imageSize)); + stagingBufferMemory.unmapMemory(); + + // Free the pixel data + if (pixels != nullptr) + { + stbi_image_free(pixels); + } + + // Create image + vk::ImageCreateInfo imageInfo{ + .imageType = vk::ImageType::e2D, + .format = vk::Format::eR8G8B8A8Srgb, + .extent = { + .width = static_cast(texWidth), + .height = static_cast(texHeight), + .depth = 1}, + .mipLevels = 1, + .arrayLayers = 1, + .samples = vk::SampleCountFlagBits::e1, + .tiling = vk::ImageTiling::eOptimal, + .usage = vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, + .sharingMode = vk::SharingMode::eExclusive, + .initialLayout = vk::ImageLayout::eUndefined}; + + textureImage = device.createImage(imageInfo); + + // Allocate memory for the image + vk::MemoryRequirements memRequirements = textureImage.getMemoryRequirements(); + + vk::MemoryAllocateInfo allocInfo{ + .allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, vk::MemoryPropertyFlagBits::eDeviceLocal)}; + + textureImageMemory = device.allocateMemory(allocInfo); + textureImage.bindMemory(*textureImageMemory, 0); + + // Transition image layout and copy buffer to image + transitionImageLayout(textureImage, vk::Format::eR8G8B8A8Srgb, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); + transitionImageLayout(textureImage, vk::Format::eR8G8B8A8Srgb, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal); + } + + // Create texture image view + void createTextureImageView() + { + textureImageView = createImageView(textureImage, vk::Format::eR8G8B8A8Srgb); + } + + // Create texture sampler + void createTextureSampler() + { + vk::SamplerCreateInfo samplerInfo{ + .magFilter = vk::Filter::eLinear, + .minFilter = vk::Filter::eLinear, + .mipmapMode = vk::SamplerMipmapMode::eLinear, + .addressModeU = vk::SamplerAddressMode::eRepeat, + .addressModeV = vk::SamplerAddressMode::eRepeat, + .addressModeW = vk::SamplerAddressMode::eRepeat, + .anisotropyEnable = VK_TRUE, + .maxAnisotropy = 16.0f, + .compareEnable = VK_FALSE, + .compareOp = vk::CompareOp::eAlways, + .borderColor = vk::BorderColor::eIntOpaqueBlack, + .unnormalizedCoordinates = VK_FALSE}; + + textureSampler = device.createSampler(samplerInfo); + } + + // Load model + void loadModel() + { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn, err; #if PLATFORM_ANDROID - // Load OBJ file from Android assets - std::optional optionalAssetManager = assetManager; - std::vector objData = readFile(MODEL_PATH, optionalAssetManager); - std::string objString(objData.begin(), objData.end()); - std::istringstream objStream(objString); - - if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, &objStream)) { - throw std::runtime_error("Failed to load model: " + MODEL_PATH + " - " + warn + err); - } + // Load OBJ file from Android assets + std::optional optionalAssetManager = assetManager; + std::vector objData = readFile(MODEL_PATH, optionalAssetManager); + std::string objString(objData.begin(), objData.end()); + std::istringstream objStream(objString); + + if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, &objStream)) + { + throw std::runtime_error("Failed to load model: " + MODEL_PATH + " - " + warn + err); + } #else - // Load OBJ file from filesystem - if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, MODEL_PATH.c_str())) { - throw std::runtime_error("Failed to load model: " + MODEL_PATH + " - " + warn + err); - } + // Load OBJ file from filesystem + if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, MODEL_PATH.c_str())) + { + throw std::runtime_error("Failed to load model: " + MODEL_PATH + " - " + warn + err); + } #endif - std::unordered_map uniqueVertices{}; - - for (const auto& shape : shapes) { - for (const auto& index : shape.mesh.indices) { - Vertex vertex{}; - - vertex.pos = { - attrib.vertices[3 * index.vertex_index + 0], - attrib.vertices[3 * index.vertex_index + 1], - attrib.vertices[3 * index.vertex_index + 2] - }; - - vertex.texCoord = { - attrib.texcoords[2 * index.texcoord_index + 0], - 1.0f - attrib.texcoords[2 * index.texcoord_index + 1] - }; - - vertex.color = {1.0f, 1.0f, 1.0f}; - - if (uniqueVertices.count(vertex) == 0) { - uniqueVertices[vertex] = static_cast(vertices.size()); - vertices.push_back(vertex); - } - - indices.push_back(uniqueVertices[vertex]); - } - } - - LOG_INFO("Model loaded successfully"); - } - - // Create vertex buffer - void createVertexBuffer() { - vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); - - vk::raii::Buffer stagingBuffer = nullptr; - vk::raii::DeviceMemory stagingBufferMemory = nullptr; - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data; - data = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(data, vertices.data(), (size_t) bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); - - copyBuffer(stagingBuffer, vertexBuffer, bufferSize); - } - - // Create index buffer - void createIndexBuffer() { - vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); - - vk::raii::Buffer stagingBuffer = nullptr; - vk::raii::DeviceMemory stagingBufferMemory = nullptr; - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data; - data = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(data, indices.data(), (size_t) bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); - - copyBuffer(stagingBuffer, indexBuffer, bufferSize); - } - - // Create uniform buffers - void createUniformBuffers() { - vk::DeviceSize bufferSize = sizeof(UniformBufferObject); - - uniformBuffers.clear(); - uniformBuffersMemory.clear(); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - uniformBuffers.push_back(nullptr); - uniformBuffersMemory.push_back(nullptr); - } - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, uniformBuffers[i], uniformBuffersMemory[i]); - } - } - - // Create descriptor pool - void createDescriptorPool() { - std::array poolSizes = { - vk::DescriptorPoolSize{ - .type = vk::DescriptorType::eUniformBuffer, - .descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT) - }, - vk::DescriptorPoolSize{ - .type = vk::DescriptorType::eCombinedImageSampler, - .descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT) - } - }; - - vk::DescriptorPoolCreateInfo poolInfo{ - .maxSets = static_cast(MAX_FRAMES_IN_FLIGHT), - .poolSizeCount = static_cast(poolSizes.size()), - .pPoolSizes = poolSizes.data() - }; - - descriptorPool = device.createDescriptorPool(poolInfo); - } - - // Create descriptor sets - void createDescriptorSets() { - std::vector layouts(MAX_FRAMES_IN_FLIGHT, *descriptorSetLayout); - vk::DescriptorSetAllocateInfo allocInfo{ - .descriptorPool = *descriptorPool, - .descriptorSetCount = static_cast(MAX_FRAMES_IN_FLIGHT), - .pSetLayouts = layouts.data() - }; - - descriptorSets = device.allocateDescriptorSets(allocInfo); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DescriptorBufferInfo bufferInfo{ - .buffer = *uniformBuffers[i], - .offset = 0, - .range = sizeof(UniformBufferObject) - }; - - vk::DescriptorImageInfo imageInfo{ - .sampler = *textureSampler, - .imageView = *textureImageView, - .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal - }; - - std::array descriptorWrites = { - vk::WriteDescriptorSet{ - .dstSet = *descriptorSets[i], - .dstBinding = 0, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eUniformBuffer, - .pBufferInfo = &bufferInfo - }, - vk::WriteDescriptorSet{ - .dstSet = *descriptorSets[i], - .dstBinding = 1, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eCombinedImageSampler, - .pImageInfo = &imageInfo - } - }; - - device.updateDescriptorSets(descriptorWrites, nullptr); - } - } - - // Create command buffers - void createCommandBuffers() { - commandBuffers.reserve(MAX_FRAMES_IN_FLIGHT); - - vk::CommandBufferAllocateInfo allocInfo{ - .commandPool = *commandPool, - .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = static_cast(MAX_FRAMES_IN_FLIGHT) - }; - - commandBuffers = device.allocateCommandBuffers(allocInfo); - } - - // Create synchronization objects - void createSyncObjects() { - imageAvailableSemaphores.reserve(MAX_FRAMES_IN_FLIGHT); - renderFinishedSemaphores.reserve(MAX_FRAMES_IN_FLIGHT); - inFlightFences.reserve(MAX_FRAMES_IN_FLIGHT); - - vk::SemaphoreCreateInfo semaphoreInfo{}; - vk::FenceCreateInfo fenceInfo{ - .flags = vk::FenceCreateFlagBits::eSignaled - }; - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - imageAvailableSemaphores.push_back(device.createSemaphore(semaphoreInfo)); - renderFinishedSemaphores.push_back(device.createSemaphore(semaphoreInfo)); - inFlightFences.push_back(device.createFence(fenceInfo)); - } - } - - // Clean up swap chain - void cleanupSwapChain() { - for (auto& framebuffer : swapChainFramebuffers) { - framebuffer = nullptr; - } - - for (auto& imageView : swapChainImageViews) { - imageView = nullptr; - } - - swapChain = nullptr; - } - - // Record command buffer - void recordCommandBuffer(vk::raii::CommandBuffer& commandBuffer, uint32_t imageIndex) { - vk::CommandBufferBeginInfo beginInfo{}; - commandBuffer.begin(beginInfo); - - vk::RenderPassBeginInfo renderPassInfo{ - .renderPass = *renderPass, - .framebuffer = *swapChainFramebuffers[imageIndex], - .renderArea = { - .offset = {0, 0}, - .extent = swapChainExtent - } - }; - - vk::ClearValue clearColor; - clearColor.color.float32 = std::array{0.0f, 0.0f, 0.0f, 1.0f}; - renderPassInfo.clearValueCount = 1; - renderPassInfo.pClearValues = &clearColor; - - commandBuffer.beginRenderPass(renderPassInfo, vk::SubpassContents::eInline); - commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - - vk::Viewport viewport{ - .x = 0.0f, - .y = 0.0f, - .width = static_cast(swapChainExtent.width), - .height = static_cast(swapChainExtent.height), - .minDepth = 0.0f, - .maxDepth = 1.0f - }; - commandBuffer.setViewport(0, viewport); - - vk::Rect2D scissor{ - .offset = {0, 0}, - .extent = swapChainExtent - }; - commandBuffer.setScissor(0, scissor); - - commandBuffer.bindVertexBuffers(0, {*vertexBuffer}, {0}); - commandBuffer.bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint32); - commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, *pipelineLayout, 0, {*descriptorSets[currentFrame]}, nullptr); - commandBuffer.drawIndexed(static_cast(indices.size()), 1, 0, 0, 0); - - commandBuffer.endRenderPass(); - commandBuffer.end(); - } - - // Draw frame - void drawFrame() { - static_cast(device.waitForFences({*inFlightFences[currentFrame]}, VK_TRUE, FenceTimeout)); - - uint32_t imageIndex; - try { - auto [result, idx] = swapChain.acquireNextImage(FenceTimeout, *imageAvailableSemaphores[currentFrame]); - imageIndex = idx; - } catch (vk::OutOfDateKHRError&) { - recreateSwapChain(); - return; - } - - // Update uniform buffer with current transformation - updateUniformBuffer(currentFrame); - - device.resetFences({*inFlightFences[currentFrame]}); - - commandBuffers[currentFrame].reset(); - recordCommandBuffer(commandBuffers[currentFrame], imageIndex); - - vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); - const vk::SubmitInfo submitInfo{ - .waitSemaphoreCount = 1, - .pWaitSemaphores = &*imageAvailableSemaphores[currentFrame], - .pWaitDstStageMask = &waitDestinationStageMask, - .commandBufferCount = 1, - .pCommandBuffers = &*commandBuffers[currentFrame], - .signalSemaphoreCount = 1, - .pSignalSemaphores = &*renderFinishedSemaphores[currentFrame] - }; - queue.submit(submitInfo, *inFlightFences[currentFrame]); - - const vk::PresentInfoKHR presentInfoKHR{ - .waitSemaphoreCount = 1, - .pWaitSemaphores = &*renderFinishedSemaphores[currentFrame], - .swapchainCount = 1, - .pSwapchains = &*swapChain, - .pImageIndices = &imageIndex - }; - - vk::Result result; - try { - result = queue.presentKHR(presentInfoKHR); - } catch (vk::OutOfDateKHRError&) { - result = vk::Result::eErrorOutOfDateKHR; - } - - if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { - framebufferResized = false; - recreateSwapChain(); - } else if (result != vk::Result::eSuccess) { - throw std::runtime_error("Failed to present swap chain image"); - } - - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; - } - - // Recreate swap chain - void recreateSwapChain() { - // Wait for device to finish operations - device.waitIdle(); - - // Clean up old swap chain - cleanupSwapChain(); - - // Create new swap chain - createSwapChain(); - createImageViews(); - createFramebuffers(); - } - - // Get required extensions - std::vector getRequiredExtensions() { + std::unordered_map uniqueVertices{}; + + for (const auto &shape : shapes) + { + for (const auto &index : shape.mesh.indices) + { + Vertex vertex{}; + + vertex.pos = { + attrib.vertices[3 * index.vertex_index + 0], + attrib.vertices[3 * index.vertex_index + 1], + attrib.vertices[3 * index.vertex_index + 2]}; + + vertex.texCoord = { + attrib.texcoords[2 * index.texcoord_index + 0], + 1.0f - attrib.texcoords[2 * index.texcoord_index + 1]}; + + vertex.color = {1.0f, 1.0f, 1.0f}; + + if (uniqueVertices.count(vertex) == 0) + { + uniqueVertices[vertex] = static_cast(vertices.size()); + vertices.push_back(vertex); + } + + indices.push_back(uniqueVertices[vertex]); + } + } + + LOG_INFO("Model loaded successfully"); + } + + // Create vertex buffer + void createVertexBuffer() + { + vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + + vk::raii::Buffer stagingBuffer = nullptr; + vk::raii::DeviceMemory stagingBufferMemory = nullptr; + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data; + data = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(data, vertices.data(), (size_t) bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + } + + // Create index buffer + void createIndexBuffer() + { + vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + + vk::raii::Buffer stagingBuffer = nullptr; + vk::raii::DeviceMemory stagingBufferMemory = nullptr; + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data; + data = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(data, indices.data(), (size_t) bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); + + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + } + + // Create uniform buffers + void createUniformBuffers() + { + vk::DeviceSize bufferSize = sizeof(UniformBufferObject); + + uniformBuffers.clear(); + uniformBuffersMemory.clear(); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + uniformBuffers.push_back(nullptr); + uniformBuffersMemory.push_back(nullptr); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, uniformBuffers[i], uniformBuffersMemory[i]); + } + } + + // Create descriptor pool + void createDescriptorPool() + { + std::array poolSizes = { + vk::DescriptorPoolSize{ + .type = vk::DescriptorType::eUniformBuffer, + .descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT)}, + vk::DescriptorPoolSize{ + .type = vk::DescriptorType::eCombinedImageSampler, + .descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT)}}; + + vk::DescriptorPoolCreateInfo poolInfo{ + .maxSets = static_cast(MAX_FRAMES_IN_FLIGHT), + .poolSizeCount = static_cast(poolSizes.size()), + .pPoolSizes = poolSizes.data()}; + + descriptorPool = device.createDescriptorPool(poolInfo); + } + + // Create descriptor sets + void createDescriptorSets() + { + std::vector layouts(MAX_FRAMES_IN_FLIGHT, *descriptorSetLayout); + vk::DescriptorSetAllocateInfo allocInfo{ + .descriptorPool = *descriptorPool, + .descriptorSetCount = static_cast(MAX_FRAMES_IN_FLIGHT), + .pSetLayouts = layouts.data()}; + + descriptorSets = device.allocateDescriptorSets(allocInfo); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DescriptorBufferInfo bufferInfo{ + .buffer = *uniformBuffers[i], + .offset = 0, + .range = sizeof(UniformBufferObject)}; + + vk::DescriptorImageInfo imageInfo{ + .sampler = *textureSampler, + .imageView = *textureImageView, + .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal}; + + std::array descriptorWrites = { + vk::WriteDescriptorSet{ + .dstSet = *descriptorSets[i], + .dstBinding = 0, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eUniformBuffer, + .pBufferInfo = &bufferInfo}, + vk::WriteDescriptorSet{ + .dstSet = *descriptorSets[i], + .dstBinding = 1, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .pImageInfo = &imageInfo}}; + + device.updateDescriptorSets(descriptorWrites, nullptr); + } + } + + // Create command buffers + void createCommandBuffers() + { + commandBuffers.reserve(MAX_FRAMES_IN_FLIGHT); + + vk::CommandBufferAllocateInfo allocInfo{ + .commandPool = *commandPool, + .level = vk::CommandBufferLevel::ePrimary, + .commandBufferCount = static_cast(MAX_FRAMES_IN_FLIGHT)}; + + commandBuffers = device.allocateCommandBuffers(allocInfo); + } + + // Create synchronization objects + void createSyncObjects() + { + imageAvailableSemaphores.reserve(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.reserve(MAX_FRAMES_IN_FLIGHT); + inFlightFences.reserve(MAX_FRAMES_IN_FLIGHT); + + vk::SemaphoreCreateInfo semaphoreInfo{}; + vk::FenceCreateInfo fenceInfo{ + .flags = vk::FenceCreateFlagBits::eSignaled}; + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + imageAvailableSemaphores.push_back(device.createSemaphore(semaphoreInfo)); + renderFinishedSemaphores.push_back(device.createSemaphore(semaphoreInfo)); + inFlightFences.push_back(device.createFence(fenceInfo)); + } + } + + // Clean up swap chain + void cleanupSwapChain() + { + for (auto &framebuffer : swapChainFramebuffers) + { + framebuffer = nullptr; + } + + for (auto &imageView : swapChainImageViews) + { + imageView = nullptr; + } + + swapChain = nullptr; + } + + // Record command buffer + void recordCommandBuffer(vk::raii::CommandBuffer &commandBuffer, uint32_t imageIndex) + { + vk::CommandBufferBeginInfo beginInfo{}; + commandBuffer.begin(beginInfo); + + vk::RenderPassBeginInfo renderPassInfo{ + .renderPass = *renderPass, + .framebuffer = *swapChainFramebuffers[imageIndex], + .renderArea = { + .offset = {0, 0}, + .extent = swapChainExtent}}; + + vk::ClearValue clearColor; + clearColor.color.float32 = std::array{0.0f, 0.0f, 0.0f, 1.0f}; + renderPassInfo.clearValueCount = 1; + renderPassInfo.pClearValues = &clearColor; + + commandBuffer.beginRenderPass(renderPassInfo, vk::SubpassContents::eInline); + commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + + vk::Viewport viewport{ + .x = 0.0f, + .y = 0.0f, + .width = static_cast(swapChainExtent.width), + .height = static_cast(swapChainExtent.height), + .minDepth = 0.0f, + .maxDepth = 1.0f}; + commandBuffer.setViewport(0, viewport); + + vk::Rect2D scissor{ + .offset = {0, 0}, + .extent = swapChainExtent}; + commandBuffer.setScissor(0, scissor); + + commandBuffer.bindVertexBuffers(0, {*vertexBuffer}, {0}); + commandBuffer.bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint32); + commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, *pipelineLayout, 0, {*descriptorSets[currentFrame]}, nullptr); + commandBuffer.drawIndexed(static_cast(indices.size()), 1, 0, 0, 0); + + commandBuffer.endRenderPass(); + commandBuffer.end(); + } + + // Draw frame + void drawFrame() + { + static_cast(device.waitForFences({*inFlightFences[currentFrame]}, VK_TRUE, FenceTimeout)); + + uint32_t imageIndex; + try + { + auto [result, idx] = swapChain.acquireNextImage(FenceTimeout, *imageAvailableSemaphores[currentFrame]); + imageIndex = idx; + } + catch (vk::OutOfDateKHRError &) + { + recreateSwapChain(); + return; + } + + // Update uniform buffer with current transformation + updateUniformBuffer(currentFrame); + + device.resetFences({*inFlightFences[currentFrame]}); + + commandBuffers[currentFrame].reset(); + recordCommandBuffer(commandBuffers[currentFrame], imageIndex); + + vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); + const vk::SubmitInfo submitInfo{ + .waitSemaphoreCount = 1, + .pWaitSemaphores = &*imageAvailableSemaphores[currentFrame], + .pWaitDstStageMask = &waitDestinationStageMask, + .commandBufferCount = 1, + .pCommandBuffers = &*commandBuffers[currentFrame], + .signalSemaphoreCount = 1, + .pSignalSemaphores = &*renderFinishedSemaphores[currentFrame]}; + queue.submit(submitInfo, *inFlightFences[currentFrame]); + + const vk::PresentInfoKHR presentInfoKHR{ + .waitSemaphoreCount = 1, + .pWaitSemaphores = &*renderFinishedSemaphores[currentFrame], + .swapchainCount = 1, + .pSwapchains = &*swapChain, + .pImageIndices = &imageIndex}; + + vk::Result result; + try + { + result = queue.presentKHR(presentInfoKHR); + } + catch (vk::OutOfDateKHRError &) + { + result = vk::Result::eErrorOutOfDateKHR; + } + + if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) + { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != vk::Result::eSuccess) + { + throw std::runtime_error("Failed to present swap chain image"); + } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + // Recreate swap chain + void recreateSwapChain() + { + // Wait for device to finish operations + device.waitIdle(); + + // Clean up old swap chain + cleanupSwapChain(); + + // Create new swap chain + createSwapChain(); + createImageViews(); + createFramebuffers(); + } + + // Get required extensions + std::vector getRequiredExtensions() + { #if PLATFORM_ANDROID - // Android requires these extensions - std::vector extensions = { - VK_KHR_SURFACE_EXTENSION_NAME, - VK_KHR_ANDROID_SURFACE_EXTENSION_NAME - }; + // Android requires these extensions + std::vector extensions = { + VK_KHR_SURFACE_EXTENSION_NAME, + VK_KHR_ANDROID_SURFACE_EXTENSION_NAME}; #else - // Get the required extensions from GLFW - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + // Get the required extensions from GLFW + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); #endif - // Check if the debug utils extension is available - std::vector props = context.enumerateInstanceExtensionProperties(); - bool debugUtilsAvailable = std::ranges::any_of(props, - [](vk::ExtensionProperties const & ep) { - return strcmp(ep.extensionName, vk::EXTDebugUtilsExtensionName) == 0; - }); - - // Always include the debug utils extension if available - if (debugUtilsAvailable) { - extensions.push_back(vk::EXTDebugUtilsExtensionName); + // Check if the debug utils extension is available + std::vector props = context.enumerateInstanceExtensionProperties(); + bool debugUtilsAvailable = std::ranges::any_of(props, + [](vk::ExtensionProperties const &ep) { + return strcmp(ep.extensionName, vk::EXTDebugUtilsExtensionName) == 0; + }); + + // Always include the debug utils extension if available + if (debugUtilsAvailable) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); #if PLATFORM_DESKTOP - } else { - LOG_INFO("VK_EXT_debug_utils extension not available. Validation layers may not work."); + } + else + { + LOG_INFO("VK_EXT_debug_utils extension not available. Validation layers may not work."); #endif - } - - return extensions; - } - - // Choose swap surface format - vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { - // Prefer SRGB format - for (const auto& availableFormat : availableFormats) { - if (availableFormat.format == vk::Format::eB8G8R8A8Srgb && - availableFormat.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear) { - return availableFormat; - } - } - - // If not available, just use the first format - return availableFormats[0]; - } - - // Choose swap present mode - vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - // Prefer mailbox mode for triple buffering - for (const auto& availablePresentMode : availablePresentModes) { - if (availablePresentMode == vk::PresentModeKHR::eMailbox) { - return availablePresentMode; - } - } - - // Fallback to FIFO (guaranteed to be available) - return vk::PresentModeKHR::eFifo; - } - - // Choose swap extent - vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) { - if (capabilities.currentExtent.width != std::numeric_limits::max()) { - return capabilities.currentExtent; - } else { + } + + return extensions; + } + + // Choose swap surface format + vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + // Prefer SRGB format + for (const auto &availableFormat : availableFormats) + { + if (availableFormat.format == vk::Format::eB8G8R8A8Srgb && + availableFormat.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear) + { + return availableFormat; + } + } + + // If not available, just use the first format + return availableFormats[0]; + } + + // Choose swap present mode + vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + // Prefer mailbox mode for triple buffering + for (const auto &availablePresentMode : availablePresentModes) + { + if (availablePresentMode == vk::PresentModeKHR::eMailbox) + { + return availablePresentMode; + } + } + + // Fallback to FIFO (guaranteed to be available) + return vk::PresentModeKHR::eFifo; + } + + // Choose swap extent + vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR &capabilities) + { + if (capabilities.currentExtent.width != std::numeric_limits::max()) + { + return capabilities.currentExtent; + } + else + { #if PLATFORM_ANDROID - // Get the window size from Android - int32_t width = ANativeWindow_getWidth(androidApp->window); - int32_t height = ANativeWindow_getHeight(androidApp->window); + // Get the window size from Android + int32_t width = ANativeWindow_getWidth(androidApp->window); + int32_t height = ANativeWindow_getHeight(androidApp->window); #else - // Get the window size from GLFW - int width, height; - glfwGetFramebufferSize(window, &width, &height); + // Get the window size from GLFW + int width, height; + glfwGetFramebufferSize(window, &width, &height); #endif - vk::Extent2D actualExtent = { - static_cast(width), - static_cast(height) - }; - - actualExtent.width = std::clamp(actualExtent.width, - capabilities.minImageExtent.width, - capabilities.maxImageExtent.width); - actualExtent.height = std::clamp(actualExtent.height, - capabilities.minImageExtent.height, - capabilities.maxImageExtent.height); - - return actualExtent; - } - } - - // Query swap chain support - SwapChainSupportDetails querySwapChainSupport(vk::raii::PhysicalDevice device) { - SwapChainSupportDetails details; - details.capabilities = device.getSurfaceCapabilitiesKHR(*surface); - details.formats = device.getSurfaceFormatsKHR(*surface); - details.presentModes = device.getSurfacePresentModesKHR(*surface); - return details; - } - - // Create buffer - void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer& buffer, vk::raii::DeviceMemory& bufferMemory) { - vk::BufferCreateInfo bufferInfo{ - .size = size, - .usage = usage, - .sharingMode = vk::SharingMode::eExclusive - }; - - buffer = device.createBuffer(bufferInfo); - - vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); - - vk::MemoryAllocateInfo allocInfo{ - .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) - }; - - bufferMemory = device.allocateMemory(allocInfo); - buffer.bindMemory(*bufferMemory, 0); - } - - // Copy buffer - void copyBuffer(vk::raii::Buffer& srcBuffer, vk::raii::Buffer& dstBuffer, vk::DeviceSize size) { - vk::CommandBufferAllocateInfo allocInfo{ - .commandPool = *commandPool, - .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = 1 - }; - - vk::raii::CommandBuffer commandBuffer = std::move(device.allocateCommandBuffers(allocInfo)[0]); - - vk::CommandBufferBeginInfo beginInfo{ - .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit - }; - commandBuffer.begin(beginInfo); - - vk::BufferCopy copyRegion{ - .srcOffset = 0, - .dstOffset = 0, - .size = size - }; - commandBuffer.copyBuffer(*srcBuffer, *dstBuffer, copyRegion); - - commandBuffer.end(); - - vk::SubmitInfo submitInfo{ - .commandBufferCount = 1, - .pCommandBuffers = &*commandBuffer - }; - - queue.submit(submitInfo, nullptr); - queue.waitIdle(); - } - - // Find memory type - uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) { - vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); - - for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { - if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { - return i; - } - } - - throw std::runtime_error("Failed to find suitable memory type"); - } - - // Create image view - vk::raii::ImageView createImageView(vk::raii::Image& image, vk::Format format) { - vk::ImageViewCreateInfo viewInfo{ - .image = *image, - .viewType = vk::ImageViewType::e2D, - .format = format, - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - - return device.createImageView(viewInfo); - } - - // Transition image layout - void transitionImageLayout(vk::raii::Image& image, vk::Format format, vk::ImageLayout oldLayout, vk::ImageLayout newLayout) { - vk::CommandBufferAllocateInfo allocInfo{ - .commandPool = *commandPool, - .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = 1 - }; - - vk::raii::CommandBuffer commandBuffer = std::move(device.allocateCommandBuffers(allocInfo)[0]); - - vk::CommandBufferBeginInfo beginInfo{ - .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit - }; - commandBuffer.begin(beginInfo); - - vk::ImageMemoryBarrier barrier{ - .oldLayout = oldLayout, - .newLayout = newLayout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = *image, - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - - vk::PipelineStageFlags sourceStage; - vk::PipelineStageFlags destinationStage; - - if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) { - barrier.srcAccessMask = vk::AccessFlagBits::eNone; - barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; - - sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; - destinationStage = vk::PipelineStageFlagBits::eTransfer; - } else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) { - barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; - barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - - sourceStage = vk::PipelineStageFlagBits::eTransfer; - destinationStage = vk::PipelineStageFlagBits::eFragmentShader; - } else { - throw std::invalid_argument("Unsupported layout transition"); - } - - commandBuffer.pipelineBarrier( - sourceStage, destinationStage, - vk::DependencyFlagBits::eByRegion, - nullptr, - nullptr, - barrier - ); - - commandBuffer.end(); - - vk::SubmitInfo submitInfo{ - .commandBufferCount = 1, - .pCommandBuffers = &*commandBuffer - }; - - queue.submit(submitInfo, nullptr); - queue.waitIdle(); - } - - // Copy buffer to image - void copyBufferToImage(vk::raii::Buffer& buffer, vk::raii::Image& image, uint32_t width, uint32_t height) { - vk::CommandBufferAllocateInfo allocInfo{ - .commandPool = *commandPool, - .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = 1 - }; - - vk::raii::CommandBuffer commandBuffer = std::move(device.allocateCommandBuffers(allocInfo)[0]); - - vk::CommandBufferBeginInfo beginInfo{ - .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit - }; - commandBuffer.begin(beginInfo); - - vk::BufferImageCopy region{ - .bufferOffset = 0, - .bufferRowLength = 0, - .bufferImageHeight = 0, - .imageSubresource = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .mipLevel = 0, - .baseArrayLayer = 0, - .layerCount = 1 - }, - .imageOffset = {0, 0, 0}, - .imageExtent = {width, height, 1} - }; - - commandBuffer.copyBufferToImage( - *buffer, - *image, - vk::ImageLayout::eTransferDstOptimal, - region - ); - - commandBuffer.end(); - - vk::SubmitInfo submitInfo{ - .commandBufferCount = 1, - .pCommandBuffers = &*commandBuffer - }; - - queue.submit(submitInfo, nullptr); - queue.waitIdle(); - } - - // Update uniform buffer - void updateUniformBuffer(uint32_t currentImage) { - static auto startTime = std::chrono::high_resolution_clock::now(); - - auto currentTime = std::chrono::high_resolution_clock::now(); - float time = std::chrono::duration(currentTime - startTime).count(); - - UniformBufferObject ubo{}; - ubo.model = glm::rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.proj = glm::perspective(glm::radians(45.0f), swapChainExtent.width / (float) swapChainExtent.height, 0.1f, 10.0f); - ubo.proj[1][1] *= -1; - - void* data; - data = uniformBuffersMemory[currentImage].mapMemory(0, sizeof(ubo)); - memcpy(data, &ubo, sizeof(ubo)); - uniformBuffersMemory[currentImage].unmapMemory(); - } + vk::Extent2D actualExtent = { + static_cast(width), + static_cast(height)}; + + actualExtent.width = std::clamp(actualExtent.width, + capabilities.minImageExtent.width, + capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, + capabilities.minImageExtent.height, + capabilities.maxImageExtent.height); + + return actualExtent; + } + } + + // Query swap chain support + SwapChainSupportDetails querySwapChainSupport(vk::raii::PhysicalDevice device) + { + SwapChainSupportDetails details; + details.capabilities = device.getSurfaceCapabilitiesKHR(*surface); + details.formats = device.getSurfaceFormatsKHR(*surface); + details.presentModes = device.getSurfacePresentModesKHR(*surface); + return details; + } + + // Create buffer + void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer &buffer, vk::raii::DeviceMemory &bufferMemory) + { + vk::BufferCreateInfo bufferInfo{ + .size = size, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive}; + + buffer = device.createBuffer(bufferInfo); + + vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); + + vk::MemoryAllocateInfo allocInfo{ + .allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + + bufferMemory = device.allocateMemory(allocInfo); + buffer.bindMemory(*bufferMemory, 0); + } + + // Copy buffer + void copyBuffer(vk::raii::Buffer &srcBuffer, vk::raii::Buffer &dstBuffer, vk::DeviceSize size) + { + vk::CommandBufferAllocateInfo allocInfo{ + .commandPool = *commandPool, + .level = vk::CommandBufferLevel::ePrimary, + .commandBufferCount = 1}; + + vk::raii::CommandBuffer commandBuffer = std::move(device.allocateCommandBuffers(allocInfo)[0]); + + vk::CommandBufferBeginInfo beginInfo{ + .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; + commandBuffer.begin(beginInfo); + + vk::BufferCopy copyRegion{ + .srcOffset = 0, + .dstOffset = 0, + .size = size}; + commandBuffer.copyBuffer(*srcBuffer, *dstBuffer, copyRegion); + + commandBuffer.end(); + + vk::SubmitInfo submitInfo{ + .commandBufferCount = 1, + .pCommandBuffers = &*commandBuffer}; + + queue.submit(submitInfo, nullptr); + queue.waitIdle(); + } + + // Find memory type + uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) + { + vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) + { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) + { + return i; + } + } + + throw std::runtime_error("Failed to find suitable memory type"); + } + + // Create image view + vk::raii::ImageView createImageView(vk::raii::Image &image, vk::Format format) + { + vk::ImageViewCreateInfo viewInfo{ + .image = *image, + .viewType = vk::ImageViewType::e2D, + .format = format, + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + + return device.createImageView(viewInfo); + } + + // Transition image layout + void transitionImageLayout(vk::raii::Image &image, vk::Format format, vk::ImageLayout oldLayout, vk::ImageLayout newLayout) + { + vk::CommandBufferAllocateInfo allocInfo{ + .commandPool = *commandPool, + .level = vk::CommandBufferLevel::ePrimary, + .commandBufferCount = 1}; + + vk::raii::CommandBuffer commandBuffer = std::move(device.allocateCommandBuffers(allocInfo)[0]); + + vk::CommandBufferBeginInfo beginInfo{ + .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; + commandBuffer.begin(beginInfo); + + vk::ImageMemoryBarrier barrier{ + .oldLayout = oldLayout, + .newLayout = newLayout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = *image, + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + + vk::PipelineStageFlags sourceStage; + vk::PipelineStageFlags destinationStage; + + if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) + { + barrier.srcAccessMask = vk::AccessFlagBits::eNone; + barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; + + sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; + destinationStage = vk::PipelineStageFlagBits::eTransfer; + } + else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) + { + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + + sourceStage = vk::PipelineStageFlagBits::eTransfer; + destinationStage = vk::PipelineStageFlagBits::eFragmentShader; + } + else + { + throw std::invalid_argument("Unsupported layout transition"); + } + + commandBuffer.pipelineBarrier( + sourceStage, destinationStage, + vk::DependencyFlagBits::eByRegion, + nullptr, + nullptr, + barrier); + + commandBuffer.end(); + + vk::SubmitInfo submitInfo{ + .commandBufferCount = 1, + .pCommandBuffers = &*commandBuffer}; + + queue.submit(submitInfo, nullptr); + queue.waitIdle(); + } + + // Copy buffer to image + void copyBufferToImage(vk::raii::Buffer &buffer, vk::raii::Image &image, uint32_t width, uint32_t height) + { + vk::CommandBufferAllocateInfo allocInfo{ + .commandPool = *commandPool, + .level = vk::CommandBufferLevel::ePrimary, + .commandBufferCount = 1}; + + vk::raii::CommandBuffer commandBuffer = std::move(device.allocateCommandBuffers(allocInfo)[0]); + + vk::CommandBufferBeginInfo beginInfo{ + .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; + commandBuffer.begin(beginInfo); + + vk::BufferImageCopy region{ + .bufferOffset = 0, + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .mipLevel = 0, + .baseArrayLayer = 0, + .layerCount = 1}, + .imageOffset = {0, 0, 0}, + .imageExtent = {width, height, 1}}; + + commandBuffer.copyBufferToImage( + *buffer, + *image, + vk::ImageLayout::eTransferDstOptimal, + region); + + commandBuffer.end(); + + vk::SubmitInfo submitInfo{ + .commandBufferCount = 1, + .pCommandBuffers = &*commandBuffer}; + + queue.submit(submitInfo, nullptr); + queue.waitIdle(); + } + + // Update uniform buffer + void updateUniformBuffer(uint32_t currentImage) + { + static auto startTime = std::chrono::high_resolution_clock::now(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + UniformBufferObject ubo{}; + ubo.model = glm::rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.proj = glm::perspective(glm::radians(45.0f), swapChainExtent.width / (float) swapChainExtent.height, 0.1f, 10.0f); + ubo.proj[1][1] *= -1; + + void *data; + data = uniformBuffersMemory[currentImage].mapMemory(0, sizeof(ubo)); + memcpy(data, &ubo, sizeof(ubo)); + uniformBuffersMemory[currentImage].unmapMemory(); + } #if PLATFORM_ANDROID - // Handle app commands - static void handleAppCommand(android_app* app, int32_t cmd) { - auto* vulkanApp = static_cast(app->userData); - switch (cmd) { - case APP_CMD_INIT_WINDOW: - // Window created, initialize Vulkan - if (app->window != nullptr) { - vulkanApp->initVulkan(); - } - break; - case APP_CMD_TERM_WINDOW: - // Window destroyed, clean up Vulkan - vulkanApp->cleanup(); - break; - default: - break; - } - } - - // Handle input events - static int32_t handleInputEvent(android_app* app, AInputEvent* event) { - auto* vulkanApp = static_cast(app->userData); - if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) { - // Handle touch events - float x = AMotionEvent_getX(event, 0); - float y = AMotionEvent_getY(event, 0); - - // Process touch coordinates - LOGI("Touch at: %f, %f", x, y); - - return 1; - } - return 0; - } + // Handle app commands + static void handleAppCommand(android_app *app, int32_t cmd) + { + auto *vulkanApp = static_cast(app->userData); + switch (cmd) + { + case APP_CMD_INIT_WINDOW: + // Window created, initialize Vulkan + if (app->window != nullptr) + { + vulkanApp->initVulkan(); + } + break; + case APP_CMD_TERM_WINDOW: + // Window destroyed, clean up Vulkan + vulkanApp->cleanup(); + break; + default: + break; + } + } + + // Handle input events + static int32_t handleInputEvent(android_app *app, AInputEvent *event) + { + auto *vulkanApp = static_cast(app->userData); + if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) + { + // Handle touch events + float x = AMotionEvent_getX(event, 0); + float y = AMotionEvent_getY(event, 0); + + // Process touch coordinates + LOGI("Touch at: %f, %f", x, y); + + return 1; + } + return 0; + } #endif }; // Platform-specific entry point #if PLATFORM_ANDROID // Android main entry point -void android_main(android_app* app) { - // Make sure glue isn't stripped - app_dummy(); - - try { - // Create and run the Vulkan application - HelloTriangleApplication vulkanApp(app); - vulkanApp.run(); - } catch (const std::exception& e) { - LOGE("Exception caught: %s", e.what()); - } +void android_main(android_app *app) +{ + // Make sure glue isn't stripped + app_dummy(); + + try + { + // Create and run the Vulkan application + HelloTriangleApplication vulkanApp(app); + vulkanApp.run(); + } + catch (const std::exception &e) + { + LOGE("Exception caught: %s", e.what()); + } } #else // Desktop main entry point -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } #endif diff --git a/attachments/35_gltf_ktx.cpp b/attachments/35_gltf_ktx.cpp index c2139878..e248d83c 100644 --- a/attachments/35_gltf_ktx.cpp +++ b/attachments/35_gltf_ktx.cpp @@ -1,32 +1,32 @@ -#include -#include -#include -#include -#include -#include -#include #include -#include #include #include +#include +#include +#include +#include +#include +#include #include +#include +#include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include #if defined(__ANDROID__) -#include -#include +# include +# include #endif #include #if defined(__ANDROID__) - #define PLATFORM_ANDROID 1 +# define PLATFORM_ANDROID 1 #else - #define PLATFORM_DESKTOP 1 +# define PLATFORM_DESKTOP 1 #endif // Include tinygltf instead of tinyobjloader @@ -38,34 +38,41 @@ import vulkan_hpp; #include #if PLATFORM_ANDROID - #include - #include - #include - #include - - // Declare and implement app_dummy function from native_app_glue - extern "C" void app_dummy() { - // This is a dummy function that does nothing - // It's used to prevent the linker from stripping out the native_app_glue code - } - - // Define AAssetManager type for Android - typedef AAssetManager AssetManagerType; - - #define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "VulkanTutorial", __VA_ARGS__)) - #define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "VulkanTutorial", __VA_ARGS__)) - #define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "VulkanTutorial", __VA_ARGS__)) +# include +# include +# include +# include + +// Declare and implement app_dummy function from native_app_glue +extern "C" void app_dummy() +{ + // This is a dummy function that does nothing + // It's used to prevent the linker from stripping out the native_app_glue code +} + +// Define AAssetManager type for Android +typedef AAssetManager AssetManagerType; + +# define LOGI(...) ((void) __android_log_print(ANDROID_LOG_INFO, "VulkanTutorial", __VA_ARGS__)) +# define LOGW(...) ((void) __android_log_print(ANDROID_LOG_WARN, "VulkanTutorial", __VA_ARGS__)) +# define LOGE(...) ((void) __android_log_print(ANDROID_LOG_ERROR, "VulkanTutorial", __VA_ARGS__)) #else - // Define AAssetManager type for non-Android platforms - typedef void AssetManagerType; - // Desktop-specific includes - #define GLFW_INCLUDE_VULKAN - #include - - // Define logging macros for Desktop - #define LOGI(...) printf(__VA_ARGS__); printf("\n") - #define LOGW(...) printf(__VA_ARGS__); printf("\n") - #define LOGE(...) fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n") +// Define AAssetManager type for non-Android platforms +typedef void AssetManagerType; +// Desktop-specific includes +# define GLFW_INCLUDE_VULKAN +# include + +// Define logging macros for Desktop +# define LOGI(...) \ + printf(__VA_ARGS__); \ + printf("\n") +# define LOGW(...) \ + printf(__VA_ARGS__); \ + printf("\n") +# define LOGE(...) \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "\n") #endif #define GLM_FORCE_RADIANS @@ -76,46 +83,49 @@ import vulkan_hpp; #include #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; constexpr uint64_t FenceTimeout = 100000000; // Update paths to use glTF model and KTX2 texture -const std::string MODEL_PATH = "models/viking_room.glb"; -const std::string TEXTURE_PATH = "textures/viking_room.ktx2"; -constexpr int MAX_FRAMES_IN_FLIGHT = 2; +const std::string MODEL_PATH = "models/viking_room.glb"; +const std::string TEXTURE_PATH = "textures/viking_room.ktx2"; +constexpr int MAX_FRAMES_IN_FLIGHT = 2; // Define VpProfileProperties structure for Android only #if PLATFORM_ANDROID -#ifndef VP_PROFILE_PROPERTIES_DEFINED -#define VP_PROFILE_PROPERTIES_DEFINED -struct VpProfileProperties { - char name[256]; - uint32_t specVersion; +# ifndef VP_PROFILE_PROPERTIES_DEFINED +# define VP_PROFILE_PROPERTIES_DEFINED +struct VpProfileProperties +{ + char name[256]; + uint32_t specVersion; }; -#endif +# endif #endif // Define Vulkan Profile constants #ifndef VP_KHR_ROADMAP_2022_NAME -#define VP_KHR_ROADMAP_2022_NAME "VP_KHR_roadmap_2022" +# define VP_KHR_ROADMAP_2022_NAME "VP_KHR_roadmap_2022" #endif #ifndef VP_KHR_ROADMAP_2022_SPEC_VERSION -#define VP_KHR_ROADMAP_2022_SPEC_VERSION 1 +# define VP_KHR_ROADMAP_2022_SPEC_VERSION 1 #endif -struct AppInfo { - bool profileSupported = false; - VpProfileProperties profile; +struct AppInfo +{ + bool profileSupported = false; + VpProfileProperties profile; }; #if PLATFORM_ANDROID -void android_main(android_app* app); +void android_main(android_app *app); -struct AndroidAppState { - ANativeWindow* nativeWindow = nullptr; - bool initialized = false; - android_app* app = nullptr; +struct AndroidAppState +{ + ANativeWindow *nativeWindow = nullptr; + bool initialized = false; + android_app *app = nullptr; }; #endif @@ -125,1320 +135,1404 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -struct Vertex { - glm::vec3 pos; - glm::vec3 color; - glm::vec2 texCoord; - - static vk::VertexInputBindingDescription getBindingDescription() { - return { 0, sizeof(Vertex), vk::VertexInputRate::eVertex }; - } - - static std::array getAttributeDescriptions() { - return { - vk::VertexInputAttributeDescription( 0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, pos) ), - vk::VertexInputAttributeDescription( 1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color) ), - vk::VertexInputAttributeDescription( 2, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, texCoord) ) - }; - } - - bool operator==(const Vertex& other) const { - return pos == other.pos && color == other.color && texCoord == other.texCoord; - } +struct Vertex +{ + glm::vec3 pos; + glm::vec3 color; + glm::vec2 texCoord; + + static vk::VertexInputBindingDescription getBindingDescription() + { + return {0, sizeof(Vertex), vk::VertexInputRate::eVertex}; + } + + static std::array getAttributeDescriptions() + { + return { + vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, pos)), + vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color)), + vk::VertexInputAttributeDescription(2, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, texCoord))}; + } + + bool operator==(const Vertex &other) const + { + return pos == other.pos && color == other.color && texCoord == other.texCoord; + } }; -template<> struct std::hash { - size_t operator()(Vertex const& vertex) const noexcept { - return ((hash()(vertex.pos) ^ (hash()(vertex.color) << 1)) >> 1) ^ (hash()(vertex.texCoord) << 1); - } +template <> +struct std::hash +{ + size_t operator()(Vertex const &vertex) const noexcept + { + return ((hash()(vertex.pos) ^ (hash()(vertex.color) << 1)) >> 1) ^ (hash()(vertex.texCoord) << 1); + } }; -struct UniformBufferObject { - alignas(16) glm::mat4 model; - alignas(16) glm::mat4 view; - alignas(16) glm::mat4 proj; +struct UniformBufferObject +{ + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; }; -class VulkanApplication { -public: +class VulkanApplication +{ + public: #if PLATFORM_ANDROID - void run(android_app* app) { - androidAppState.nativeWindow = app->window; - androidAppState.app = app; - app->userData = &androidAppState; - app->onAppCmd = handleAppCommand; - // Note: onInputEvent is no longer a member of android_app in the current NDK version - // Input events are now handled differently - - int events; - android_poll_source* source; - - while (app->destroyRequested == 0) { - while (ALooper_pollOnce(androidAppState.initialized ? 0 : -1, nullptr, &events, (void**)&source) >= 0) { - if (source != nullptr) { - source->process(app, source); - } - } - - if (androidAppState.initialized && androidAppState.nativeWindow != nullptr) { - drawFrame(); - } - } - - if (androidAppState.initialized) { - device.waitIdle(); - } - } + void run(android_app *app) + { + androidAppState.nativeWindow = app->window; + androidAppState.app = app; + app->userData = &androidAppState; + app->onAppCmd = handleAppCommand; + // Note: onInputEvent is no longer a member of android_app in the current NDK version + // Input events are now handled differently + + int events; + android_poll_source *source; + + while (app->destroyRequested == 0) + { + while (ALooper_pollOnce(androidAppState.initialized ? 0 : -1, nullptr, &events, (void **) &source) >= 0) + { + if (source != nullptr) + { + source->process(app, source); + } + } + + if (androidAppState.initialized && androidAppState.nativeWindow != nullptr) + { + drawFrame(); + } + } + + if (androidAppState.initialized) + { + device.waitIdle(); + } + } #else - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } + void run() + { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } #endif -private: + private: #if PLATFORM_ANDROID - AndroidAppState androidAppState; - - static void handleAppCommand(android_app* app, int32_t cmd) { - auto* appState = static_cast(app->userData); - - switch (cmd) { - case APP_CMD_INIT_WINDOW: - if (app->window != nullptr) { - appState->nativeWindow = app->window; - // We can't cast AndroidAppState to VulkanApplication directly - // Instead, we need to access the VulkanApplication instance through a global variable - // or another mechanism. For now, we'll just set the initialized flag. - appState->initialized = true; - } - break; - case APP_CMD_TERM_WINDOW: - appState->nativeWindow = nullptr; - break; - default: - break; - } - } - - static int32_t handleInputEvent(android_app* app, AInputEvent* event) { - if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) { - float x = AMotionEvent_getX(event, 0); - float y = AMotionEvent_getY(event, 0); - - LOGI("Touch at: %f, %f", x, y); - - return 1; - } - return 0; - } + AndroidAppState androidAppState; + + static void handleAppCommand(android_app *app, int32_t cmd) + { + auto *appState = static_cast(app->userData); + + switch (cmd) + { + case APP_CMD_INIT_WINDOW: + if (app->window != nullptr) + { + appState->nativeWindow = app->window; + // We can't cast AndroidAppState to VulkanApplication directly + // Instead, we need to access the VulkanApplication instance through a global variable + // or another mechanism. For now, we'll just set the initialized flag. + appState->initialized = true; + } + break; + case APP_CMD_TERM_WINDOW: + appState->nativeWindow = nullptr; + break; + default: + break; + } + } + + static int32_t handleInputEvent(android_app *app, AInputEvent *event) + { + if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) + { + float x = AMotionEvent_getX(event, 0); + float y = AMotionEvent_getY(event, 0); + + LOGI("Touch at: %f, %f", x, y); + + return 1; + } + return 0; + } #else - GLFWwindow* window = nullptr; + GLFWwindow *window = nullptr; #endif - AppInfo appInfo; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::Format swapChainImageFormat = vk::Format::eUndefined; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - - vk::raii::Image depthImage = nullptr; - vk::raii::DeviceMemory depthImageMemory = nullptr; - vk::raii::ImageView depthImageView = nullptr; - - vk::raii::Image textureImage = nullptr; - vk::raii::DeviceMemory textureImageMemory = nullptr; - vk::raii::ImageView textureImageView = nullptr; - vk::raii::Sampler textureSampler = nullptr; - vk::Format textureImageFormat = vk::Format::eUndefined; - - std::vector vertices; - std::vector indices; - vk::raii::Buffer vertexBuffer = nullptr; - vk::raii::DeviceMemory vertexBufferMemory = nullptr; - vk::raii::Buffer indexBuffer = nullptr; - vk::raii::DeviceMemory indexBufferMemory = nullptr; - - std::vector uniformBuffers; - std::vector uniformBuffersMemory; - std::vector uniformBuffersMapped; - - vk::raii::DescriptorPool descriptorPool = nullptr; - std::vector descriptorSets; - - vk::raii::CommandPool commandPool = nullptr; - std::vector commandBuffers; - - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; - std::vector inFlightFences; - uint32_t currentFrame = 0; - - bool framebufferResized = false; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; + AppInfo appInfo; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::Format swapChainImageFormat = vk::Format::eUndefined; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + + vk::raii::Image depthImage = nullptr; + vk::raii::DeviceMemory depthImageMemory = nullptr; + vk::raii::ImageView depthImageView = nullptr; + + vk::raii::Image textureImage = nullptr; + vk::raii::DeviceMemory textureImageMemory = nullptr; + vk::raii::ImageView textureImageView = nullptr; + vk::raii::Sampler textureSampler = nullptr; + vk::Format textureImageFormat = vk::Format::eUndefined; + + std::vector vertices; + std::vector indices; + vk::raii::Buffer vertexBuffer = nullptr; + vk::raii::DeviceMemory vertexBufferMemory = nullptr; + vk::raii::Buffer indexBuffer = nullptr; + vk::raii::DeviceMemory indexBufferMemory = nullptr; + + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + std::vector uniformBuffersMapped; + + vk::raii::DescriptorPool descriptorPool = nullptr; + std::vector descriptorSets; + + vk::raii::CommandPool commandPool = nullptr; + std::vector commandBuffers; + + std::vector presentCompleteSemaphore; + std::vector renderFinishedSemaphore; + std::vector inFlightFences; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; #if PLATFORM_DESKTOP - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); - } - - static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { - auto app = static_cast(glfwGetWindowUserPointer(window)); - app->framebufferResized = true; - } + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow *window, int width, int height) + { + auto app = static_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } #endif -public: - void initVulkan() { - createInstance(); - setupDebugMessenger(); - createSurface(); - pickPhysicalDevice(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createDescriptorSetLayout(); - createGraphicsPipeline(); - createCommandPool(); - createDepthResources(); - createTextureImage(); - createTextureImageView(); - createTextureSampler(); - loadModel(); - createVertexBuffer(); - createIndexBuffer(); - createUniformBuffers(); - createDescriptorPool(); - createDescriptorSets(); - createCommandBuffers(); - createSyncObjects(); - } - -private: - + public: + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createDescriptorSetLayout(); + createGraphicsPipeline(); + createCommandPool(); + createDepthResources(); + createTextureImage(); + createTextureImageView(); + createTextureSampler(); + loadModel(); + createVertexBuffer(); + createIndexBuffer(); + createUniformBuffers(); + createDescriptorPool(); + createDescriptorSets(); + createCommandBuffers(); + createSyncObjects(); + } + + private: #if PLATFORM_DESKTOP - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - drawFrame(); - } - - device.waitIdle(); - } + void mainLoop() + { + while (!glfwWindowShouldClose(window)) + { + glfwPollEvents(); + drawFrame(); + } + + device.waitIdle(); + } #endif - void cleanupSwapChain() { - swapChainImageViews.clear(); - swapChain = nullptr; - } + void cleanupSwapChain() + { + swapChainImageViews.clear(); + swapChain = nullptr; + } #if PLATFORM_DESKTOP - void cleanup() const { - glfwDestroyWindow(window); - glfwTerminate(); - } + void cleanup() const + { + glfwDestroyWindow(window); + glfwTerminate(); + } #endif - void recreateSwapChain() { + void recreateSwapChain() + { #if PLATFORM_DESKTOP - int width = 0, height = 0; - glfwGetFramebufferSize(window, &width, &height); - while (width == 0 || height == 0) { - glfwGetFramebufferSize(window, &width, &height); - glfwWaitEvents(); - } + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) + { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } #endif - device.waitIdle(); - - cleanupSwapChain(); - createSwapChain(); - createImageViews(); - createDepthResources(); - } - - 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_API_VERSION_1_3 - }; - - auto extensions = getRequiredExtensions(); - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledExtensionCount = static_cast(extensions.size()), - .ppEnabledExtensionNames = extensions.data() - }; - - instance = vk::raii::Instance(context, createInfo); - LOGI("Vulkan instance created"); - } - - void setupDebugMessenger() { - // Debug messenger setup is disabled for now to avoid compatibility issues - // This is a simplified approach to get the code compiling - if (!enableValidationLayers) return; - - LOGI("Debug messenger setup skipped for compatibility"); - } - - void createSurface() { + device.waitIdle(); + + cleanupSwapChain(); + createSwapChain(); + createImageViews(); + createDepthResources(); + } + + 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_API_VERSION_1_3}; + + auto extensions = getRequiredExtensions(); + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledExtensionCount = static_cast(extensions.size()), + .ppEnabledExtensionNames = extensions.data()}; + + instance = vk::raii::Instance(context, createInfo); + LOGI("Vulkan instance created"); + } + + void setupDebugMessenger() + { + // Debug messenger setup is disabled for now to avoid compatibility issues + // This is a simplified approach to get the code compiling + if (!enableValidationLayers) + return; + + LOGI("Debug messenger setup skipped for compatibility"); + } + + void createSurface() + { #if PLATFORM_DESKTOP - VkSurfaceKHR _surface; - if (glfwCreateWindowSurface(*instance, window, nullptr, &_surface) != VK_SUCCESS) { - throw std::runtime_error("failed to create window surface!"); - } - surface = vk::raii::SurfaceKHR(instance, _surface); + VkSurfaceKHR _surface; + if (glfwCreateWindowSurface(*instance, window, nullptr, &_surface) != VK_SUCCESS) + { + throw std::runtime_error("failed to create window surface!"); + } + surface = vk::raii::SurfaceKHR(instance, _surface); #else - VkSurfaceKHR _surface; - VkAndroidSurfaceCreateInfoKHR createInfo{ - .sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR, - .window = androidAppState.nativeWindow - }; - if (vkCreateAndroidSurfaceKHR(*instance, &createInfo, nullptr, &_surface) != VK_SUCCESS) { - throw std::runtime_error("failed to create Android surface!"); - } - surface = vk::raii::SurfaceKHR(instance, _surface); + VkSurfaceKHR _surface; + VkAndroidSurfaceCreateInfoKHR createInfo{ + .sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR, + .window = androidAppState.nativeWindow}; + if (vkCreateAndroidSurfaceKHR(*instance, &createInfo, nullptr, &_surface) != VK_SUCCESS) + { + throw std::runtime_error("failed to create Android surface!"); + } + surface = vk::raii::SurfaceKHR(instance, _surface); #endif - } + } - void pickPhysicalDevice() { - std::vector devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( + void pickPhysicalDevice() + { + std::vector devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( devices, - [&](auto const& device) { + [&](auto const &device) { // Check if the device supports the Vulkan 1.3 API version bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; // Check if any of the queue families support graphics operations auto queueFamilies = device.getQueueFamilyProperties(); bool supportsGraphics = - std::ranges::any_of(queueFamilies, [](auto const& qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); // Check if all required device extensions are available auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); bool supportsAllRequiredExtensions = std::ranges::all_of(requiredDeviceExtension, - [&availableDeviceExtensions](auto const& requiredDeviceExtension) { - return std::ranges::any_of(availableDeviceExtensions, - [requiredDeviceExtension](auto const& availableDeviceExtension) { - return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; - }); - }); - - auto features = device.template getFeatures2(); + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { + return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; + }); + }); + + auto features = device.template getFeatures2(); bool supportsRequiredFeatures = features.template get().dynamicRendering && - features.template get().extendedDynamicState; + features.template get().extendedDynamicState; return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; }); - if (devIter != devices.end()) { - physicalDevice = *devIter; + if (devIter != devices.end()) + { + physicalDevice = *devIter; - // Check for Vulkan profile support - VpProfileProperties profileProperties; + // Check for Vulkan profile support + VpProfileProperties profileProperties; #if PLATFORM_ANDROID - strcpy(profileProperties.name, VP_KHR_ROADMAP_2022_NAME); + strcpy(profileProperties.name, VP_KHR_ROADMAP_2022_NAME); #else - strcpy(profileProperties.profileName, VP_KHR_ROADMAP_2022_NAME); + strcpy(profileProperties.profileName, VP_KHR_ROADMAP_2022_NAME); #endif - profileProperties.specVersion = VP_KHR_ROADMAP_2022_SPEC_VERSION; + profileProperties.specVersion = VP_KHR_ROADMAP_2022_SPEC_VERSION; - VkBool32 supported = VK_FALSE; - bool result = false; + VkBool32 supported = VK_FALSE; + bool result = false; #if PLATFORM_ANDROID - // Create a vp::ProfileDesc from our VpProfileProperties - vp::ProfileDesc profileDesc = { - profileProperties.name, - profileProperties.specVersion - }; - - // Use vp::GetProfileSupport for Android - result = vp::GetProfileSupport( - *physicalDevice, // Pass the physical device directly - &profileDesc, // Pass the profile description - &supported // Output parameter for support status - ); + // Create a vp::ProfileDesc from our VpProfileProperties + vp::ProfileDesc profileDesc = { + profileProperties.name, + profileProperties.specVersion}; + + // Use vp::GetProfileSupport for Android + result = vp::GetProfileSupport( + *physicalDevice, // Pass the physical device directly + &profileDesc, // Pass the profile description + &supported // Output parameter for support status + ); #else - // Use vpGetPhysicalDeviceProfileSupport for Desktop - VkResult vk_result = vpGetPhysicalDeviceProfileSupport( - *instance, - *physicalDevice, - &profileProperties, - &supported - ); - result = vk_result == static_cast(vk::Result::eSuccess); + // Use vpGetPhysicalDeviceProfileSupport for Desktop + VkResult vk_result = vpGetPhysicalDeviceProfileSupport( + *instance, + *physicalDevice, + &profileProperties, + &supported); + result = vk_result == static_cast(vk::Result::eSuccess); #endif - const char* name = nullptr; + const char *name = nullptr; #ifdef PLATFORM_ANDROID - name = profileProperties.name; + name = profileProperties.name; #else - name = profileProperties.profileName; + name = profileProperties.profileName; #endif - if (result && supported == VK_TRUE) { - appInfo.profileSupported = true; - appInfo.profile = profileProperties; - LOGI("Device supports Vulkan profile: %s", name); - } else { - LOGI("Device does not support Vulkan profile: %s", name); - } - } else { - throw std::runtime_error("failed to find a suitable GPU!"); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - auto features = physicalDevice.getFeatures2(); - vk::PhysicalDeviceVulkan13Features vulkan13Features; - vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT extendedDynamicStateFeatures; - vulkan13Features.dynamicRendering = vk::True; - vulkan13Features.synchronization2 = vk::True; - extendedDynamicStateFeatures.extendedDynamicState = vk::True; - vulkan13Features.pNext = &extendedDynamicStateFeatures; - features.pNext = &vulkan13Features; - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo { .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ - .pNext = &features, - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() - }; - - // Create the device with the appropriate features - device = vk::raii::Device(physicalDevice, deviceCreateInfo); - - queue = vk::raii::Queue(device, queueIndex, 0); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(*surface); - swapChainImageFormat = 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{ - .surface = *surface, .minImageCount = minImageCount, - .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, - .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 }; - - swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); - swapChainImages = swapChain.getImages(); - } - - void createImageViews() { - 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( device, imageViewCreateInfo ); - } - } - - void createDescriptorSetLayout() { - std::array bindings = { - vk::DescriptorSetLayoutBinding( 0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr), - vk::DescriptorSetLayoutBinding( 1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment, nullptr) - }; - - vk::DescriptorSetLayoutCreateInfo layoutInfo{ .bindingCount = static_cast(bindings.size()), .pBindings = bindings.data() }; - descriptorSetLayout = vk::raii::DescriptorSetLayout(device, layoutInfo); - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(this->readFile("shaders/slang.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}; - - auto bindingDescription = Vertex::getBindingDescription(); - auto attributeDescriptions = Vertex::getAttributeDescriptions(); - vk::PipelineVertexInputStateCreateInfo vertexInputInfo{ - .vertexBindingDescriptionCount = 1, - .pVertexBindingDescriptions = &bindingDescription, - .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), - .pVertexAttributeDescriptions = attributeDescriptions.data() - }; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ - .topology = vk::PrimitiveTopology::eTriangleList, - .primitiveRestartEnable = vk::False - }; - vk::PipelineViewportStateCreateInfo viewportState{ - .viewportCount = 1, - .scissorCount = 1 - }; - vk::PipelineRasterizationStateCreateInfo rasterizer{ - .depthClampEnable = vk::False, - .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, - .cullMode = vk::CullModeFlagBits::eBack, // Re-enabled culling for better performance - .frontFace = vk::FrontFace::eClockwise, // Keeping Clockwise for glTF - .depthBiasEnable = vk::False - }; - rasterizer.lineWidth = 1.0f; - vk::PipelineMultisampleStateCreateInfo multisampling{ - .rasterizationSamples = vk::SampleCountFlagBits::e1, - .sampleShadingEnable = vk::False - }; - vk::PipelineDepthStencilStateCreateInfo depthStencil{ - .depthTestEnable = vk::True, - .depthWriteEnable = vk::True, - .depthCompareOp = vk::CompareOp::eLess, - .depthBoundsTestEnable = vk::False, - .stencilTestEnable = vk::False - }; - vk::PipelineColorBlendAttachmentState colorBlendAttachment; - colorBlendAttachment.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA; - colorBlendAttachment.blendEnable = vk::False; - - vk::PipelineColorBlendStateCreateInfo colorBlending{ - .logicOpEnable = vk::False, - .logicOp = vk::LogicOp::eCopy, - .attachmentCount = 1, - .pAttachments = &colorBlendAttachment - }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0 }; - - pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); - - vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ .colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainImageFormat }; - vk::GraphicsPipelineCreateInfo pipelineInfo{ .pNext = &pipelineRenderingCreateInfo, - .stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pDepthStencilState = &depthStencil, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = *pipelineLayout, - .renderPass = nullptr - }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{ - .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex - }; - commandPool = vk::raii::CommandPool(device, poolInfo); - } - - void createDepthResources() { - vk::Format depthFormat = findDepthFormat(); - - createImage(swapChainExtent.width, swapChainExtent.height, depthFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eDepthStencilAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, depthImage, depthImageMemory); - depthImageView = createImageView(depthImage, depthFormat, vk::ImageAspectFlagBits::eDepth); - } - - vk::Format findSupportedFormat(const std::vector& candidates, vk::ImageTiling tiling, vk::FormatFeatureFlags features) const { - for (const auto format : candidates) { - vk::FormatProperties props = physicalDevice.getFormatProperties(format); - - if (tiling == vk::ImageTiling::eLinear && (props.linearTilingFeatures & features) == features) { - return format; - } - if (tiling == vk::ImageTiling::eOptimal && (props.optimalTilingFeatures & features) == features) { - return format; - } - } - - throw std::runtime_error("failed to find supported format!"); - } - - [[nodiscard]] vk::Format findDepthFormat() const { - return findSupportedFormat( - {vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, vk::Format::eD24UnormS8Uint}, - vk::ImageTiling::eOptimal, - vk::FormatFeatureFlagBits::eDepthStencilAttachment - ); - } - - static bool hasStencilComponent(vk::Format format) { - return format == vk::Format::eD32SfloatS8Uint || format == vk::Format::eD24UnormS8Uint; - } - - void createTextureImage() { - // Load KTX2 texture instead of using stb_image - ktxTexture* kTexture; - KTX_error_code result = ktxTexture_CreateFromNamedFile( - TEXTURE_PATH.c_str(), - KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, - &kTexture); - - if (result != KTX_SUCCESS) { - throw std::runtime_error("failed to load ktx texture image!"); - } - - // Get texture dimensions and data - uint32_t texWidth = kTexture->baseWidth; - uint32_t texHeight = kTexture->baseHeight; - ktx_size_t imageSize = ktxTexture_GetImageSize(kTexture, 0); - ktx_uint8_t* ktxTextureData = ktxTexture_GetData(kTexture); - - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, imageSize); - memcpy(data, ktxTextureData, imageSize); - stagingBufferMemory.unmapMemory(); - - // Determine the Vulkan format from KTX format - vk::Format textureFormat; - - // Check if the KTX texture has a format - if (kTexture->classId == ktxTexture2_c) { - // For KTX2 files, we can get the format directly - auto* ktx2 = reinterpret_cast(kTexture); - textureFormat = static_cast(ktx2->vkFormat); - if (textureFormat == vk::Format::eUndefined) { - // If the format is undefined, fall back to a reasonable default - textureFormat = vk::Format::eR8G8B8A8Unorm; - } - } else { - // For KTX1 files or if we can't determine the format, use a reasonable default - textureFormat = vk::Format::eR8G8B8A8Unorm; - } - - textureImageFormat = textureFormat; - - createImage(texWidth, texHeight, textureFormat, vk::ImageTiling::eOptimal, - vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, - vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory); - - transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal); - copyBufferToImage(stagingBuffer, textureImage, texWidth, texHeight); - transitionImageLayout(textureImage, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal); - - ktxTexture_Destroy(kTexture); - } - - void createTextureImageView() { - textureImageView = createImageView(textureImage, textureImageFormat, vk::ImageAspectFlagBits::eColor); - } - - void createTextureSampler() { - vk::PhysicalDeviceProperties properties = physicalDevice.getProperties(); - vk::SamplerCreateInfo samplerInfo{ - .magFilter = vk::Filter::eLinear, - .minFilter = vk::Filter::eLinear, - .mipmapMode = vk::SamplerMipmapMode::eLinear, - .addressModeU = vk::SamplerAddressMode::eRepeat, - .addressModeV = vk::SamplerAddressMode::eRepeat, - .addressModeW = vk::SamplerAddressMode::eRepeat, - .mipLodBias = 0.0f, - .anisotropyEnable = vk::True, - .maxAnisotropy = properties.limits.maxSamplerAnisotropy, - .compareEnable = vk::False, - .compareOp = vk::CompareOp::eAlways - }; - textureSampler = vk::raii::Sampler(device, samplerInfo); - } - - vk::raii::ImageView createImageView(vk::raii::Image& image, vk::Format format, vk::ImageAspectFlags aspectFlags) { - vk::ImageViewCreateInfo viewInfo{ - .image = *image, - .viewType = vk::ImageViewType::e2D, - .format = format, - .subresourceRange = { aspectFlags, 0, 1, 0, 1 } - }; - return vk::raii::ImageView(device, viewInfo); - } - - void createImage(uint32_t width, uint32_t height, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image& image, vk::raii::DeviceMemory& imageMemory) { - vk::ImageCreateInfo imageInfo{ - .imageType = vk::ImageType::e2D, - .format = format, - .extent = {width, height, 1}, - .mipLevels = 1, - .arrayLayers = 1, - .samples = vk::SampleCountFlagBits::e1, - .tiling = tiling, - .usage = usage, - .sharingMode = vk::SharingMode::eExclusive, - .initialLayout = vk::ImageLayout::eUndefined - }; - image = vk::raii::Image(device, imageInfo); - - vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ - .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) - }; - imageMemory = vk::raii::DeviceMemory(device, allocInfo); - image.bindMemory(*imageMemory, 0); - } - - void transitionImageLayout(const vk::raii::Image& image, vk::ImageLayout oldLayout, vk::ImageLayout newLayout) { - auto commandBuffer = beginSingleTimeCommands(); - - vk::ImageMemoryBarrier barrier{ - .oldLayout = oldLayout, - .newLayout = newLayout, - .image = *image, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } - }; - - vk::PipelineStageFlags sourceStage; - vk::PipelineStageFlags destinationStage; - - if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) { - barrier.srcAccessMask = {}; - barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; - - sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; - destinationStage = vk::PipelineStageFlagBits::eTransfer; - } else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) { - barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; - barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - - sourceStage = vk::PipelineStageFlagBits::eTransfer; - destinationStage = vk::PipelineStageFlagBits::eFragmentShader; - } else { - throw std::invalid_argument("unsupported layout transition!"); - } - commandBuffer->pipelineBarrier( sourceStage, destinationStage, {}, {}, nullptr, barrier ); - endSingleTimeCommands(*commandBuffer); - } - - void copyBufferToImage(const vk::raii::Buffer& buffer, vk::raii::Image& image, uint32_t width, uint32_t height) { - std::unique_ptr commandBuffer = beginSingleTimeCommands(); - vk::BufferImageCopy region{ - .bufferOffset = 0, - .bufferRowLength = 0, - .bufferImageHeight = 0, - .imageSubresource = { vk::ImageAspectFlagBits::eColor, 0, 0, 1 }, - .imageOffset = {0, 0, 0}, - .imageExtent = {width, height, 1} - }; - commandBuffer->copyBufferToImage(*buffer, *image, vk::ImageLayout::eTransferDstOptimal, {region}); - endSingleTimeCommands(*commandBuffer); - } - - void loadModel() { - // Use tinygltf to load the model instead of tinyobjloader - tinygltf::Model model; - tinygltf::TinyGLTF loader; - std::string err; - std::string warn; - - bool ret = loader.LoadBinaryFromFile(&model, &err, &warn, MODEL_PATH); - - if (!warn.empty()) { - std::cout << "glTF warning: " << warn << std::endl; - } - - if (!err.empty()) { - std::cout << "glTF error: " << err << std::endl; - } - - if (!ret) { - throw std::runtime_error("Failed to load glTF model"); - } - - vertices.clear(); - indices.clear(); - - // Process all meshes in the model - for (const auto& mesh : model.meshes) { - for (const auto& primitive : mesh.primitives) { - // Get indices - const tinygltf::Accessor& indexAccessor = model.accessors[primitive.indices]; - const tinygltf::BufferView& indexBufferView = model.bufferViews[indexAccessor.bufferView]; - const tinygltf::Buffer& indexBuffer = model.buffers[indexBufferView.buffer]; - - // Get vertex positions - const tinygltf::Accessor& posAccessor = model.accessors[primitive.attributes.at("POSITION")]; - const tinygltf::BufferView& posBufferView = model.bufferViews[posAccessor.bufferView]; - const tinygltf::Buffer& posBuffer = model.buffers[posBufferView.buffer]; - - // Get texture coordinates if available - bool hasTexCoords = primitive.attributes.find("TEXCOORD_0") != primitive.attributes.end(); - const tinygltf::Accessor* texCoordAccessor = nullptr; - const tinygltf::BufferView* texCoordBufferView = nullptr; - const tinygltf::Buffer* texCoordBuffer = nullptr; - - if (hasTexCoords) { - texCoordAccessor = &model.accessors[primitive.attributes.at("TEXCOORD_0")]; - texCoordBufferView = &model.bufferViews[texCoordAccessor->bufferView]; - texCoordBuffer = &model.buffers[texCoordBufferView->buffer]; - } - - uint32_t baseVertex = static_cast(vertices.size()); - - for (size_t i = 0; i < posAccessor.count; i++) { - Vertex vertex{}; - - const float* pos = reinterpret_cast(&posBuffer.data[posBufferView.byteOffset + posAccessor.byteOffset + i * 12]); - // glTF uses a right-handed coordinate system with Y-up - // Vulkan uses a right-handed coordinate system with Y-down - // We need to flip the Y coordinate - vertex.pos = {pos[0], -pos[1], pos[2]}; - - if (hasTexCoords) { - const float* texCoord = reinterpret_cast(&texCoordBuffer->data[texCoordBufferView->byteOffset + texCoordAccessor->byteOffset + i * 8]); - vertex.texCoord = {texCoord[0], texCoord[1]}; - } else { - vertex.texCoord = {0.0f, 0.0f}; - } - - vertex.color = {1.0f, 1.0f, 1.0f}; - - vertices.push_back(vertex); - } - - const unsigned char* indexData = &indexBuffer.data[indexBufferView.byteOffset + indexAccessor.byteOffset]; - size_t indexCount = indexAccessor.count; - size_t indexStride = 0; - - // Determine index stride based on component type - if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) { - indexStride = sizeof(uint16_t); - } else if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT) { - indexStride = sizeof(uint32_t); - } else if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) { - indexStride = sizeof(uint8_t); - } else { - throw std::runtime_error("Unsupported index component type"); - } - - indices.reserve(indices.size() + indexCount); - - for (size_t i = 0; i < indexCount; i++) { - uint32_t index = 0; - - if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) { - index = *reinterpret_cast(indexData + i * indexStride); - } else if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT) { - index = *reinterpret_cast(indexData + i * indexStride); - } else if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) { - index = *reinterpret_cast(indexData + i * indexStride); - } - - indices.push_back(baseVertex + index); - } - } - } - } - - void createVertexBuffer() { - vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(dataStaging, vertices.data(), bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); - - copyBuffer(stagingBuffer, vertexBuffer, bufferSize); - } - - void createIndexBuffer() { - vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); - - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(data, indices.data(), bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); - - copyBuffer(stagingBuffer, indexBuffer, bufferSize); - } - - void createUniformBuffers() { - uniformBuffers.clear(); - uniformBuffersMemory.clear(); - uniformBuffersMapped.clear(); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DeviceSize bufferSize = sizeof(UniformBufferObject); - vk::raii::Buffer buffer({}); - vk::raii::DeviceMemory bufferMem({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); - uniformBuffers.emplace_back(std::move(buffer)); - uniformBuffersMemory.emplace_back(std::move(bufferMem)); - uniformBuffersMapped.emplace_back( uniformBuffersMemory[i].mapMemory(0, bufferSize)); - } - } - - void createDescriptorPool() { - std::array poolSize { - vk::DescriptorPoolSize( vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT), - vk::DescriptorPoolSize( vk::DescriptorType::eCombinedImageSampler, MAX_FRAMES_IN_FLIGHT) - }; - vk::DescriptorPoolCreateInfo poolInfo{ - .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, - .maxSets = MAX_FRAMES_IN_FLIGHT, - .poolSizeCount = static_cast(poolSize.size()), - .pPoolSizes = poolSize.data() - }; - descriptorPool = vk::raii::DescriptorPool(device, poolInfo); - } - - void createDescriptorSets() { - std::vector layouts(MAX_FRAMES_IN_FLIGHT, *descriptorSetLayout); - vk::DescriptorSetAllocateInfo allocInfo{ - .descriptorPool = *descriptorPool, - .descriptorSetCount = static_cast(layouts.size()), - .pSetLayouts = layouts.data() - }; - - descriptorSets.clear(); - descriptorSets = device.allocateDescriptorSets(allocInfo); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DescriptorBufferInfo bufferInfo{ - .buffer = *uniformBuffers[i], - .offset = 0, - .range = sizeof(UniformBufferObject) - }; - vk::DescriptorImageInfo imageInfo{ - .sampler = *textureSampler, - .imageView = *textureImageView, - .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal - }; - std::array descriptorWrites{ - vk::WriteDescriptorSet{ - .dstSet = *descriptorSets[i], - .dstBinding = 0, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eUniformBuffer, - .pBufferInfo = &bufferInfo - }, - vk::WriteDescriptorSet{ - .dstSet = *descriptorSets[i], - .dstBinding = 1, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eCombinedImageSampler, - .pImageInfo = &imageInfo - } - }; - device.updateDescriptorSets(descriptorWrites, {}); - } - } - - void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer& buffer, vk::raii::DeviceMemory& bufferMemory) { - vk::BufferCreateInfo bufferInfo{ - .size = size, - .usage = usage, - .sharingMode = vk::SharingMode::eExclusive - }; - buffer = vk::raii::Buffer(device, bufferInfo); - vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ - .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) - }; - bufferMemory = vk::raii::DeviceMemory(device, allocInfo); - buffer.bindMemory(*bufferMemory, 0); - } - - std::unique_ptr beginSingleTimeCommands() { - vk::CommandBufferAllocateInfo allocInfo{ - .commandPool = *commandPool, - .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = 1 - }; - std::unique_ptr commandBuffer = std::make_unique(std::move(vk::raii::CommandBuffers(device, allocInfo).front())); - - vk::CommandBufferBeginInfo beginInfo{ - .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit - }; - commandBuffer->begin(beginInfo); - - return commandBuffer; - } - - void endSingleTimeCommands(const vk::raii::CommandBuffer& commandBuffer) const { - commandBuffer.end(); - - vk::SubmitInfo submitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandBuffer }; - queue.submit(submitInfo, nullptr); - queue.waitIdle(); - } - - void copyBuffer(vk::raii::Buffer & srcBuffer, vk::raii::Buffer & dstBuffer, vk::DeviceSize size) { - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = *commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1 }; - vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); - commandCopyBuffer.begin(vk::CommandBufferBeginInfo{ .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit }); - commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy{ .size = size }); - commandCopyBuffer.end(); - queue.submit(vk::SubmitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer }, nullptr); - queue.waitIdle(); - } - - uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) { - vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); - - for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { - if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { - return i; - } - } - - throw std::runtime_error("failed to find suitable memory type!"); - } - - void createCommandBuffers() { - commandBuffers.clear(); - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = *commandPool, .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = MAX_FRAMES_IN_FLIGHT }; - commandBuffers = vk::raii::CommandBuffers(device, allocInfo); - } - - void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin({}); - transition_image_layout( - imageIndex, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, - vk::AccessFlagBits2::eColorAttachmentWrite, - vk::PipelineStageFlagBits2::eTopOfPipe, - vk::PipelineStageFlagBits2::eColorAttachmentOutput - ); - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::RenderingAttachmentInfo attachmentInfo = { - .imageView = *swapChainImageViews[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor - }; - vk::RenderingInfo renderingInfo = { - .renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &attachmentInfo - }; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].bindIndexBuffer( *indexBuffer, 0, vk::IndexType::eUint32 ); - commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, *pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); - commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); - commandBuffers[currentFrame].endRendering(); - transition_image_layout( - imageIndex, - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, - {}, - vk::PipelineStageFlagBits2::eColorAttachmentOutput, - vk::PipelineStageFlagBits2::eBottomOfPipe - ); - commandBuffers[currentFrame].end(); - } - - void transition_image_layout( - uint32_t imageIndex, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::AccessFlags2 src_access_mask, - vk::AccessFlags2 dst_access_mask, - vk::PipelineStageFlags2 src_stage_mask, - vk::PipelineStageFlags2 dst_stage_mask - ) { - vk::ImageMemoryBarrier2 barrier = { - .srcStageMask = src_stage_mask, - .srcAccessMask = src_access_mask, - .dstStageMask = dst_stage_mask, - .dstAccessMask = dst_access_mask, - .oldLayout = old_layout, - .newLayout = new_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); - } - - void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); - - for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - } - - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - inFlightFences.emplace_back(device, vk::FenceCreateInfo{ .flags = vk::FenceCreateFlagBits::eSignaled }); - } - } - - void updateUniformBuffer(uint32_t currentImage) const { - static auto startTime = std::chrono::high_resolution_clock::now(); - - auto currentTime = std::chrono::high_resolution_clock::now(); - float time = std::chrono::duration(currentTime - startTime).count(); - - UniformBufferObject ubo{}; - glm::mat4 initialRotation = glm::rotate(glm::mat4(1.0f), glm::radians(-90.0f), glm::vec3(1.0f, 0.0f, 0.0f)); - glm::mat4 continuousRotation = glm::rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.model = continuousRotation * initialRotation; - ubo.view = lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); - ubo.proj[1][1] *= -1; - - memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); - } - - void drawFrame() { - while ( vk::Result::eTimeout == device.waitForFences( *inFlightFences[currentFrame], vk::True, UINT64_MAX ) ) - ; - auto [result, imageIndex] = swapChain.acquireNextImage( UINT64_MAX, *presentCompleteSemaphore[currentFrame], nullptr ); - - if (result == vk::Result::eErrorOutOfDateKHR) { - recreateSwapChain(); - return; - } - if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) { - throw std::runtime_error("failed to acquire swap chain image!"); - } - updateUniformBuffer(currentFrame); - - device.resetFences( *inFlightFences[currentFrame] ); - commandBuffers[currentFrame].reset(); - recordCommandBuffer(imageIndex); - - vk::PipelineStageFlags waitDestinationStageMask( vk::PipelineStageFlagBits::eColorAttachmentOutput ); - const vk::SubmitInfo submitInfo{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[currentFrame], - .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], - .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex] }; - queue.submit(submitInfo, *inFlightFences[currentFrame]); - - - const vk::PresentInfoKHR presentInfoKHR{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], - .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex }; - result = queue.presentKHR(presentInfoKHR); - if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { - framebufferResized = false; - recreateSwapChain(); - } else if (result != vk::Result::eSuccess) { - throw std::runtime_error("failed to present swap chain image!"); - } - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; - } - - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size(), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static vk::Format chooseSwapSurfaceFormat(const std::vector& availableFormats) { - return (availableFormats[0].format == vk::Format::eUndefined) ? vk::Format::eB8G8R8A8Unorm : availableFormats[0].format; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eFifo; - } - - vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) { - if (capabilities.currentExtent.width != std::numeric_limits::max()) { - return capabilities.currentExtent; - } + if (result && supported == VK_TRUE) + { + appInfo.profileSupported = true; + appInfo.profile = profileProperties; + LOGI("Device supports Vulkan profile: %s", name); + } + else + { + LOGI("Device does not support Vulkan profile: %s", name); + } + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + auto features = physicalDevice.getFeatures2(); + vk::PhysicalDeviceVulkan13Features vulkan13Features; + vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT extendedDynamicStateFeatures; + vulkan13Features.dynamicRendering = vk::True; + vulkan13Features.synchronization2 = vk::True; + extendedDynamicStateFeatures.extendedDynamicState = vk::True; + vulkan13Features.pNext = &extendedDynamicStateFeatures; + features.pNext = &vulkan13Features; + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{ + .pNext = &features, + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + // Create the device with the appropriate features + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(*surface); + swapChainImageFormat = 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{ + .surface = *surface, .minImageCount = minImageCount, .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + void createImageViews() + { + 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(device, imageViewCreateInfo); + } + } + + void createDescriptorSetLayout() + { + std::array bindings = { + vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr), + vk::DescriptorSetLayoutBinding(1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment, nullptr)}; + + vk::DescriptorSetLayoutCreateInfo layoutInfo{.bindingCount = static_cast(bindings.size()), .pBindings = bindings.data()}; + descriptorSetLayout = vk::raii::DescriptorSetLayout(device, layoutInfo); + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(this->readFile("shaders/slang.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}; + + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{ + .vertexBindingDescriptionCount = 1, + .pVertexBindingDescriptions = &bindingDescription, + .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), + .pVertexAttributeDescriptions = attributeDescriptions.data()}; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ + .topology = vk::PrimitiveTopology::eTriangleList, + .primitiveRestartEnable = vk::False}; + vk::PipelineViewportStateCreateInfo viewportState{ + .viewportCount = 1, + .scissorCount = 1}; + vk::PipelineRasterizationStateCreateInfo rasterizer{ + .depthClampEnable = vk::False, + .rasterizerDiscardEnable = vk::False, + .polygonMode = vk::PolygonMode::eFill, + .cullMode = vk::CullModeFlagBits::eBack, // Re-enabled culling for better performance + .frontFace = vk::FrontFace::eClockwise, // Keeping Clockwise for glTF + .depthBiasEnable = vk::False}; + rasterizer.lineWidth = 1.0f; + vk::PipelineMultisampleStateCreateInfo multisampling{ + .rasterizationSamples = vk::SampleCountFlagBits::e1, + .sampleShadingEnable = vk::False}; + vk::PipelineDepthStencilStateCreateInfo depthStencil{ + .depthTestEnable = vk::True, + .depthWriteEnable = vk::True, + .depthCompareOp = vk::CompareOp::eLess, + .depthBoundsTestEnable = vk::False, + .stencilTestEnable = vk::False}; + vk::PipelineColorBlendAttachmentState colorBlendAttachment; + colorBlendAttachment.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA; + colorBlendAttachment.blendEnable = vk::False; + + vk::PipelineColorBlendStateCreateInfo colorBlending{ + .logicOpEnable = vk::False, + .logicOp = vk::LogicOp::eCopy, + .attachmentCount = 1, + .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0}; + + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainImageFormat}; + vk::GraphicsPipelineCreateInfo pipelineInfo{.pNext = &pipelineRenderingCreateInfo, + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pDepthStencilState = &depthStencil, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = *pipelineLayout, + .renderPass = nullptr}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{ + .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex}; + commandPool = vk::raii::CommandPool(device, poolInfo); + } + + void createDepthResources() + { + vk::Format depthFormat = findDepthFormat(); + + createImage(swapChainExtent.width, swapChainExtent.height, depthFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eDepthStencilAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, depthImage, depthImageMemory); + depthImageView = createImageView(depthImage, depthFormat, vk::ImageAspectFlagBits::eDepth); + } + + vk::Format findSupportedFormat(const std::vector &candidates, vk::ImageTiling tiling, vk::FormatFeatureFlags features) const + { + for (const auto format : candidates) + { + vk::FormatProperties props = physicalDevice.getFormatProperties(format); + + if (tiling == vk::ImageTiling::eLinear && (props.linearTilingFeatures & features) == features) + { + return format; + } + if (tiling == vk::ImageTiling::eOptimal && (props.optimalTilingFeatures & features) == features) + { + return format; + } + } + + throw std::runtime_error("failed to find supported format!"); + } + + [[nodiscard]] vk::Format findDepthFormat() const + { + return findSupportedFormat( + {vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, vk::Format::eD24UnormS8Uint}, + vk::ImageTiling::eOptimal, + vk::FormatFeatureFlagBits::eDepthStencilAttachment); + } + + static bool hasStencilComponent(vk::Format format) + { + return format == vk::Format::eD32SfloatS8Uint || format == vk::Format::eD24UnormS8Uint; + } + + void createTextureImage() + { + // Load KTX2 texture instead of using stb_image + ktxTexture *kTexture; + KTX_error_code result = ktxTexture_CreateFromNamedFile( + TEXTURE_PATH.c_str(), + KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, + &kTexture); + + if (result != KTX_SUCCESS) + { + throw std::runtime_error("failed to load ktx texture image!"); + } + + // Get texture dimensions and data + uint32_t texWidth = kTexture->baseWidth; + uint32_t texHeight = kTexture->baseHeight; + ktx_size_t imageSize = ktxTexture_GetImageSize(kTexture, 0); + ktx_uint8_t *ktxTextureData = ktxTexture_GetData(kTexture); + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, imageSize); + memcpy(data, ktxTextureData, imageSize); + stagingBufferMemory.unmapMemory(); + + // Determine the Vulkan format from KTX format + vk::Format textureFormat; + + // Check if the KTX texture has a format + if (kTexture->classId == ktxTexture2_c) + { + // For KTX2 files, we can get the format directly + auto *ktx2 = reinterpret_cast(kTexture); + textureFormat = static_cast(ktx2->vkFormat); + if (textureFormat == vk::Format::eUndefined) + { + // If the format is undefined, fall back to a reasonable default + textureFormat = vk::Format::eR8G8B8A8Unorm; + } + } + else + { + // For KTX1 files or if we can't determine the format, use a reasonable default + textureFormat = vk::Format::eR8G8B8A8Unorm; + } + + textureImageFormat = textureFormat; + + createImage(texWidth, texHeight, textureFormat, vk::ImageTiling::eOptimal, + vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, + vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory); + + transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal); + copyBufferToImage(stagingBuffer, textureImage, texWidth, texHeight); + transitionImageLayout(textureImage, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal); + + ktxTexture_Destroy(kTexture); + } + + void createTextureImageView() + { + textureImageView = createImageView(textureImage, textureImageFormat, vk::ImageAspectFlagBits::eColor); + } + + void createTextureSampler() + { + vk::PhysicalDeviceProperties properties = physicalDevice.getProperties(); + vk::SamplerCreateInfo samplerInfo{ + .magFilter = vk::Filter::eLinear, + .minFilter = vk::Filter::eLinear, + .mipmapMode = vk::SamplerMipmapMode::eLinear, + .addressModeU = vk::SamplerAddressMode::eRepeat, + .addressModeV = vk::SamplerAddressMode::eRepeat, + .addressModeW = vk::SamplerAddressMode::eRepeat, + .mipLodBias = 0.0f, + .anisotropyEnable = vk::True, + .maxAnisotropy = properties.limits.maxSamplerAnisotropy, + .compareEnable = vk::False, + .compareOp = vk::CompareOp::eAlways}; + textureSampler = vk::raii::Sampler(device, samplerInfo); + } + + vk::raii::ImageView createImageView(vk::raii::Image &image, vk::Format format, vk::ImageAspectFlags aspectFlags) + { + vk::ImageViewCreateInfo viewInfo{ + .image = *image, + .viewType = vk::ImageViewType::e2D, + .format = format, + .subresourceRange = {aspectFlags, 0, 1, 0, 1}}; + return vk::raii::ImageView(device, viewInfo); + } + + void createImage(uint32_t width, uint32_t height, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image &image, vk::raii::DeviceMemory &imageMemory) + { + vk::ImageCreateInfo imageInfo{ + .imageType = vk::ImageType::e2D, + .format = format, + .extent = {width, height, 1}, + .mipLevels = 1, + .arrayLayers = 1, + .samples = vk::SampleCountFlagBits::e1, + .tiling = tiling, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive, + .initialLayout = vk::ImageLayout::eUndefined}; + image = vk::raii::Image(device, imageInfo); + + vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{ + .allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + imageMemory = vk::raii::DeviceMemory(device, allocInfo); + image.bindMemory(*imageMemory, 0); + } + + void transitionImageLayout(const vk::raii::Image &image, vk::ImageLayout oldLayout, vk::ImageLayout newLayout) + { + auto commandBuffer = beginSingleTimeCommands(); + + vk::ImageMemoryBarrier barrier{ + .oldLayout = oldLayout, + .newLayout = newLayout, + .image = *image, + .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + + vk::PipelineStageFlags sourceStage; + vk::PipelineStageFlags destinationStage; + + if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) + { + barrier.srcAccessMask = {}; + barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; + + sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; + destinationStage = vk::PipelineStageFlagBits::eTransfer; + } + else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) + { + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + + sourceStage = vk::PipelineStageFlagBits::eTransfer; + destinationStage = vk::PipelineStageFlagBits::eFragmentShader; + } + else + { + throw std::invalid_argument("unsupported layout transition!"); + } + commandBuffer->pipelineBarrier(sourceStage, destinationStage, {}, {}, nullptr, barrier); + endSingleTimeCommands(*commandBuffer); + } + + void copyBufferToImage(const vk::raii::Buffer &buffer, vk::raii::Image &image, uint32_t width, uint32_t height) + { + std::unique_ptr commandBuffer = beginSingleTimeCommands(); + vk::BufferImageCopy region{ + .bufferOffset = 0, + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource = {vk::ImageAspectFlagBits::eColor, 0, 0, 1}, + .imageOffset = {0, 0, 0}, + .imageExtent = {width, height, 1}}; + commandBuffer->copyBufferToImage(*buffer, *image, vk::ImageLayout::eTransferDstOptimal, {region}); + endSingleTimeCommands(*commandBuffer); + } + + void loadModel() + { + // Use tinygltf to load the model instead of tinyobjloader + tinygltf::Model model; + tinygltf::TinyGLTF loader; + std::string err; + std::string warn; + + bool ret = loader.LoadBinaryFromFile(&model, &err, &warn, MODEL_PATH); + + if (!warn.empty()) + { + std::cout << "glTF warning: " << warn << std::endl; + } + + if (!err.empty()) + { + std::cout << "glTF error: " << err << std::endl; + } + + if (!ret) + { + throw std::runtime_error("Failed to load glTF model"); + } + + vertices.clear(); + indices.clear(); + + // Process all meshes in the model + for (const auto &mesh : model.meshes) + { + for (const auto &primitive : mesh.primitives) + { + // Get indices + const tinygltf::Accessor &indexAccessor = model.accessors[primitive.indices]; + const tinygltf::BufferView &indexBufferView = model.bufferViews[indexAccessor.bufferView]; + const tinygltf::Buffer &indexBuffer = model.buffers[indexBufferView.buffer]; + + // Get vertex positions + const tinygltf::Accessor &posAccessor = model.accessors[primitive.attributes.at("POSITION")]; + const tinygltf::BufferView &posBufferView = model.bufferViews[posAccessor.bufferView]; + const tinygltf::Buffer &posBuffer = model.buffers[posBufferView.buffer]; + + // Get texture coordinates if available + bool hasTexCoords = primitive.attributes.find("TEXCOORD_0") != primitive.attributes.end(); + const tinygltf::Accessor *texCoordAccessor = nullptr; + const tinygltf::BufferView *texCoordBufferView = nullptr; + const tinygltf::Buffer *texCoordBuffer = nullptr; + + if (hasTexCoords) + { + texCoordAccessor = &model.accessors[primitive.attributes.at("TEXCOORD_0")]; + texCoordBufferView = &model.bufferViews[texCoordAccessor->bufferView]; + texCoordBuffer = &model.buffers[texCoordBufferView->buffer]; + } + + uint32_t baseVertex = static_cast(vertices.size()); + + for (size_t i = 0; i < posAccessor.count; i++) + { + Vertex vertex{}; + + const float *pos = reinterpret_cast(&posBuffer.data[posBufferView.byteOffset + posAccessor.byteOffset + i * 12]); + // glTF uses a right-handed coordinate system with Y-up + // Vulkan uses a right-handed coordinate system with Y-down + // We need to flip the Y coordinate + vertex.pos = {pos[0], -pos[1], pos[2]}; + + if (hasTexCoords) + { + const float *texCoord = reinterpret_cast(&texCoordBuffer->data[texCoordBufferView->byteOffset + texCoordAccessor->byteOffset + i * 8]); + vertex.texCoord = {texCoord[0], texCoord[1]}; + } + else + { + vertex.texCoord = {0.0f, 0.0f}; + } + + vertex.color = {1.0f, 1.0f, 1.0f}; + + vertices.push_back(vertex); + } + + const unsigned char *indexData = &indexBuffer.data[indexBufferView.byteOffset + indexAccessor.byteOffset]; + size_t indexCount = indexAccessor.count; + size_t indexStride = 0; + + // Determine index stride based on component type + if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) + { + indexStride = sizeof(uint16_t); + } + else if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT) + { + indexStride = sizeof(uint32_t); + } + else if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) + { + indexStride = sizeof(uint8_t); + } + else + { + throw std::runtime_error("Unsupported index component type"); + } + + indices.reserve(indices.size() + indexCount); + + for (size_t i = 0; i < indexCount; i++) + { + uint32_t index = 0; + + if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) + { + index = *reinterpret_cast(indexData + i * indexStride); + } + else if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT) + { + index = *reinterpret_cast(indexData + i * indexStride); + } + else if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) + { + index = *reinterpret_cast(indexData + i * indexStride); + } + + indices.push_back(baseVertex + index); + } + } + } + } + + void createVertexBuffer() + { + vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(dataStaging, vertices.data(), bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + } + + void createIndexBuffer() + { + vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(data, indices.data(), bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); + + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + } + + void createUniformBuffers() + { + uniformBuffers.clear(); + uniformBuffersMemory.clear(); + uniformBuffersMapped.clear(); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DeviceSize bufferSize = sizeof(UniformBufferObject); + vk::raii::Buffer buffer({}); + vk::raii::DeviceMemory bufferMem({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); + uniformBuffers.emplace_back(std::move(buffer)); + uniformBuffersMemory.emplace_back(std::move(bufferMem)); + uniformBuffersMapped.emplace_back(uniformBuffersMemory[i].mapMemory(0, bufferSize)); + } + } + + void createDescriptorPool() + { + std::array poolSize{ + vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT), + vk::DescriptorPoolSize(vk::DescriptorType::eCombinedImageSampler, MAX_FRAMES_IN_FLIGHT)}; + vk::DescriptorPoolCreateInfo poolInfo{ + .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, + .maxSets = MAX_FRAMES_IN_FLIGHT, + .poolSizeCount = static_cast(poolSize.size()), + .pPoolSizes = poolSize.data()}; + descriptorPool = vk::raii::DescriptorPool(device, poolInfo); + } + + void createDescriptorSets() + { + std::vector layouts(MAX_FRAMES_IN_FLIGHT, *descriptorSetLayout); + vk::DescriptorSetAllocateInfo allocInfo{ + .descriptorPool = *descriptorPool, + .descriptorSetCount = static_cast(layouts.size()), + .pSetLayouts = layouts.data()}; + + descriptorSets.clear(); + descriptorSets = device.allocateDescriptorSets(allocInfo); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DescriptorBufferInfo bufferInfo{ + .buffer = *uniformBuffers[i], + .offset = 0, + .range = sizeof(UniformBufferObject)}; + vk::DescriptorImageInfo imageInfo{ + .sampler = *textureSampler, + .imageView = *textureImageView, + .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal}; + std::array descriptorWrites{ + vk::WriteDescriptorSet{ + .dstSet = *descriptorSets[i], + .dstBinding = 0, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eUniformBuffer, + .pBufferInfo = &bufferInfo}, + vk::WriteDescriptorSet{ + .dstSet = *descriptorSets[i], + .dstBinding = 1, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .pImageInfo = &imageInfo}}; + device.updateDescriptorSets(descriptorWrites, {}); + } + } + + void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer &buffer, vk::raii::DeviceMemory &bufferMemory) + { + vk::BufferCreateInfo bufferInfo{ + .size = size, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive}; + buffer = vk::raii::Buffer(device, bufferInfo); + vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{ + .allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + bufferMemory = vk::raii::DeviceMemory(device, allocInfo); + buffer.bindMemory(*bufferMemory, 0); + } + + std::unique_ptr beginSingleTimeCommands() + { + vk::CommandBufferAllocateInfo allocInfo{ + .commandPool = *commandPool, + .level = vk::CommandBufferLevel::ePrimary, + .commandBufferCount = 1}; + std::unique_ptr commandBuffer = std::make_unique(std::move(vk::raii::CommandBuffers(device, allocInfo).front())); + + vk::CommandBufferBeginInfo beginInfo{ + .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; + commandBuffer->begin(beginInfo); + + return commandBuffer; + } + + void endSingleTimeCommands(const vk::raii::CommandBuffer &commandBuffer) const + { + commandBuffer.end(); + + vk::SubmitInfo submitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandBuffer}; + queue.submit(submitInfo, nullptr); + queue.waitIdle(); + } + + void copyBuffer(vk::raii::Buffer &srcBuffer, vk::raii::Buffer &dstBuffer, vk::DeviceSize size) + { + vk::CommandBufferAllocateInfo allocInfo{.commandPool = *commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1}; + vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); + commandCopyBuffer.begin(vk::CommandBufferBeginInfo{.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); + commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy{.size = size}); + commandCopyBuffer.end(); + queue.submit(vk::SubmitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer}, nullptr); + queue.waitIdle(); + } + + uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) + { + vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) + { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) + { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + void createCommandBuffers() + { + commandBuffers.clear(); + vk::CommandBufferAllocateInfo allocInfo{.commandPool = *commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = MAX_FRAMES_IN_FLIGHT}; + commandBuffers = vk::raii::CommandBuffers(device, allocInfo); + } + + void recordCommandBuffer(uint32_t imageIndex) + { + commandBuffers[currentFrame].begin({}); + transition_image_layout( + imageIndex, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, + vk::AccessFlagBits2::eColorAttachmentWrite, + vk::PipelineStageFlagBits2::eTopOfPipe, + vk::PipelineStageFlagBits2::eColorAttachmentOutput); + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::RenderingAttachmentInfo attachmentInfo = { + .imageView = *swapChainImageViews[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor}; + vk::RenderingInfo renderingInfo = { + .renderArea = {.offset = {0, 0}, .extent = swapChainExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &attachmentInfo}; + commandBuffers[currentFrame].beginRendering(renderingInfo); + commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint32); + commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, *pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); + commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); + commandBuffers[currentFrame].endRendering(); + transition_image_layout( + imageIndex, + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, + {}, + vk::PipelineStageFlagBits2::eColorAttachmentOutput, + vk::PipelineStageFlagBits2::eBottomOfPipe); + commandBuffers[currentFrame].end(); + } + + void transition_image_layout( + uint32_t imageIndex, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout, + vk::AccessFlags2 src_access_mask, + vk::AccessFlags2 dst_access_mask, + vk::PipelineStageFlags2 src_stage_mask, + vk::PipelineStageFlags2 dst_stage_mask) + { + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = src_stage_mask, + .srcAccessMask = src_access_mask, + .dstStageMask = dst_stage_mask, + .dstAccessMask = dst_access_mask, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo dependency_info = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + } + + void createSyncObjects() + { + presentCompleteSemaphore.clear(); + renderFinishedSemaphore.clear(); + inFlightFences.clear(); + + for (size_t i = 0; i < swapChainImages.size(); i++) + { + presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); + } + } + + void updateUniformBuffer(uint32_t currentImage) const + { + static auto startTime = std::chrono::high_resolution_clock::now(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + UniformBufferObject ubo{}; + glm::mat4 initialRotation = glm::rotate(glm::mat4(1.0f), glm::radians(-90.0f), glm::vec3(1.0f, 0.0f, 0.0f)); + glm::mat4 continuousRotation = glm::rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.model = continuousRotation * initialRotation; + ubo.view = lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); + ubo.proj[1][1] *= -1; + + memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); + } + + void drawFrame() + { + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + ; + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[currentFrame], nullptr); + + if (result == vk::Result::eErrorOutOfDateKHR) + { + recreateSwapChain(); + return; + } + if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) + { + throw std::runtime_error("failed to acquire swap chain image!"); + } + updateUniformBuffer(currentFrame); + + device.resetFences(*inFlightFences[currentFrame]); + commandBuffers[currentFrame].reset(); + recordCommandBuffer(imageIndex); + + vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[currentFrame], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[currentFrame]); + + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + result = queue.presentKHR(presentInfoKHR); + if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) + { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != vk::Result::eSuccess) + { + throw std::runtime_error("failed to present swap chain image!"); + } + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size(), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static vk::Format chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + return (availableFormats[0].format == vk::Format::eUndefined) ? vk::Format::eB8G8R8A8Unorm : availableFormats[0].format; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + vk::PresentModeKHR::eFifo; + } + + vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR &capabilities) + { + if (capabilities.currentExtent.width != std::numeric_limits::max()) + { + return capabilities.currentExtent; + } #if PLATFORM_DESKTOP - int width, height; - glfwGetFramebufferSize(window, &width, &height); + int width, height; + glfwGetFramebufferSize(window, &width, &height); #else - ANativeWindow* window = androidAppState.nativeWindow; - int width = ANativeWindow_getWidth(window); - int height = ANativeWindow_getHeight(window); + ANativeWindow *window = androidAppState.nativeWindow; + int width = ANativeWindow_getWidth(window); + int height = ANativeWindow_getHeight(window); #endif - return { - std::clamp(width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width), - std::clamp(height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height) - }; - } + return { + std::clamp(width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width), + std::clamp(height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height)}; + } - [[nodiscard]] std::vector getRequiredExtensions() const { - std::vector extensions; + [[nodiscard]] std::vector getRequiredExtensions() const + { + std::vector extensions; #if PLATFORM_DESKTOP - // Get GLFW extensions - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - extensions.assign(glfwExtensions, glfwExtensions + glfwExtensionCount); + // Get GLFW extensions + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + extensions.assign(glfwExtensions, glfwExtensions + glfwExtensionCount); #else - // Android extensions - extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME); - extensions.push_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME); + // Android extensions + extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME); + extensions.push_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME); #endif - // Add debug extensions if validation layers are enabled - if (enableValidationLayers) { - extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); - } - - return extensions; - } - - [[nodiscard]] bool checkValidationLayerSupport() const { - return (std::ranges::any_of(context.enumerateInstanceLayerProperties(), - []( vk::LayerProperties const & lp ) { return ( strcmp( "VK_LAYER_KHRONOS_validation", lp.layerName ) == 0 ); } ) ); - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - std::vector readFile(const std::string& filename) { + // Add debug extensions if validation layers are enabled + if (enableValidationLayers) + { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + [[nodiscard]] bool checkValidationLayerSupport() const + { + return (std::ranges::any_of(context.enumerateInstanceLayerProperties(), + [](vk::LayerProperties const &lp) { return (strcmp("VK_LAYER_KHRONOS_validation", lp.layerName) == 0); })); + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + std::vector readFile(const std::string &filename) + { #if PLATFORM_ANDROID - // Android asset loading - if (androidAppState.app == nullptr) { - LOGE("Android app not initialized"); - throw std::runtime_error("Android app not initialized"); - } - AAsset* asset = AAssetManager_open(androidAppState.app->activity->assetManager, filename.c_str(), AASSET_MODE_BUFFER); - if (!asset) { - throw std::runtime_error("failed to open file: " + filename); - } - - size_t size = AAsset_getLength(asset); - std::vector buffer(size); - AAsset_read(asset, buffer.data(), size); - AAsset_close(asset); + // Android asset loading + if (androidAppState.app == nullptr) + { + LOGE("Android app not initialized"); + throw std::runtime_error("Android app not initialized"); + } + AAsset *asset = AAssetManager_open(androidAppState.app->activity->assetManager, filename.c_str(), AASSET_MODE_BUFFER); + if (!asset) + { + throw std::runtime_error("failed to open file: " + filename); + } + + size_t size = AAsset_getLength(asset); + std::vector buffer(size); + AAsset_read(asset, buffer.data(), size); + AAsset_close(asset); #else - // Desktop file loading - std::ifstream file(filename, std::ios::ate | std::ios::binary); - if (!file.is_open()) { - throw std::runtime_error("failed to open file: " + filename); - } - - size_t fileSize = static_cast(file.tellg()); - std::vector buffer(fileSize); - file.seekg(0); - file.read(buffer.data(), fileSize); - file.close(); + // Desktop file loading + std::ifstream file(filename, std::ios::ate | std::ios::binary); + if (!file.is_open()) + { + throw std::runtime_error("failed to open file: " + filename); + } + + size_t fileSize = static_cast(file.tellg()); + std::vector buffer(fileSize); + file.seekg(0); + file.read(buffer.data(), fileSize); + file.close(); #endif - return buffer; - } + return buffer; + } }; #if PLATFORM_ANDROID -void android_main(android_app* app) { - app_dummy(); +void android_main(android_app *app) +{ + app_dummy(); - VulkanApplication vulkanApp; - vulkanApp.run(app); + VulkanApplication vulkanApp; + vulkanApp.run(app); } #else -int main() { - try { - VulkanApplication app; - app.run(); - } catch (const std::exception& e) { - LOGE("%s", e.what()); - return EXIT_FAILURE; - } - return EXIT_SUCCESS; +int main() +{ + try + { + VulkanApplication app; + app.run(); + } + catch (const std::exception &e) + { + LOGE("%s", e.what()); + return EXIT_FAILURE; + } + return EXIT_SUCCESS; } #endif diff --git a/attachments/36_multiple_objects.cpp b/attachments/36_multiple_objects.cpp index 85da0542..79c68f8a 100644 --- a/attachments/36_multiple_objects.cpp +++ b/attachments/36_multiple_objects.cpp @@ -1,32 +1,32 @@ -#include -#include -#include -#include -#include -#include -#include #include -#include #include #include +#include +#include +#include +#include +#include +#include #include +#include +#include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include #if defined(__ANDROID__) -#include -#include +# include +# include #endif #include #if defined(__ANDROID__) - #define PLATFORM_ANDROID 1 +# define PLATFORM_ANDROID 1 #else - #define PLATFORM_DESKTOP 1 +# define PLATFORM_DESKTOP 1 #endif // Include tinygltf instead of tinyobjloader @@ -38,34 +38,41 @@ import vulkan_hpp; #include #if PLATFORM_ANDROID - #include - #include - #include - #include - - // Declare and implement app_dummy function from native_app_glue - extern "C" void app_dummy() { - // This is a dummy function that does nothing - // It's used to prevent the linker from stripping out the native_app_glue code - } - - // Define AAssetManager type for Android - typedef AAssetManager AssetManagerType; - - #define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "VulkanTutorial", __VA_ARGS__)) - #define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "VulkanTutorial", __VA_ARGS__)) - #define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "VulkanTutorial", __VA_ARGS__)) +# include +# include +# include +# include + +// Declare and implement app_dummy function from native_app_glue +extern "C" void app_dummy() +{ + // This is a dummy function that does nothing + // It's used to prevent the linker from stripping out the native_app_glue code +} + +// Define AAssetManager type for Android +typedef AAssetManager AssetManagerType; + +# define LOGI(...) ((void) __android_log_print(ANDROID_LOG_INFO, "VulkanTutorial", __VA_ARGS__)) +# define LOGW(...) ((void) __android_log_print(ANDROID_LOG_WARN, "VulkanTutorial", __VA_ARGS__)) +# define LOGE(...) ((void) __android_log_print(ANDROID_LOG_ERROR, "VulkanTutorial", __VA_ARGS__)) #else - // Define AAssetManager type for non-Android platforms - typedef void AssetManagerType; - // Desktop-specific includes - #define GLFW_INCLUDE_VULKAN - #include - - // Define logging macros for Desktop - #define LOGI(...) printf(__VA_ARGS__); printf("\n") - #define LOGW(...) printf(__VA_ARGS__); printf("\n") - #define LOGE(...) fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n") +// Define AAssetManager type for non-Android platforms +typedef void AssetManagerType; +// Desktop-specific includes +# define GLFW_INCLUDE_VULKAN +# include + +// Define logging macros for Desktop +# define LOGI(...) \ + printf(__VA_ARGS__); \ + printf("\n") +# define LOGW(...) \ + printf(__VA_ARGS__); \ + printf("\n") +# define LOGE(...) \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "\n") #endif #define GLM_FORCE_RADIANS @@ -76,48 +83,51 @@ import vulkan_hpp; #include #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; constexpr uint64_t FenceTimeout = 100000000; // Update paths to use glTF model and KTX2 texture -const std::string MODEL_PATH = "models/viking_room.glb"; -const std::string TEXTURE_PATH = "textures/viking_room.ktx2"; -constexpr int MAX_FRAMES_IN_FLIGHT = 2; +const std::string MODEL_PATH = "models/viking_room.glb"; +const std::string TEXTURE_PATH = "textures/viking_room.ktx2"; +constexpr int MAX_FRAMES_IN_FLIGHT = 2; // Define the number of objects to render constexpr int MAX_OBJECTS = 3; // Define VpProfileProperties structure for Android only #if PLATFORM_ANDROID -#ifndef VP_PROFILE_PROPERTIES_DEFINED -#define VP_PROFILE_PROPERTIES_DEFINED -struct VpProfileProperties { - char name[256]; - uint32_t specVersion; +# ifndef VP_PROFILE_PROPERTIES_DEFINED +# define VP_PROFILE_PROPERTIES_DEFINED +struct VpProfileProperties +{ + char name[256]; + uint32_t specVersion; }; -#endif +# endif #endif // Define Vulkan Profile constants #ifndef VP_KHR_ROADMAP_2022_NAME -#define VP_KHR_ROADMAP_2022_NAME "VP_KHR_roadmap_2022" +# define VP_KHR_ROADMAP_2022_NAME "VP_KHR_roadmap_2022" #endif #ifndef VP_KHR_ROADMAP_2022_SPEC_VERSION -#define VP_KHR_ROADMAP_2022_SPEC_VERSION 1 +# define VP_KHR_ROADMAP_2022_SPEC_VERSION 1 #endif -struct AppInfo { - bool profileSupported = false; - VpProfileProperties profile; +struct AppInfo +{ + bool profileSupported = false; + VpProfileProperties profile; }; #if PLATFORM_ANDROID -void android_main(android_app* app); +void android_main(android_app *app); -struct AndroidAppState { - ANativeWindow* nativeWindow = nullptr; - bool initialized = false; - android_app* app = nullptr; +struct AndroidAppState +{ + ANativeWindow *nativeWindow = nullptr; + bool initialized = false; + android_app *app = nullptr; }; #endif @@ -127,1451 +137,1551 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -struct Vertex { - glm::vec3 pos; - glm::vec3 color; - glm::vec2 texCoord; - - static vk::VertexInputBindingDescription getBindingDescription() { - return { 0, sizeof(Vertex), vk::VertexInputRate::eVertex }; - } - - static std::array getAttributeDescriptions() { - return { - vk::VertexInputAttributeDescription( 0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, pos) ), - vk::VertexInputAttributeDescription( 1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color) ), - vk::VertexInputAttributeDescription( 2, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, texCoord) ) - }; - } - - bool operator==(const Vertex& other) const { - return pos == other.pos && color == other.color && texCoord == other.texCoord; - } +struct Vertex +{ + glm::vec3 pos; + glm::vec3 color; + glm::vec2 texCoord; + + static vk::VertexInputBindingDescription getBindingDescription() + { + return {0, sizeof(Vertex), vk::VertexInputRate::eVertex}; + } + + static std::array getAttributeDescriptions() + { + return { + vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, pos)), + vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color)), + vk::VertexInputAttributeDescription(2, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, texCoord))}; + } + + bool operator==(const Vertex &other) const + { + return pos == other.pos && color == other.color && texCoord == other.texCoord; + } }; -template<> struct std::hash { - size_t operator()(Vertex const& vertex) const noexcept { - return ((hash()(vertex.pos) ^ (hash()(vertex.color) << 1)) >> 1) ^ (hash()(vertex.texCoord) << 1); - } +template <> +struct std::hash +{ + size_t operator()(Vertex const &vertex) const noexcept + { + return ((hash()(vertex.pos) ^ (hash()(vertex.color) << 1)) >> 1) ^ (hash()(vertex.texCoord) << 1); + } }; // Define a structure to hold per-object data -struct GameObject { - // Transform properties - glm::vec3 position = {0.0f, 0.0f, 0.0f}; - glm::vec3 rotation = {0.0f, 0.0f, 0.0f}; - glm::vec3 scale = {1.0f, 1.0f, 1.0f}; - - // Uniform buffer for this object (one per frame in flight) - std::vector uniformBuffers; - std::vector uniformBuffersMemory; - std::vector uniformBuffersMapped; - - // Descriptor sets for this object (one per frame in flight) - std::vector descriptorSets; - - // Calculate model matrix based on position, rotation, and scale - glm::mat4 getModelMatrix() const { - glm::mat4 model = glm::mat4(1.0f); - model = glm::translate(model, position); - model = glm::rotate(model, rotation.x, glm::vec3(1.0f, 0.0f, 0.0f)); - model = glm::rotate(model, rotation.y, glm::vec3(0.0f, 1.0f, 0.0f)); - model = glm::rotate(model, rotation.z, glm::vec3(0.0f, 0.0f, 1.0f)); - model = glm::scale(model, scale); - return model; - } +struct GameObject +{ + // Transform properties + glm::vec3 position = {0.0f, 0.0f, 0.0f}; + glm::vec3 rotation = {0.0f, 0.0f, 0.0f}; + glm::vec3 scale = {1.0f, 1.0f, 1.0f}; + + // Uniform buffer for this object (one per frame in flight) + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + std::vector uniformBuffersMapped; + + // Descriptor sets for this object (one per frame in flight) + std::vector descriptorSets; + + // Calculate model matrix based on position, rotation, and scale + glm::mat4 getModelMatrix() const + { + glm::mat4 model = glm::mat4(1.0f); + model = glm::translate(model, position); + model = glm::rotate(model, rotation.x, glm::vec3(1.0f, 0.0f, 0.0f)); + model = glm::rotate(model, rotation.y, glm::vec3(0.0f, 1.0f, 0.0f)); + model = glm::rotate(model, rotation.z, glm::vec3(0.0f, 0.0f, 1.0f)); + model = glm::scale(model, scale); + return model; + } }; -struct UniformBufferObject { - alignas(16) glm::mat4 model; - alignas(16) glm::mat4 view; - alignas(16) glm::mat4 proj; +struct UniformBufferObject +{ + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; }; -class VulkanApplication { -public: +class VulkanApplication +{ + public: #if PLATFORM_ANDROID - void cleanupAndroid() { - // Clean up resources in each GameObject - for (auto& gameObject : gameObjects) { - // Unmap memory - for (size_t i = 0; i < gameObject.uniformBuffersMemory.size(); i++) { - if (gameObject.uniformBuffersMapped[i] != nullptr) { - gameObject.uniformBuffersMemory[i].unmapMemory(); - } - } - - // Clear vectors to release resources - gameObject.uniformBuffers.clear(); - gameObject.uniformBuffersMemory.clear(); - gameObject.uniformBuffersMapped.clear(); - gameObject.descriptorSets.clear(); - } - } - - void run(android_app* app) { - androidAppState.nativeWindow = app->window; - androidAppState.app = app; - app->userData = &androidAppState; - app->onAppCmd = handleAppCommand; - // Note: onInputEvent is no longer a member of android_app in the current NDK version - // Input events are now handled differently - - int events; - android_poll_source* source; - - while (app->destroyRequested == 0) { - while (ALooper_pollOnce(androidAppState.initialized ? 0 : -1, nullptr, &events, (void**)&source) >= 0) { - if (source != nullptr) { - source->process(app, source); - } - } - - if (androidAppState.initialized && androidAppState.nativeWindow != nullptr) { - drawFrame(); - } - } - - if (androidAppState.initialized) { - device.waitIdle(); - cleanupAndroid(); - } - } + void cleanupAndroid() + { + // Clean up resources in each GameObject + for (auto &gameObject : gameObjects) + { + // Unmap memory + for (size_t i = 0; i < gameObject.uniformBuffersMemory.size(); i++) + { + if (gameObject.uniformBuffersMapped[i] != nullptr) + { + gameObject.uniformBuffersMemory[i].unmapMemory(); + } + } + + // Clear vectors to release resources + gameObject.uniformBuffers.clear(); + gameObject.uniformBuffersMemory.clear(); + gameObject.uniformBuffersMapped.clear(); + gameObject.descriptorSets.clear(); + } + } + + void run(android_app *app) + { + androidAppState.nativeWindow = app->window; + androidAppState.app = app; + app->userData = &androidAppState; + app->onAppCmd = handleAppCommand; + // Note: onInputEvent is no longer a member of android_app in the current NDK version + // Input events are now handled differently + + int events; + android_poll_source *source; + + while (app->destroyRequested == 0) + { + while (ALooper_pollOnce(androidAppState.initialized ? 0 : -1, nullptr, &events, (void **) &source) >= 0) + { + if (source != nullptr) + { + source->process(app, source); + } + } + + if (androidAppState.initialized && androidAppState.nativeWindow != nullptr) + { + drawFrame(); + } + } + + if (androidAppState.initialized) + { + device.waitIdle(); + cleanupAndroid(); + } + } #else - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } + void run() + { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } #endif -private: + private: #if PLATFORM_ANDROID - AndroidAppState androidAppState; - - static void handleAppCommand(android_app* app, int32_t cmd) { - auto* appState = static_cast(app->userData); - - switch (cmd) { - case APP_CMD_INIT_WINDOW: - if (app->window != nullptr) { - appState->nativeWindow = app->window; - // We can't cast AndroidAppState to VulkanApplication directly - // Instead, we need to access the VulkanApplication instance through a global variable - // or another mechanism. For now, we'll just set the initialized flag. - appState->initialized = true; - } - break; - case APP_CMD_TERM_WINDOW: - appState->nativeWindow = nullptr; - break; - default: - break; - } - } - - static int32_t handleInputEvent(android_app* app, AInputEvent* event) { - if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) { - float x = AMotionEvent_getX(event, 0); - float y = AMotionEvent_getY(event, 0); - - LOGI("Touch at: %f, %f", x, y); - - return 1; - } - return 0; - } + AndroidAppState androidAppState; + + static void handleAppCommand(android_app *app, int32_t cmd) + { + auto *appState = static_cast(app->userData); + + switch (cmd) + { + case APP_CMD_INIT_WINDOW: + if (app->window != nullptr) + { + appState->nativeWindow = app->window; + // We can't cast AndroidAppState to VulkanApplication directly + // Instead, we need to access the VulkanApplication instance through a global variable + // or another mechanism. For now, we'll just set the initialized flag. + appState->initialized = true; + } + break; + case APP_CMD_TERM_WINDOW: + appState->nativeWindow = nullptr; + break; + default: + break; + } + } + + static int32_t handleInputEvent(android_app *app, AInputEvent *event) + { + if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) + { + float x = AMotionEvent_getX(event, 0); + float y = AMotionEvent_getY(event, 0); + + LOGI("Touch at: %f, %f", x, y); + + return 1; + } + return 0; + } #else - GLFWwindow* window = nullptr; + GLFWwindow *window = nullptr; #endif - AppInfo appInfo = {}; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::Format swapChainImageFormat = vk::Format::eUndefined; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - - vk::raii::Image depthImage = nullptr; - vk::raii::DeviceMemory depthImageMemory = nullptr; - vk::raii::ImageView depthImageView = nullptr; - - vk::raii::Image textureImage = nullptr; - vk::raii::DeviceMemory textureImageMemory = nullptr; - vk::raii::ImageView textureImageView = nullptr; - vk::raii::Sampler textureSampler = nullptr; - vk::Format textureImageFormat = vk::Format::eUndefined; - - std::vector vertices; - std::vector indices; - vk::raii::Buffer vertexBuffer = nullptr; - vk::raii::DeviceMemory vertexBufferMemory = nullptr; - vk::raii::Buffer indexBuffer = nullptr; - vk::raii::DeviceMemory indexBufferMemory = nullptr; - - // Array of game objects to render - std::array gameObjects; - - vk::raii::DescriptorPool descriptorPool = nullptr; - - vk::raii::CommandPool commandPool = nullptr; - std::vector commandBuffers; - - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; - std::vector inFlightFences; - uint32_t currentFrame = 0; - - bool framebufferResized = false; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; + AppInfo appInfo = {}; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::Format swapChainImageFormat = vk::Format::eUndefined; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + + vk::raii::Image depthImage = nullptr; + vk::raii::DeviceMemory depthImageMemory = nullptr; + vk::raii::ImageView depthImageView = nullptr; + + vk::raii::Image textureImage = nullptr; + vk::raii::DeviceMemory textureImageMemory = nullptr; + vk::raii::ImageView textureImageView = nullptr; + vk::raii::Sampler textureSampler = nullptr; + vk::Format textureImageFormat = vk::Format::eUndefined; + + std::vector vertices; + std::vector indices; + vk::raii::Buffer vertexBuffer = nullptr; + vk::raii::DeviceMemory vertexBufferMemory = nullptr; + vk::raii::Buffer indexBuffer = nullptr; + vk::raii::DeviceMemory indexBufferMemory = nullptr; + + // Array of game objects to render + std::array gameObjects; + + vk::raii::DescriptorPool descriptorPool = nullptr; + + vk::raii::CommandPool commandPool = nullptr; + std::vector commandBuffers; + + std::vector presentCompleteSemaphore; + std::vector renderFinishedSemaphore; + std::vector inFlightFences; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; #if PLATFORM_DESKTOP - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); - } - - static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { - auto app = static_cast(glfwGetWindowUserPointer(window)); - app->framebufferResized = true; - } + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow *window, int width, int height) + { + auto app = static_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } #endif -public: - void initVulkan() { - createInstance(); - setupDebugMessenger(); - createSurface(); - pickPhysicalDevice(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createDescriptorSetLayout(); - createGraphicsPipeline(); - createCommandPool(); - createDepthResources(); - createTextureImage(); - createTextureImageView(); - createTextureSampler(); - loadModel(); - createVertexBuffer(); - createIndexBuffer(); - setupGameObjects(); - createUniformBuffers(); - createDescriptorPool(); - createDescriptorSets(); - createCommandBuffers(); - createSyncObjects(); - } - -private: - + public: + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createDescriptorSetLayout(); + createGraphicsPipeline(); + createCommandPool(); + createDepthResources(); + createTextureImage(); + createTextureImageView(); + createTextureSampler(); + loadModel(); + createVertexBuffer(); + createIndexBuffer(); + setupGameObjects(); + createUniformBuffers(); + createDescriptorPool(); + createDescriptorSets(); + createCommandBuffers(); + createSyncObjects(); + } + + private: #if PLATFORM_DESKTOP - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - drawFrame(); - } - - device.waitIdle(); - } + void mainLoop() + { + while (!glfwWindowShouldClose(window)) + { + glfwPollEvents(); + drawFrame(); + } + + device.waitIdle(); + } #endif - void cleanupSwapChain() { - swapChainImageViews.clear(); - swapChain = nullptr; - } + void cleanupSwapChain() + { + swapChainImageViews.clear(); + swapChain = nullptr; + } #if PLATFORM_DESKTOP - void cleanup() { - // Clean up resources in each GameObject - for (auto& gameObject : gameObjects) { - // Unmap memory - for (size_t i = 0; i < gameObject.uniformBuffersMemory.size(); i++) { - if (gameObject.uniformBuffersMapped[i] != nullptr) { - gameObject.uniformBuffersMemory[i].unmapMemory(); - } - } - - // Clear vectors to release resources - gameObject.uniformBuffers.clear(); - gameObject.uniformBuffersMemory.clear(); - gameObject.uniformBuffersMapped.clear(); - gameObject.descriptorSets.clear(); - } - - // Clean up GLFW resources - glfwDestroyWindow(window); - glfwTerminate(); - } + void cleanup() + { + // Clean up resources in each GameObject + for (auto &gameObject : gameObjects) + { + // Unmap memory + for (size_t i = 0; i < gameObject.uniformBuffersMemory.size(); i++) + { + if (gameObject.uniformBuffersMapped[i] != nullptr) + { + gameObject.uniformBuffersMemory[i].unmapMemory(); + } + } + + // Clear vectors to release resources + gameObject.uniformBuffers.clear(); + gameObject.uniformBuffersMemory.clear(); + gameObject.uniformBuffersMapped.clear(); + gameObject.descriptorSets.clear(); + } + + // Clean up GLFW resources + glfwDestroyWindow(window); + glfwTerminate(); + } #endif - void recreateSwapChain() { + void recreateSwapChain() + { #if PLATFORM_DESKTOP - int width = 0, height = 0; - glfwGetFramebufferSize(window, &width, &height); - while (width == 0 || height == 0) { - glfwGetFramebufferSize(window, &width, &height); - glfwWaitEvents(); - } + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) + { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } #endif - device.waitIdle(); - - cleanupSwapChain(); - createSwapChain(); - createImageViews(); - createDepthResources(); - } - - 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_API_VERSION_1_3 - }; - - auto extensions = getRequiredExtensions(); - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledExtensionCount = static_cast(extensions.size()), - .ppEnabledExtensionNames = extensions.data() - }; - - instance = vk::raii::Instance(context, createInfo); - LOGI("Vulkan instance created"); - } - - void setupDebugMessenger() { - // Debug messenger setup is disabled for now to avoid compatibility issues - // This is a simplified approach to get the code compiling - if (!enableValidationLayers) return; - - LOGI("Debug messenger setup skipped for compatibility"); - } - - void createSurface() { + device.waitIdle(); + + cleanupSwapChain(); + createSwapChain(); + createImageViews(); + createDepthResources(); + } + + 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_API_VERSION_1_3}; + + auto extensions = getRequiredExtensions(); + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledExtensionCount = static_cast(extensions.size()), + .ppEnabledExtensionNames = extensions.data()}; + + instance = vk::raii::Instance(context, createInfo); + LOGI("Vulkan instance created"); + } + + void setupDebugMessenger() + { + // Debug messenger setup is disabled for now to avoid compatibility issues + // This is a simplified approach to get the code compiling + if (!enableValidationLayers) + return; + + LOGI("Debug messenger setup skipped for compatibility"); + } + + void createSurface() + { #if PLATFORM_DESKTOP - VkSurfaceKHR _surface; - if (glfwCreateWindowSurface(*instance, window, nullptr, &_surface) != VK_SUCCESS) { - throw std::runtime_error("failed to create window surface!"); - } - surface = vk::raii::SurfaceKHR(instance, _surface); + VkSurfaceKHR _surface; + if (glfwCreateWindowSurface(*instance, window, nullptr, &_surface) != VK_SUCCESS) + { + throw std::runtime_error("failed to create window surface!"); + } + surface = vk::raii::SurfaceKHR(instance, _surface); #else - VkSurfaceKHR _surface; - VkAndroidSurfaceCreateInfoKHR createInfo{ - .sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR, - .window = androidAppState.nativeWindow - }; - if (vkCreateAndroidSurfaceKHR(*instance, &createInfo, nullptr, &_surface) != VK_SUCCESS) { - throw std::runtime_error("failed to create Android surface!"); - } - surface = vk::raii::SurfaceKHR(instance, _surface); + VkSurfaceKHR _surface; + VkAndroidSurfaceCreateInfoKHR createInfo{ + .sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR, + .window = androidAppState.nativeWindow}; + if (vkCreateAndroidSurfaceKHR(*instance, &createInfo, nullptr, &_surface) != VK_SUCCESS) + { + throw std::runtime_error("failed to create Android surface!"); + } + surface = vk::raii::SurfaceKHR(instance, _surface); #endif - } + } - void pickPhysicalDevice() { - std::vector devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( + void pickPhysicalDevice() + { + std::vector devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( devices, - [&](auto const& device) { + [&](auto const &device) { // Check if the device supports the Vulkan 1.3 API version bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; // Check if any of the queue families support graphics operations auto queueFamilies = device.getQueueFamilyProperties(); bool supportsGraphics = - std::ranges::any_of(queueFamilies, [](auto const& qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); // Check if all required device extensions are available auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); bool supportsAllRequiredExtensions = std::ranges::all_of(requiredDeviceExtension, - [&availableDeviceExtensions](auto const& requiredDeviceExtension) { - return std::ranges::any_of(availableDeviceExtensions, - [requiredDeviceExtension](auto const& availableDeviceExtension) { - return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; - }); - }); - - auto features = device.template getFeatures2(); + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { + return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; + }); + }); + + auto features = device.template getFeatures2(); bool supportsRequiredFeatures = features.template get().dynamicRendering && - features.template get().extendedDynamicState; + features.template get().extendedDynamicState; return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; }); - if (devIter != devices.end()) { - physicalDevice = *devIter; + if (devIter != devices.end()) + { + physicalDevice = *devIter; - // Check for Vulkan profile support - VpProfileProperties profileProperties; + // Check for Vulkan profile support + VpProfileProperties profileProperties; #if PLATFORM_ANDROID - strcpy(profileProperties.name, VP_KHR_ROADMAP_2022_NAME); + strcpy(profileProperties.name, VP_KHR_ROADMAP_2022_NAME); #else - strcpy(profileProperties.profileName, VP_KHR_ROADMAP_2022_NAME); + strcpy(profileProperties.profileName, VP_KHR_ROADMAP_2022_NAME); #endif - profileProperties.specVersion = VP_KHR_ROADMAP_2022_SPEC_VERSION; + profileProperties.specVersion = VP_KHR_ROADMAP_2022_SPEC_VERSION; - VkBool32 supported = VK_FALSE; - bool result = false; + VkBool32 supported = VK_FALSE; + bool result = false; #if PLATFORM_ANDROID - // Create a vp::ProfileDesc from our VpProfileProperties - vp::ProfileDesc profileDesc = { - profileProperties.name, - profileProperties.specVersion - }; - - // Use vp::GetProfileSupport for Android - result = vp::GetProfileSupport( - *physicalDevice, // Pass the physical device directly - &profileDesc, // Pass the profile description - &supported // Output parameter for support status - ); + // Create a vp::ProfileDesc from our VpProfileProperties + vp::ProfileDesc profileDesc = { + profileProperties.name, + profileProperties.specVersion}; + + // Use vp::GetProfileSupport for Android + result = vp::GetProfileSupport( + *physicalDevice, // Pass the physical device directly + &profileDesc, // Pass the profile description + &supported // Output parameter for support status + ); #else - // Use vpGetPhysicalDeviceProfileSupport for Desktop - VkResult vk_result = vpGetPhysicalDeviceProfileSupport( - *instance, - *physicalDevice, - &profileProperties, - &supported - ); - result = vk_result == static_cast(vk::Result::eSuccess); + // Use vpGetPhysicalDeviceProfileSupport for Desktop + VkResult vk_result = vpGetPhysicalDeviceProfileSupport( + *instance, + *physicalDevice, + &profileProperties, + &supported); + result = vk_result == static_cast(vk::Result::eSuccess); #endif - const char* name = nullptr; + const char *name = nullptr; #ifdef PLATFORM_ANDROID - name = profileProperties.name; + name = profileProperties.name; #else - name = profileProperties.profileName; + name = profileProperties.profileName; #endif - if (result && supported == VK_TRUE) { - appInfo.profileSupported = true; - appInfo.profile = profileProperties; - LOGI("Device supports Vulkan profile: %s", name); - } else { - LOGI("Device does not support Vulkan profile: %s", name); - } - } else { - throw std::runtime_error("failed to find a suitable GPU!"); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - auto features = physicalDevice.getFeatures2(); - vk::PhysicalDeviceVulkan13Features vulkan13Features; - vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT extendedDynamicStateFeatures; - vulkan13Features.dynamicRendering = vk::True; - vulkan13Features.synchronization2 = vk::True; - extendedDynamicStateFeatures.extendedDynamicState = vk::True; - vulkan13Features.pNext = &extendedDynamicStateFeatures; - features.pNext = &vulkan13Features; - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo { .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ - .pNext = &features, - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() - }; - - // Create the device with the appropriate features - device = vk::raii::Device(physicalDevice, deviceCreateInfo); - - queue = vk::raii::Queue(device, queueIndex, 0); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(*surface); - swapChainImageFormat = 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{ - .surface = *surface, .minImageCount = minImageCount, - .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, - .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 }; - - swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); - swapChainImages = swapChain.getImages(); - } - - void createImageViews() { - 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( device, imageViewCreateInfo ); - } - } - - void createDescriptorSetLayout() { - std::array bindings = { - vk::DescriptorSetLayoutBinding( 0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr), - vk::DescriptorSetLayoutBinding( 1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment, nullptr) - }; - - vk::DescriptorSetLayoutCreateInfo layoutInfo{ .bindingCount = static_cast(bindings.size()), .pBindings = bindings.data() }; - descriptorSetLayout = vk::raii::DescriptorSetLayout(device, layoutInfo); - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(this->readFile("shaders/slang.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}; - - auto bindingDescription = Vertex::getBindingDescription(); - auto attributeDescriptions = Vertex::getAttributeDescriptions(); - vk::PipelineVertexInputStateCreateInfo vertexInputInfo{ - .vertexBindingDescriptionCount = 1, - .pVertexBindingDescriptions = &bindingDescription, - .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), - .pVertexAttributeDescriptions = attributeDescriptions.data() - }; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ - .topology = vk::PrimitiveTopology::eTriangleList, - .primitiveRestartEnable = vk::False - }; - vk::PipelineViewportStateCreateInfo viewportState{ - .viewportCount = 1, - .scissorCount = 1 - }; - vk::PipelineRasterizationStateCreateInfo rasterizer{ - .depthClampEnable = vk::False, - .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, - .cullMode = vk::CullModeFlagBits::eBack, // Re-enabled culling for better performance - .frontFace = vk::FrontFace::eClockwise, // Keeping Clockwise for glTF - .depthBiasEnable = vk::False - }; - rasterizer.lineWidth = 1.0f; - vk::PipelineMultisampleStateCreateInfo multisampling{ - .rasterizationSamples = vk::SampleCountFlagBits::e1, - .sampleShadingEnable = vk::False - }; - vk::PipelineDepthStencilStateCreateInfo depthStencil{ - .depthTestEnable = vk::True, - .depthWriteEnable = vk::True, - .depthCompareOp = vk::CompareOp::eLess, - .depthBoundsTestEnable = vk::False, - .stencilTestEnable = vk::False - }; - vk::PipelineColorBlendAttachmentState colorBlendAttachment; - colorBlendAttachment.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA; - colorBlendAttachment.blendEnable = vk::False; - - vk::PipelineColorBlendStateCreateInfo colorBlending{ - .logicOpEnable = vk::False, - .logicOp = vk::LogicOp::eCopy, - .attachmentCount = 1, - .pAttachments = &colorBlendAttachment - }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0 }; - - pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); - - vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ .colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainImageFormat }; - vk::GraphicsPipelineCreateInfo pipelineInfo{ .pNext = &pipelineRenderingCreateInfo, - .stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pDepthStencilState = &depthStencil, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = *pipelineLayout, - .renderPass = nullptr - }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{ - .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex - }; - commandPool = vk::raii::CommandPool(device, poolInfo); - } - - void createDepthResources() { - vk::Format depthFormat = findDepthFormat(); - - createImage(swapChainExtent.width, swapChainExtent.height, depthFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eDepthStencilAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, depthImage, depthImageMemory); - depthImageView = createImageView(depthImage, depthFormat, vk::ImageAspectFlagBits::eDepth); - } - - vk::Format findSupportedFormat(const std::vector& candidates, vk::ImageTiling tiling, vk::FormatFeatureFlags features) const { - for (const auto format : candidates) { - vk::FormatProperties props = physicalDevice.getFormatProperties(format); - - if (tiling == vk::ImageTiling::eLinear && (props.linearTilingFeatures & features) == features) { - return format; - } - if (tiling == vk::ImageTiling::eOptimal && (props.optimalTilingFeatures & features) == features) { - return format; - } - } - - throw std::runtime_error("failed to find supported format!"); - } - - [[nodiscard]] vk::Format findDepthFormat() const { - return findSupportedFormat( - {vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, vk::Format::eD24UnormS8Uint}, - vk::ImageTiling::eOptimal, - vk::FormatFeatureFlagBits::eDepthStencilAttachment - ); - } - - static bool hasStencilComponent(vk::Format format) { - return format == vk::Format::eD32SfloatS8Uint || format == vk::Format::eD24UnormS8Uint; - } - - void createTextureImage() { - // Load KTX2 texture instead of using stb_image - ktxTexture* kTexture; - KTX_error_code result = ktxTexture_CreateFromNamedFile( - TEXTURE_PATH.c_str(), - KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, - &kTexture); - - if (result != KTX_SUCCESS) { - throw std::runtime_error("failed to load ktx texture image!"); - } - - // Get texture dimensions and data - uint32_t texWidth = kTexture->baseWidth; - uint32_t texHeight = kTexture->baseHeight; - ktx_size_t imageSize = ktxTexture_GetImageSize(kTexture, 0); - ktx_uint8_t* ktxTextureData = ktxTexture_GetData(kTexture); - - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, imageSize); - memcpy(data, ktxTextureData, imageSize); - stagingBufferMemory.unmapMemory(); - - // Determine the Vulkan format from KTX format - vk::Format textureFormat; - - // Check if the KTX texture has a format - if (kTexture->classId == ktxTexture2_c) { - // For KTX2 files, we can get the format directly - auto* ktx2 = reinterpret_cast(kTexture); - textureFormat = static_cast(ktx2->vkFormat); - if (textureFormat == vk::Format::eUndefined) { - // If the format is undefined, fall back to a reasonable default - textureFormat = vk::Format::eR8G8B8A8Unorm; - } - } else { - // For KTX1 files or if we can't determine the format, use a reasonable default - textureFormat = vk::Format::eR8G8B8A8Unorm; - } - - textureImageFormat = textureFormat; - - createImage(texWidth, texHeight, textureFormat, vk::ImageTiling::eOptimal, - vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, - vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory); - - transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal); - copyBufferToImage(stagingBuffer, textureImage, texWidth, texHeight); - transitionImageLayout(textureImage, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal); - - ktxTexture_Destroy(kTexture); - } - - void createTextureImageView() { - textureImageView = createImageView(textureImage, textureImageFormat, vk::ImageAspectFlagBits::eColor); - } - - void createTextureSampler() { - vk::PhysicalDeviceProperties properties = physicalDevice.getProperties(); - vk::SamplerCreateInfo samplerInfo{ - .magFilter = vk::Filter::eLinear, - .minFilter = vk::Filter::eLinear, - .mipmapMode = vk::SamplerMipmapMode::eLinear, - .addressModeU = vk::SamplerAddressMode::eRepeat, - .addressModeV = vk::SamplerAddressMode::eRepeat, - .addressModeW = vk::SamplerAddressMode::eRepeat, - .mipLodBias = 0.0f, - .anisotropyEnable = vk::True, - .maxAnisotropy = properties.limits.maxSamplerAnisotropy, - .compareEnable = vk::False, - .compareOp = vk::CompareOp::eAlways - }; - textureSampler = vk::raii::Sampler(device, samplerInfo); - } - - vk::raii::ImageView createImageView(vk::raii::Image& image, vk::Format format, vk::ImageAspectFlags aspectFlags) { - vk::ImageViewCreateInfo viewInfo{ - .image = *image, - .viewType = vk::ImageViewType::e2D, - .format = format, - .subresourceRange = { aspectFlags, 0, 1, 0, 1 } - }; - return vk::raii::ImageView(device, viewInfo); - } - - void createImage(uint32_t width, uint32_t height, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image& image, vk::raii::DeviceMemory& imageMemory) { - vk::ImageCreateInfo imageInfo{ - .imageType = vk::ImageType::e2D, - .format = format, - .extent = {width, height, 1}, - .mipLevels = 1, - .arrayLayers = 1, - .samples = vk::SampleCountFlagBits::e1, - .tiling = tiling, - .usage = usage, - .sharingMode = vk::SharingMode::eExclusive, - .initialLayout = vk::ImageLayout::eUndefined - }; - image = vk::raii::Image(device, imageInfo); - - vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ - .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) - }; - imageMemory = vk::raii::DeviceMemory(device, allocInfo); - image.bindMemory(*imageMemory, 0); - } - - void transitionImageLayout(const vk::raii::Image& image, vk::ImageLayout oldLayout, vk::ImageLayout newLayout) { - auto commandBuffer = beginSingleTimeCommands(); - - vk::ImageMemoryBarrier barrier{ - .oldLayout = oldLayout, - .newLayout = newLayout, - .image = *image, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } - }; - - vk::PipelineStageFlags sourceStage; - vk::PipelineStageFlags destinationStage; - - if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) { - barrier.srcAccessMask = {}; - barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; - - sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; - destinationStage = vk::PipelineStageFlagBits::eTransfer; - } else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) { - barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; - barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - - sourceStage = vk::PipelineStageFlagBits::eTransfer; - destinationStage = vk::PipelineStageFlagBits::eFragmentShader; - } else { - throw std::invalid_argument("unsupported layout transition!"); - } - commandBuffer->pipelineBarrier( sourceStage, destinationStage, {}, {}, nullptr, barrier ); - endSingleTimeCommands(*commandBuffer); - } - - void copyBufferToImage(const vk::raii::Buffer& buffer, vk::raii::Image& image, uint32_t width, uint32_t height) { - std::unique_ptr commandBuffer = beginSingleTimeCommands(); - vk::BufferImageCopy region{ - .bufferOffset = 0, - .bufferRowLength = 0, - .bufferImageHeight = 0, - .imageSubresource = { vk::ImageAspectFlagBits::eColor, 0, 0, 1 }, - .imageOffset = {0, 0, 0}, - .imageExtent = {width, height, 1} - }; - commandBuffer->copyBufferToImage(*buffer, *image, vk::ImageLayout::eTransferDstOptimal, {region}); - endSingleTimeCommands(*commandBuffer); - } - - void loadModel() { - // Use tinygltf to load the model instead of tinyobjloader - tinygltf::Model model; - tinygltf::TinyGLTF loader; - std::string err; - std::string warn; - - bool ret = loader.LoadBinaryFromFile(&model, &err, &warn, MODEL_PATH); - - if (!warn.empty()) { - std::cout << "glTF warning: " << warn << std::endl; - } - - if (!err.empty()) { - std::cout << "glTF error: " << err << std::endl; - } - - if (!ret) { - throw std::runtime_error("Failed to load glTF model"); - } - - vertices.clear(); - indices.clear(); - - // Process all meshes in the model - for (const auto& mesh : model.meshes) { - for (const auto& primitive : mesh.primitives) { - // Get indices - const tinygltf::Accessor& indexAccessor = model.accessors[primitive.indices]; - const tinygltf::BufferView& indexBufferView = model.bufferViews[indexAccessor.bufferView]; - const tinygltf::Buffer& indexBuffer = model.buffers[indexBufferView.buffer]; - - // Get vertex positions - const tinygltf::Accessor& posAccessor = model.accessors[primitive.attributes.at("POSITION")]; - const tinygltf::BufferView& posBufferView = model.bufferViews[posAccessor.bufferView]; - const tinygltf::Buffer& posBuffer = model.buffers[posBufferView.buffer]; - - // Get texture coordinates if available - bool hasTexCoords = primitive.attributes.find("TEXCOORD_0") != primitive.attributes.end(); - const tinygltf::Accessor* texCoordAccessor = nullptr; - const tinygltf::BufferView* texCoordBufferView = nullptr; - const tinygltf::Buffer* texCoordBuffer = nullptr; - - if (hasTexCoords) { - texCoordAccessor = &model.accessors[primitive.attributes.at("TEXCOORD_0")]; - texCoordBufferView = &model.bufferViews[texCoordAccessor->bufferView]; - texCoordBuffer = &model.buffers[texCoordBufferView->buffer]; - } - - uint32_t baseVertex = static_cast(vertices.size()); - - for (size_t i = 0; i < posAccessor.count; i++) { - Vertex vertex{}; - - const float* pos = reinterpret_cast(&posBuffer.data[posBufferView.byteOffset + posAccessor.byteOffset + i * 12]); - // glTF uses a right-handed coordinate system with Y-up - // Vulkan uses a right-handed coordinate system with Y-down - // We need to flip the Y coordinate - vertex.pos = {pos[0], -pos[1], pos[2]}; - - if (hasTexCoords) { - const float* texCoord = reinterpret_cast(&texCoordBuffer->data[texCoordBufferView->byteOffset + texCoordAccessor->byteOffset + i * 8]); - vertex.texCoord = {texCoord[0], texCoord[1]}; - } else { - vertex.texCoord = {0.0f, 0.0f}; - } - - vertex.color = {1.0f, 1.0f, 1.0f}; - - vertices.push_back(vertex); - } - - const unsigned char* indexData = &indexBuffer.data[indexBufferView.byteOffset + indexAccessor.byteOffset]; - size_t indexCount = indexAccessor.count; - size_t indexStride = 0; - - // Determine index stride based on component type - if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) { - indexStride = sizeof(uint16_t); - } else if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT) { - indexStride = sizeof(uint32_t); - } else if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) { - indexStride = sizeof(uint8_t); - } else { - throw std::runtime_error("Unsupported index component type"); - } - - indices.reserve(indices.size() + indexCount); - - for (size_t i = 0; i < indexCount; i++) { - uint32_t index = 0; - - if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) { - index = *reinterpret_cast(indexData + i * indexStride); - } else if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT) { - index = *reinterpret_cast(indexData + i * indexStride); - } else if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) { - index = *reinterpret_cast(indexData + i * indexStride); - } - - indices.push_back(baseVertex + index); - } - } - } - } - - void createVertexBuffer() { - vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(dataStaging, vertices.data(), bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); - - copyBuffer(stagingBuffer, vertexBuffer, bufferSize); - } - - void createIndexBuffer() { - vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); - - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(data, indices.data(), bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); - - copyBuffer(stagingBuffer, indexBuffer, bufferSize); - } - - // Initialize the game objects with different positions, rotations, and scales - void setupGameObjects() { - // Object 1 - Center - gameObjects[0].position = {0.0f, 0.0f, 0.0f}; - gameObjects[0].rotation = {0.0f, 0.0f, 0.0f}; - gameObjects[0].scale = {1.0f, 1.0f, 1.0f}; - - // Object 2 - Left - gameObjects[1].position = {-2.0f, 0.0f, -1.0f}; - gameObjects[1].rotation = {0.0f, glm::radians(45.0f), 0.0f}; - gameObjects[1].scale = {0.75f, 0.75f, 0.75f}; - - // Object 3 - Right - gameObjects[2].position = {2.0f, 0.0f, -1.0f}; - gameObjects[2].rotation = {0.0f, glm::radians(-45.0f), 0.0f}; - gameObjects[2].scale = {0.75f, 0.75f, 0.75f}; - } - - // Create uniform buffers for each object - void createUniformBuffers() { - // For each game object - for (auto& gameObject : gameObjects) { - gameObject.uniformBuffers.clear(); - gameObject.uniformBuffersMemory.clear(); - gameObject.uniformBuffersMapped.clear(); - - // Create uniform buffers for each frame in flight - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DeviceSize bufferSize = sizeof(UniformBufferObject); - vk::raii::Buffer buffer({}); - vk::raii::DeviceMemory bufferMem({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); - gameObject.uniformBuffers.emplace_back(std::move(buffer)); - gameObject.uniformBuffersMemory.emplace_back(std::move(bufferMem)); - gameObject.uniformBuffersMapped.emplace_back(gameObject.uniformBuffersMemory[i].mapMemory(0, bufferSize)); - } - } - } - - void createDescriptorPool() { - // We need MAX_OBJECTS * MAX_FRAMES_IN_FLIGHT descriptor sets - std::array poolSize { - vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, MAX_OBJECTS * MAX_FRAMES_IN_FLIGHT), - vk::DescriptorPoolSize(vk::DescriptorType::eCombinedImageSampler, MAX_OBJECTS * MAX_FRAMES_IN_FLIGHT) - }; - vk::DescriptorPoolCreateInfo poolInfo{ - .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, - .maxSets = MAX_OBJECTS * MAX_FRAMES_IN_FLIGHT, - .poolSizeCount = static_cast(poolSize.size()), - .pPoolSizes = poolSize.data() - }; - descriptorPool = vk::raii::DescriptorPool(device, poolInfo); - } - - void createDescriptorSets() { - // For each game object - for (auto& gameObject : gameObjects) { - // Create descriptor sets for each frame in flight - std::vector layouts(MAX_FRAMES_IN_FLIGHT, *descriptorSetLayout); - vk::DescriptorSetAllocateInfo allocInfo{ - .descriptorPool = *descriptorPool, - .descriptorSetCount = static_cast(layouts.size()), - .pSetLayouts = layouts.data() - }; - - gameObject.descriptorSets.clear(); - gameObject.descriptorSets = device.allocateDescriptorSets(allocInfo); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DescriptorBufferInfo bufferInfo{ - .buffer = *gameObject.uniformBuffers[i], - .offset = 0, - .range = sizeof(UniformBufferObject) - }; - vk::DescriptorImageInfo imageInfo{ - .sampler = *textureSampler, - .imageView = *textureImageView, - .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal - }; - std::array descriptorWrites{ - vk::WriteDescriptorSet{ - .dstSet = *gameObject.descriptorSets[i], - .dstBinding = 0, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eUniformBuffer, - .pBufferInfo = &bufferInfo - }, - vk::WriteDescriptorSet{ - .dstSet = *gameObject.descriptorSets[i], - .dstBinding = 1, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eCombinedImageSampler, - .pImageInfo = &imageInfo - } - }; - device.updateDescriptorSets(descriptorWrites, {}); - } - } - } - - void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer& buffer, vk::raii::DeviceMemory& bufferMemory) { - vk::BufferCreateInfo bufferInfo{ - .size = size, - .usage = usage, - .sharingMode = vk::SharingMode::eExclusive - }; - buffer = vk::raii::Buffer(device, bufferInfo); - vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ - .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) - }; - bufferMemory = vk::raii::DeviceMemory(device, allocInfo); - buffer.bindMemory(*bufferMemory, 0); - } - - std::unique_ptr beginSingleTimeCommands() { - vk::CommandBufferAllocateInfo allocInfo{ - .commandPool = *commandPool, - .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = 1 - }; - std::unique_ptr commandBuffer = std::make_unique(std::move(vk::raii::CommandBuffers(device, allocInfo).front())); - - vk::CommandBufferBeginInfo beginInfo{ - .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit - }; - commandBuffer->begin(beginInfo); - - return commandBuffer; - } - - void endSingleTimeCommands(const vk::raii::CommandBuffer& commandBuffer) const { - commandBuffer.end(); - - vk::SubmitInfo submitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandBuffer }; - queue.submit(submitInfo, nullptr); - queue.waitIdle(); - } - - void copyBuffer(vk::raii::Buffer & srcBuffer, vk::raii::Buffer & dstBuffer, vk::DeviceSize size) { - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = *commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1 }; - vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); - commandCopyBuffer.begin(vk::CommandBufferBeginInfo{ .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit }); - commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy{ .size = size }); - commandCopyBuffer.end(); - queue.submit(vk::SubmitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer }, nullptr); - queue.waitIdle(); - } - - uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) { - vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); - - for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { - if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { - return i; - } - } - - throw std::runtime_error("failed to find suitable memory type!"); - } - - void createCommandBuffers() { - commandBuffers.clear(); - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = *commandPool, .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = MAX_FRAMES_IN_FLIGHT }; - commandBuffers = vk::raii::CommandBuffers(device, allocInfo); - } - - void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin({}); - transition_image_layout( - imageIndex, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, - vk::AccessFlagBits2::eColorAttachmentWrite, - vk::PipelineStageFlagBits2::eTopOfPipe, - vk::PipelineStageFlagBits2::eColorAttachmentOutput - ); - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::RenderingAttachmentInfo attachmentInfo = { - .imageView = *swapChainImageViews[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor - }; - vk::RenderingInfo renderingInfo = { - .renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &attachmentInfo - }; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); - - // Bind vertex and index buffers (shared by all objects) - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint32); - - // Draw each object with its own descriptor set - for (const auto& gameObject : gameObjects) { - // Bind the descriptor set for this object - commandBuffers[currentFrame].bindDescriptorSets( - vk::PipelineBindPoint::eGraphics, - *pipelineLayout, - 0, - *gameObject.descriptorSets[currentFrame], - nullptr - ); - - // Draw the object - commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); - } - - commandBuffers[currentFrame].endRendering(); - transition_image_layout( - imageIndex, - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, - {}, - vk::PipelineStageFlagBits2::eColorAttachmentOutput, - vk::PipelineStageFlagBits2::eBottomOfPipe - ); - commandBuffers[currentFrame].end(); - } - - void transition_image_layout( - uint32_t imageIndex, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::AccessFlags2 src_access_mask, - vk::AccessFlags2 dst_access_mask, - vk::PipelineStageFlags2 src_stage_mask, - vk::PipelineStageFlags2 dst_stage_mask - ) { - vk::ImageMemoryBarrier2 barrier = { - .srcStageMask = src_stage_mask, - .srcAccessMask = src_access_mask, - .dstStageMask = dst_stage_mask, - .dstAccessMask = dst_access_mask, - .oldLayout = old_layout, - .newLayout = new_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); - } - - void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); - - for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - } - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - inFlightFences.emplace_back(device, vk::FenceCreateInfo{ .flags = vk::FenceCreateFlagBits::eSignaled }); - } - } - - void updateUniformBuffers() { - static auto startTime = std::chrono::high_resolution_clock::now(); - static auto lastFrameTime = startTime; - auto currentTime = std::chrono::high_resolution_clock::now(); - float time = std::chrono::duration(currentTime - startTime).count(); - float deltaTime = std::chrono::duration(currentTime - lastFrameTime).count(); - lastFrameTime = currentTime; - - // Camera and projection matrices (shared by all objects) - glm::mat4 view = glm::lookAt(glm::vec3(2.0f, 2.0f, 6.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f)); - glm::mat4 proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 20.0f); - // Update uniform buffers for each object - for (auto& gameObject : gameObjects) { - // Apply continuous rotation to the object based on frame time - const float rotationSpeed = 0.5f; // Rotation speed in radians per second - gameObject.rotation.y += rotationSpeed * deltaTime; // Slow rotation around Y axis scaled by frame time - - // Get the model matrix for this object - glm::mat4 model = gameObject.getModelMatrix(); - - // Create and update the UBO - UniformBufferObject ubo{ - .model = model, - .view = view, - .proj = proj - }; - - // Copy the UBO data to the mapped memory - memcpy(gameObject.uniformBuffersMapped[currentFrame], &ubo, sizeof(ubo)); - } - } - - void drawFrame() { - while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)); - auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[currentFrame], nullptr); - - if (result == vk::Result::eErrorOutOfDateKHR) { - recreateSwapChain(); - return; - } - if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) { - throw std::runtime_error("failed to acquire swap chain image!"); - } - - // Update uniform buffers for all objects - updateUniformBuffers(); - - device.resetFences(*inFlightFences[currentFrame]); - commandBuffers[currentFrame].reset(); - recordCommandBuffer(imageIndex); - - vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); - const vk::SubmitInfo submitInfo{ - .waitSemaphoreCount = 1, - .pWaitSemaphores = &*presentCompleteSemaphore[currentFrame], - .pWaitDstStageMask = &waitDestinationStageMask, - .commandBufferCount = 1, - .pCommandBuffers = &*commandBuffers[currentFrame], - .signalSemaphoreCount = 1, - .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex] - }; - queue.submit(submitInfo, *inFlightFences[currentFrame]); - - const vk::PresentInfoKHR presentInfoKHR{ - .waitSemaphoreCount = 1, - .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], - .swapchainCount = 1, - .pSwapchains = &*swapChain, - .pImageIndices = &imageIndex - }; - result = queue.presentKHR(presentInfoKHR); - if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { - framebufferResized = false; - recreateSwapChain(); - } else if (result != vk::Result::eSuccess) { - throw std::runtime_error("failed to present swap chain image!"); - } - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; - } - - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size(), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static vk::Format chooseSwapSurfaceFormat(const std::vector& availableFormats) { - return (availableFormats[0].format == vk::Format::eUndefined) ? vk::Format::eB8G8R8A8Unorm : availableFormats[0].format; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eFifo; - } - - vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) { - if (capabilities.currentExtent.width != std::numeric_limits::max()) { - return capabilities.currentExtent; - } + if (result && supported == VK_TRUE) + { + appInfo.profileSupported = true; + appInfo.profile = profileProperties; + LOGI("Device supports Vulkan profile: %s", name); + } + else + { + LOGI("Device does not support Vulkan profile: %s", name); + } + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + auto features = physicalDevice.getFeatures2(); + vk::PhysicalDeviceVulkan13Features vulkan13Features; + vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT extendedDynamicStateFeatures; + vulkan13Features.dynamicRendering = vk::True; + vulkan13Features.synchronization2 = vk::True; + extendedDynamicStateFeatures.extendedDynamicState = vk::True; + vulkan13Features.pNext = &extendedDynamicStateFeatures; + features.pNext = &vulkan13Features; + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{ + .pNext = &features, + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + // Create the device with the appropriate features + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(*surface); + swapChainImageFormat = 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{ + .surface = *surface, .minImageCount = minImageCount, .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + void createImageViews() + { + 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(device, imageViewCreateInfo); + } + } + + void createDescriptorSetLayout() + { + std::array bindings = { + vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr), + vk::DescriptorSetLayoutBinding(1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment, nullptr)}; + + vk::DescriptorSetLayoutCreateInfo layoutInfo{.bindingCount = static_cast(bindings.size()), .pBindings = bindings.data()}; + descriptorSetLayout = vk::raii::DescriptorSetLayout(device, layoutInfo); + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(this->readFile("shaders/slang.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}; + + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{ + .vertexBindingDescriptionCount = 1, + .pVertexBindingDescriptions = &bindingDescription, + .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), + .pVertexAttributeDescriptions = attributeDescriptions.data()}; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ + .topology = vk::PrimitiveTopology::eTriangleList, + .primitiveRestartEnable = vk::False}; + vk::PipelineViewportStateCreateInfo viewportState{ + .viewportCount = 1, + .scissorCount = 1}; + vk::PipelineRasterizationStateCreateInfo rasterizer{ + .depthClampEnable = vk::False, + .rasterizerDiscardEnable = vk::False, + .polygonMode = vk::PolygonMode::eFill, + .cullMode = vk::CullModeFlagBits::eBack, // Re-enabled culling for better performance + .frontFace = vk::FrontFace::eClockwise, // Keeping Clockwise for glTF + .depthBiasEnable = vk::False}; + rasterizer.lineWidth = 1.0f; + vk::PipelineMultisampleStateCreateInfo multisampling{ + .rasterizationSamples = vk::SampleCountFlagBits::e1, + .sampleShadingEnable = vk::False}; + vk::PipelineDepthStencilStateCreateInfo depthStencil{ + .depthTestEnable = vk::True, + .depthWriteEnable = vk::True, + .depthCompareOp = vk::CompareOp::eLess, + .depthBoundsTestEnable = vk::False, + .stencilTestEnable = vk::False}; + vk::PipelineColorBlendAttachmentState colorBlendAttachment; + colorBlendAttachment.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA; + colorBlendAttachment.blendEnable = vk::False; + + vk::PipelineColorBlendStateCreateInfo colorBlending{ + .logicOpEnable = vk::False, + .logicOp = vk::LogicOp::eCopy, + .attachmentCount = 1, + .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0}; + + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainImageFormat}; + vk::GraphicsPipelineCreateInfo pipelineInfo{.pNext = &pipelineRenderingCreateInfo, + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pDepthStencilState = &depthStencil, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = *pipelineLayout, + .renderPass = nullptr}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{ + .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex}; + commandPool = vk::raii::CommandPool(device, poolInfo); + } + + void createDepthResources() + { + vk::Format depthFormat = findDepthFormat(); + + createImage(swapChainExtent.width, swapChainExtent.height, depthFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eDepthStencilAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, depthImage, depthImageMemory); + depthImageView = createImageView(depthImage, depthFormat, vk::ImageAspectFlagBits::eDepth); + } + + vk::Format findSupportedFormat(const std::vector &candidates, vk::ImageTiling tiling, vk::FormatFeatureFlags features) const + { + for (const auto format : candidates) + { + vk::FormatProperties props = physicalDevice.getFormatProperties(format); + + if (tiling == vk::ImageTiling::eLinear && (props.linearTilingFeatures & features) == features) + { + return format; + } + if (tiling == vk::ImageTiling::eOptimal && (props.optimalTilingFeatures & features) == features) + { + return format; + } + } + + throw std::runtime_error("failed to find supported format!"); + } + + [[nodiscard]] vk::Format findDepthFormat() const + { + return findSupportedFormat( + {vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, vk::Format::eD24UnormS8Uint}, + vk::ImageTiling::eOptimal, + vk::FormatFeatureFlagBits::eDepthStencilAttachment); + } + + static bool hasStencilComponent(vk::Format format) + { + return format == vk::Format::eD32SfloatS8Uint || format == vk::Format::eD24UnormS8Uint; + } + + void createTextureImage() + { + // Load KTX2 texture instead of using stb_image + ktxTexture *kTexture; + KTX_error_code result = ktxTexture_CreateFromNamedFile( + TEXTURE_PATH.c_str(), + KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, + &kTexture); + + if (result != KTX_SUCCESS) + { + throw std::runtime_error("failed to load ktx texture image!"); + } + + // Get texture dimensions and data + uint32_t texWidth = kTexture->baseWidth; + uint32_t texHeight = kTexture->baseHeight; + ktx_size_t imageSize = ktxTexture_GetImageSize(kTexture, 0); + ktx_uint8_t *ktxTextureData = ktxTexture_GetData(kTexture); + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, imageSize); + memcpy(data, ktxTextureData, imageSize); + stagingBufferMemory.unmapMemory(); + + // Determine the Vulkan format from KTX format + vk::Format textureFormat; + + // Check if the KTX texture has a format + if (kTexture->classId == ktxTexture2_c) + { + // For KTX2 files, we can get the format directly + auto *ktx2 = reinterpret_cast(kTexture); + textureFormat = static_cast(ktx2->vkFormat); + if (textureFormat == vk::Format::eUndefined) + { + // If the format is undefined, fall back to a reasonable default + textureFormat = vk::Format::eR8G8B8A8Unorm; + } + } + else + { + // For KTX1 files or if we can't determine the format, use a reasonable default + textureFormat = vk::Format::eR8G8B8A8Unorm; + } + + textureImageFormat = textureFormat; + + createImage(texWidth, texHeight, textureFormat, vk::ImageTiling::eOptimal, + vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, + vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory); + + transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal); + copyBufferToImage(stagingBuffer, textureImage, texWidth, texHeight); + transitionImageLayout(textureImage, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal); + + ktxTexture_Destroy(kTexture); + } + + void createTextureImageView() + { + textureImageView = createImageView(textureImage, textureImageFormat, vk::ImageAspectFlagBits::eColor); + } + + void createTextureSampler() + { + vk::PhysicalDeviceProperties properties = physicalDevice.getProperties(); + vk::SamplerCreateInfo samplerInfo{ + .magFilter = vk::Filter::eLinear, + .minFilter = vk::Filter::eLinear, + .mipmapMode = vk::SamplerMipmapMode::eLinear, + .addressModeU = vk::SamplerAddressMode::eRepeat, + .addressModeV = vk::SamplerAddressMode::eRepeat, + .addressModeW = vk::SamplerAddressMode::eRepeat, + .mipLodBias = 0.0f, + .anisotropyEnable = vk::True, + .maxAnisotropy = properties.limits.maxSamplerAnisotropy, + .compareEnable = vk::False, + .compareOp = vk::CompareOp::eAlways}; + textureSampler = vk::raii::Sampler(device, samplerInfo); + } + + vk::raii::ImageView createImageView(vk::raii::Image &image, vk::Format format, vk::ImageAspectFlags aspectFlags) + { + vk::ImageViewCreateInfo viewInfo{ + .image = *image, + .viewType = vk::ImageViewType::e2D, + .format = format, + .subresourceRange = {aspectFlags, 0, 1, 0, 1}}; + return vk::raii::ImageView(device, viewInfo); + } + + void createImage(uint32_t width, uint32_t height, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image &image, vk::raii::DeviceMemory &imageMemory) + { + vk::ImageCreateInfo imageInfo{ + .imageType = vk::ImageType::e2D, + .format = format, + .extent = {width, height, 1}, + .mipLevels = 1, + .arrayLayers = 1, + .samples = vk::SampleCountFlagBits::e1, + .tiling = tiling, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive, + .initialLayout = vk::ImageLayout::eUndefined}; + image = vk::raii::Image(device, imageInfo); + + vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{ + .allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + imageMemory = vk::raii::DeviceMemory(device, allocInfo); + image.bindMemory(*imageMemory, 0); + } + + void transitionImageLayout(const vk::raii::Image &image, vk::ImageLayout oldLayout, vk::ImageLayout newLayout) + { + auto commandBuffer = beginSingleTimeCommands(); + + vk::ImageMemoryBarrier barrier{ + .oldLayout = oldLayout, + .newLayout = newLayout, + .image = *image, + .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + + vk::PipelineStageFlags sourceStage; + vk::PipelineStageFlags destinationStage; + + if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) + { + barrier.srcAccessMask = {}; + barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; + + sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; + destinationStage = vk::PipelineStageFlagBits::eTransfer; + } + else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) + { + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + + sourceStage = vk::PipelineStageFlagBits::eTransfer; + destinationStage = vk::PipelineStageFlagBits::eFragmentShader; + } + else + { + throw std::invalid_argument("unsupported layout transition!"); + } + commandBuffer->pipelineBarrier(sourceStage, destinationStage, {}, {}, nullptr, barrier); + endSingleTimeCommands(*commandBuffer); + } + + void copyBufferToImage(const vk::raii::Buffer &buffer, vk::raii::Image &image, uint32_t width, uint32_t height) + { + std::unique_ptr commandBuffer = beginSingleTimeCommands(); + vk::BufferImageCopy region{ + .bufferOffset = 0, + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource = {vk::ImageAspectFlagBits::eColor, 0, 0, 1}, + .imageOffset = {0, 0, 0}, + .imageExtent = {width, height, 1}}; + commandBuffer->copyBufferToImage(*buffer, *image, vk::ImageLayout::eTransferDstOptimal, {region}); + endSingleTimeCommands(*commandBuffer); + } + + void loadModel() + { + // Use tinygltf to load the model instead of tinyobjloader + tinygltf::Model model; + tinygltf::TinyGLTF loader; + std::string err; + std::string warn; + + bool ret = loader.LoadBinaryFromFile(&model, &err, &warn, MODEL_PATH); + + if (!warn.empty()) + { + std::cout << "glTF warning: " << warn << std::endl; + } + + if (!err.empty()) + { + std::cout << "glTF error: " << err << std::endl; + } + + if (!ret) + { + throw std::runtime_error("Failed to load glTF model"); + } + + vertices.clear(); + indices.clear(); + + // Process all meshes in the model + for (const auto &mesh : model.meshes) + { + for (const auto &primitive : mesh.primitives) + { + // Get indices + const tinygltf::Accessor &indexAccessor = model.accessors[primitive.indices]; + const tinygltf::BufferView &indexBufferView = model.bufferViews[indexAccessor.bufferView]; + const tinygltf::Buffer &indexBuffer = model.buffers[indexBufferView.buffer]; + + // Get vertex positions + const tinygltf::Accessor &posAccessor = model.accessors[primitive.attributes.at("POSITION")]; + const tinygltf::BufferView &posBufferView = model.bufferViews[posAccessor.bufferView]; + const tinygltf::Buffer &posBuffer = model.buffers[posBufferView.buffer]; + + // Get texture coordinates if available + bool hasTexCoords = primitive.attributes.find("TEXCOORD_0") != primitive.attributes.end(); + const tinygltf::Accessor *texCoordAccessor = nullptr; + const tinygltf::BufferView *texCoordBufferView = nullptr; + const tinygltf::Buffer *texCoordBuffer = nullptr; + + if (hasTexCoords) + { + texCoordAccessor = &model.accessors[primitive.attributes.at("TEXCOORD_0")]; + texCoordBufferView = &model.bufferViews[texCoordAccessor->bufferView]; + texCoordBuffer = &model.buffers[texCoordBufferView->buffer]; + } + + uint32_t baseVertex = static_cast(vertices.size()); + + for (size_t i = 0; i < posAccessor.count; i++) + { + Vertex vertex{}; + + const float *pos = reinterpret_cast(&posBuffer.data[posBufferView.byteOffset + posAccessor.byteOffset + i * 12]); + // glTF uses a right-handed coordinate system with Y-up + // Vulkan uses a right-handed coordinate system with Y-down + // We need to flip the Y coordinate + vertex.pos = {pos[0], -pos[1], pos[2]}; + + if (hasTexCoords) + { + const float *texCoord = reinterpret_cast(&texCoordBuffer->data[texCoordBufferView->byteOffset + texCoordAccessor->byteOffset + i * 8]); + vertex.texCoord = {texCoord[0], texCoord[1]}; + } + else + { + vertex.texCoord = {0.0f, 0.0f}; + } + + vertex.color = {1.0f, 1.0f, 1.0f}; + + vertices.push_back(vertex); + } + + const unsigned char *indexData = &indexBuffer.data[indexBufferView.byteOffset + indexAccessor.byteOffset]; + size_t indexCount = indexAccessor.count; + size_t indexStride = 0; + + // Determine index stride based on component type + if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) + { + indexStride = sizeof(uint16_t); + } + else if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT) + { + indexStride = sizeof(uint32_t); + } + else if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) + { + indexStride = sizeof(uint8_t); + } + else + { + throw std::runtime_error("Unsupported index component type"); + } + + indices.reserve(indices.size() + indexCount); + + for (size_t i = 0; i < indexCount; i++) + { + uint32_t index = 0; + + if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) + { + index = *reinterpret_cast(indexData + i * indexStride); + } + else if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT) + { + index = *reinterpret_cast(indexData + i * indexStride); + } + else if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) + { + index = *reinterpret_cast(indexData + i * indexStride); + } + + indices.push_back(baseVertex + index); + } + } + } + } + + void createVertexBuffer() + { + vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(dataStaging, vertices.data(), bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + } + + void createIndexBuffer() + { + vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(data, indices.data(), bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); + + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + } + + // Initialize the game objects with different positions, rotations, and scales + void setupGameObjects() + { + // Object 1 - Center + gameObjects[0].position = {0.0f, 0.0f, 0.0f}; + gameObjects[0].rotation = {0.0f, 0.0f, 0.0f}; + gameObjects[0].scale = {1.0f, 1.0f, 1.0f}; + + // Object 2 - Left + gameObjects[1].position = {-2.0f, 0.0f, -1.0f}; + gameObjects[1].rotation = {0.0f, glm::radians(45.0f), 0.0f}; + gameObjects[1].scale = {0.75f, 0.75f, 0.75f}; + + // Object 3 - Right + gameObjects[2].position = {2.0f, 0.0f, -1.0f}; + gameObjects[2].rotation = {0.0f, glm::radians(-45.0f), 0.0f}; + gameObjects[2].scale = {0.75f, 0.75f, 0.75f}; + } + + // Create uniform buffers for each object + void createUniformBuffers() + { + // For each game object + for (auto &gameObject : gameObjects) + { + gameObject.uniformBuffers.clear(); + gameObject.uniformBuffersMemory.clear(); + gameObject.uniformBuffersMapped.clear(); + + // Create uniform buffers for each frame in flight + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DeviceSize bufferSize = sizeof(UniformBufferObject); + vk::raii::Buffer buffer({}); + vk::raii::DeviceMemory bufferMem({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); + gameObject.uniformBuffers.emplace_back(std::move(buffer)); + gameObject.uniformBuffersMemory.emplace_back(std::move(bufferMem)); + gameObject.uniformBuffersMapped.emplace_back(gameObject.uniformBuffersMemory[i].mapMemory(0, bufferSize)); + } + } + } + + void createDescriptorPool() + { + // We need MAX_OBJECTS * MAX_FRAMES_IN_FLIGHT descriptor sets + std::array poolSize{ + vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, MAX_OBJECTS * MAX_FRAMES_IN_FLIGHT), + vk::DescriptorPoolSize(vk::DescriptorType::eCombinedImageSampler, MAX_OBJECTS * MAX_FRAMES_IN_FLIGHT)}; + vk::DescriptorPoolCreateInfo poolInfo{ + .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, + .maxSets = MAX_OBJECTS * MAX_FRAMES_IN_FLIGHT, + .poolSizeCount = static_cast(poolSize.size()), + .pPoolSizes = poolSize.data()}; + descriptorPool = vk::raii::DescriptorPool(device, poolInfo); + } + + void createDescriptorSets() + { + // For each game object + for (auto &gameObject : gameObjects) + { + // Create descriptor sets for each frame in flight + std::vector layouts(MAX_FRAMES_IN_FLIGHT, *descriptorSetLayout); + vk::DescriptorSetAllocateInfo allocInfo{ + .descriptorPool = *descriptorPool, + .descriptorSetCount = static_cast(layouts.size()), + .pSetLayouts = layouts.data()}; + + gameObject.descriptorSets.clear(); + gameObject.descriptorSets = device.allocateDescriptorSets(allocInfo); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DescriptorBufferInfo bufferInfo{ + .buffer = *gameObject.uniformBuffers[i], + .offset = 0, + .range = sizeof(UniformBufferObject)}; + vk::DescriptorImageInfo imageInfo{ + .sampler = *textureSampler, + .imageView = *textureImageView, + .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal}; + std::array descriptorWrites{ + vk::WriteDescriptorSet{ + .dstSet = *gameObject.descriptorSets[i], + .dstBinding = 0, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eUniformBuffer, + .pBufferInfo = &bufferInfo}, + vk::WriteDescriptorSet{ + .dstSet = *gameObject.descriptorSets[i], + .dstBinding = 1, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .pImageInfo = &imageInfo}}; + device.updateDescriptorSets(descriptorWrites, {}); + } + } + } + + void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer &buffer, vk::raii::DeviceMemory &bufferMemory) + { + vk::BufferCreateInfo bufferInfo{ + .size = size, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive}; + buffer = vk::raii::Buffer(device, bufferInfo); + vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{ + .allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + bufferMemory = vk::raii::DeviceMemory(device, allocInfo); + buffer.bindMemory(*bufferMemory, 0); + } + + std::unique_ptr beginSingleTimeCommands() + { + vk::CommandBufferAllocateInfo allocInfo{ + .commandPool = *commandPool, + .level = vk::CommandBufferLevel::ePrimary, + .commandBufferCount = 1}; + std::unique_ptr commandBuffer = std::make_unique(std::move(vk::raii::CommandBuffers(device, allocInfo).front())); + + vk::CommandBufferBeginInfo beginInfo{ + .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; + commandBuffer->begin(beginInfo); + + return commandBuffer; + } + + void endSingleTimeCommands(const vk::raii::CommandBuffer &commandBuffer) const + { + commandBuffer.end(); + + vk::SubmitInfo submitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandBuffer}; + queue.submit(submitInfo, nullptr); + queue.waitIdle(); + } + + void copyBuffer(vk::raii::Buffer &srcBuffer, vk::raii::Buffer &dstBuffer, vk::DeviceSize size) + { + vk::CommandBufferAllocateInfo allocInfo{.commandPool = *commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1}; + vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); + commandCopyBuffer.begin(vk::CommandBufferBeginInfo{.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); + commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy{.size = size}); + commandCopyBuffer.end(); + queue.submit(vk::SubmitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer}, nullptr); + queue.waitIdle(); + } + + uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) + { + vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) + { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) + { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + void createCommandBuffers() + { + commandBuffers.clear(); + vk::CommandBufferAllocateInfo allocInfo{.commandPool = *commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = MAX_FRAMES_IN_FLIGHT}; + commandBuffers = vk::raii::CommandBuffers(device, allocInfo); + } + + void recordCommandBuffer(uint32_t imageIndex) + { + commandBuffers[currentFrame].begin({}); + transition_image_layout( + imageIndex, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, + vk::AccessFlagBits2::eColorAttachmentWrite, + vk::PipelineStageFlagBits2::eTopOfPipe, + vk::PipelineStageFlagBits2::eColorAttachmentOutput); + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::RenderingAttachmentInfo attachmentInfo = { + .imageView = *swapChainImageViews[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor}; + vk::RenderingInfo renderingInfo = { + .renderArea = {.offset = {0, 0}, .extent = swapChainExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &attachmentInfo}; + commandBuffers[currentFrame].beginRendering(renderingInfo); + commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + + // Bind vertex and index buffers (shared by all objects) + commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint32); + + // Draw each object with its own descriptor set + for (const auto &gameObject : gameObjects) + { + // Bind the descriptor set for this object + commandBuffers[currentFrame].bindDescriptorSets( + vk::PipelineBindPoint::eGraphics, + *pipelineLayout, + 0, + *gameObject.descriptorSets[currentFrame], + nullptr); + + // Draw the object + commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); + } + + commandBuffers[currentFrame].endRendering(); + transition_image_layout( + imageIndex, + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, + {}, + vk::PipelineStageFlagBits2::eColorAttachmentOutput, + vk::PipelineStageFlagBits2::eBottomOfPipe); + commandBuffers[currentFrame].end(); + } + + void transition_image_layout( + uint32_t imageIndex, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout, + vk::AccessFlags2 src_access_mask, + vk::AccessFlags2 dst_access_mask, + vk::PipelineStageFlags2 src_stage_mask, + vk::PipelineStageFlags2 dst_stage_mask) + { + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = src_stage_mask, + .srcAccessMask = src_access_mask, + .dstStageMask = dst_stage_mask, + .dstAccessMask = dst_access_mask, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo dependency_info = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + } + + void createSyncObjects() + { + presentCompleteSemaphore.clear(); + renderFinishedSemaphore.clear(); + inFlightFences.clear(); + + for (size_t i = 0; i < swapChainImages.size(); i++) + { + presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); + } + } + + void updateUniformBuffers() + { + static auto startTime = std::chrono::high_resolution_clock::now(); + static auto lastFrameTime = startTime; + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + float deltaTime = std::chrono::duration(currentTime - lastFrameTime).count(); + lastFrameTime = currentTime; + + // Camera and projection matrices (shared by all objects) + glm::mat4 view = glm::lookAt(glm::vec3(2.0f, 2.0f, 6.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f)); + glm::mat4 proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 20.0f); + // Update uniform buffers for each object + for (auto &gameObject : gameObjects) + { + // Apply continuous rotation to the object based on frame time + const float rotationSpeed = 0.5f; // Rotation speed in radians per second + gameObject.rotation.y += rotationSpeed * deltaTime; // Slow rotation around Y axis scaled by frame time + + // Get the model matrix for this object + glm::mat4 model = gameObject.getModelMatrix(); + + // Create and update the UBO + UniformBufferObject ubo{ + .model = model, + .view = view, + .proj = proj}; + + // Copy the UBO data to the mapped memory + memcpy(gameObject.uniformBuffersMapped[currentFrame], &ubo, sizeof(ubo)); + } + } + + void drawFrame() + { + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + ; + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[currentFrame], nullptr); + + if (result == vk::Result::eErrorOutOfDateKHR) + { + recreateSwapChain(); + return; + } + if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) + { + throw std::runtime_error("failed to acquire swap chain image!"); + } + + // Update uniform buffers for all objects + updateUniformBuffers(); + + device.resetFences(*inFlightFences[currentFrame]); + commandBuffers[currentFrame].reset(); + recordCommandBuffer(imageIndex); + + vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); + const vk::SubmitInfo submitInfo{ + .waitSemaphoreCount = 1, + .pWaitSemaphores = &*presentCompleteSemaphore[currentFrame], + .pWaitDstStageMask = &waitDestinationStageMask, + .commandBufferCount = 1, + .pCommandBuffers = &*commandBuffers[currentFrame], + .signalSemaphoreCount = 1, + .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[currentFrame]); + + const vk::PresentInfoKHR presentInfoKHR{ + .waitSemaphoreCount = 1, + .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], + .swapchainCount = 1, + .pSwapchains = &*swapChain, + .pImageIndices = &imageIndex}; + result = queue.presentKHR(presentInfoKHR); + if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) + { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != vk::Result::eSuccess) + { + throw std::runtime_error("failed to present swap chain image!"); + } + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size(), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static vk::Format chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + return (availableFormats[0].format == vk::Format::eUndefined) ? vk::Format::eB8G8R8A8Unorm : availableFormats[0].format; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + vk::PresentModeKHR::eFifo; + } + + vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR &capabilities) + { + if (capabilities.currentExtent.width != std::numeric_limits::max()) + { + return capabilities.currentExtent; + } #if PLATFORM_DESKTOP - int width, height; - glfwGetFramebufferSize(window, &width, &height); + int width, height; + glfwGetFramebufferSize(window, &width, &height); #else - ANativeWindow* window = androidAppState.nativeWindow; - int width = ANativeWindow_getWidth(window); - int height = ANativeWindow_getHeight(window); + ANativeWindow *window = androidAppState.nativeWindow; + int width = ANativeWindow_getWidth(window); + int height = ANativeWindow_getHeight(window); #endif - return { - std::clamp(width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width), - std::clamp(height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height) - }; - } + return { + std::clamp(width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width), + std::clamp(height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height)}; + } - [[nodiscard]] std::vector getRequiredExtensions() const { - std::vector extensions; + [[nodiscard]] std::vector getRequiredExtensions() const + { + std::vector extensions; #if PLATFORM_DESKTOP - // Get GLFW extensions - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - extensions.assign(glfwExtensions, glfwExtensions + glfwExtensionCount); + // Get GLFW extensions + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + extensions.assign(glfwExtensions, glfwExtensions + glfwExtensionCount); #else - // Android extensions - extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME); - extensions.push_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME); + // Android extensions + extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME); + extensions.push_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME); #endif - // Add debug extensions if validation layers are enabled - if (enableValidationLayers) { - extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); - } - - return extensions; - } - - [[nodiscard]] bool checkValidationLayerSupport() const { - return (std::ranges::any_of(context.enumerateInstanceLayerProperties(), - []( vk::LayerProperties const & lp ) { return ( strcmp( "VK_LAYER_KHRONOS_validation", lp.layerName ) == 0 ); } ) ); - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - std::vector readFile(const std::string& filename) { + // Add debug extensions if validation layers are enabled + if (enableValidationLayers) + { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + [[nodiscard]] bool checkValidationLayerSupport() const + { + return (std::ranges::any_of(context.enumerateInstanceLayerProperties(), + [](vk::LayerProperties const &lp) { return (strcmp("VK_LAYER_KHRONOS_validation", lp.layerName) == 0); })); + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + std::vector readFile(const std::string &filename) + { #if PLATFORM_ANDROID - // Android asset loading - if (androidAppState.app == nullptr) { - LOGE("Android app not initialized"); - throw std::runtime_error("Android app not initialized"); - } - AAsset* asset = AAssetManager_open(androidAppState.app->activity->assetManager, filename.c_str(), AASSET_MODE_BUFFER); - if (!asset) { - throw std::runtime_error("failed to open file: " + filename); - } - - size_t size = AAsset_getLength(asset); - std::vector buffer(size); - AAsset_read(asset, buffer.data(), size); - AAsset_close(asset); + // Android asset loading + if (androidAppState.app == nullptr) + { + LOGE("Android app not initialized"); + throw std::runtime_error("Android app not initialized"); + } + AAsset *asset = AAssetManager_open(androidAppState.app->activity->assetManager, filename.c_str(), AASSET_MODE_BUFFER); + if (!asset) + { + throw std::runtime_error("failed to open file: " + filename); + } + + size_t size = AAsset_getLength(asset); + std::vector buffer(size); + AAsset_read(asset, buffer.data(), size); + AAsset_close(asset); #else - // Desktop file loading - std::ifstream file(filename, std::ios::ate | std::ios::binary); - if (!file.is_open()) { - throw std::runtime_error("failed to open file: " + filename); - } - - size_t fileSize = static_cast(file.tellg()); - std::vector buffer(fileSize); - file.seekg(0); - file.read(buffer.data(), fileSize); - file.close(); + // Desktop file loading + std::ifstream file(filename, std::ios::ate | std::ios::binary); + if (!file.is_open()) + { + throw std::runtime_error("failed to open file: " + filename); + } + + size_t fileSize = static_cast(file.tellg()); + std::vector buffer(fileSize); + file.seekg(0); + file.read(buffer.data(), fileSize); + file.close(); #endif - return buffer; - } + return buffer; + } }; #if PLATFORM_ANDROID -void android_main(android_app* app) { - app_dummy(); +void android_main(android_app *app) +{ + app_dummy(); - VulkanApplication vulkanApp; - vulkanApp.run(app); + VulkanApplication vulkanApp; + vulkanApp.run(app); } #else -int main() { - try { - VulkanApplication app; - app.run(); - } catch (const std::exception& e) { - LOGE("%s", e.what()); - return EXIT_FAILURE; - } - return EXIT_SUCCESS; +int main() +{ + try + { + VulkanApplication app; + app.run(); + } + catch (const std::exception &e) + { + LOGE("%s", e.what()); + return EXIT_FAILURE; + } + return EXIT_SUCCESS; } #endif diff --git a/attachments/37_multithreading.cpp b/attachments/37_multithreading.cpp index c30c4d1f..ab8029fa 100644 --- a/attachments/37_multithreading.cpp +++ b/attachments/37_multithreading.cpp @@ -1,1213 +1,1294 @@ -#include -#include -#include -#include -#include -#include -#include #include -#include #include -#include -#include -#include -#include #include +#include #include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include #define GLM_FORCE_RADIANS #include #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -constexpr uint64_t FenceTimeout = 100000000; +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; +constexpr uint64_t FenceTimeout = 100000000; constexpr uint32_t PARTICLE_COUNT = 8192; constexpr int MAX_FRAMES_IN_FLIGHT = 2; - -struct UniformBufferObject { - float deltaTime = 1.0f; +struct UniformBufferObject +{ + float deltaTime = 1.0f; }; -struct Particle { - glm::vec2 position; - glm::vec2 velocity; - glm::vec4 color; - - static vk::VertexInputBindingDescription getBindingDescription() { - return { 0, sizeof(Particle), vk::VertexInputRate::eVertex }; - } - - static std::array getAttributeDescriptions() { - return { - vk::VertexInputAttributeDescription( 0, 0, vk::Format::eR32G32Sfloat, offsetof(Particle, position) ), - vk::VertexInputAttributeDescription( 1, 0, vk::Format::eR32G32B32A32Sfloat, offsetof(Particle, color) ), - }; - } +struct Particle +{ + glm::vec2 position; + glm::vec2 velocity; + glm::vec4 color; + + static vk::VertexInputBindingDescription getBindingDescription() + { + return {0, sizeof(Particle), vk::VertexInputRate::eVertex}; + } + + static std::array getAttributeDescriptions() + { + return { + vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32Sfloat, offsetof(Particle, position)), + vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32A32Sfloat, offsetof(Particle, color)), + }; + } }; // Simple logging function -template -void log(Args&&... args) { - // Only log in debug builds +template +void log(Args &&...args) +{ + // Only log in debug builds #ifdef _DEBUG - (std::cout << ... << std::forward(args)) << std::endl; + (std::cout << ... << std::forward(args)) << std::endl; #endif } -class ThreadSafeResourceManager { -private: - std::mutex resourceMutex; - std::vector commandPools; - std::vector commandBuffers; - -public: - void createThreadCommandPools(vk::raii::Device& device, uint32_t queueFamilyIndex, uint32_t threadCount) { - std::lock_guard lock(resourceMutex); - - commandBuffers.clear(); - commandPools.clear(); - - for (uint32_t i = 0; i < threadCount; i++) { - vk::CommandPoolCreateInfo poolInfo{ - .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueFamilyIndex - }; - try { - commandPools.emplace_back(device, poolInfo); - } catch (const std::exception&) { - throw; // Re-throw the exception to be caught by the caller - } - } - } - - vk::raii::CommandPool& getCommandPool(uint32_t threadIndex) { - std::lock_guard lock(resourceMutex); - return commandPools[threadIndex]; - } - - void allocateCommandBuffers(vk::raii::Device& device, uint32_t threadCount, uint32_t buffersPerThread) { - std::lock_guard lock(resourceMutex); - - commandBuffers.clear(); - - if (commandPools.size() < threadCount) { - throw std::runtime_error("Not enough command pools for thread count"); - } - - for (uint32_t i = 0; i < threadCount; i++) { - vk::CommandBufferAllocateInfo allocInfo{ - .commandPool = *commandPools[i], - .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = buffersPerThread - }; - try { - auto threadBuffers = device.allocateCommandBuffers(allocInfo); - for (auto& buffer : threadBuffers) { - commandBuffers.emplace_back(std::move(buffer)); - } - } catch (const std::exception&) { - throw; // Re-throw the exception to be caught by the caller - } - } - } - - vk::raii::CommandBuffer& getCommandBuffer(uint32_t index) { - // No need for mutex here as each thread accesses its own command buffer - if (index >= commandBuffers.size()) { - throw std::runtime_error("Command buffer index out of range: " + std::to_string(index) + - " (available: " + std::to_string(commandBuffers.size()) + ")"); - } - return commandBuffers[index]; - } +class ThreadSafeResourceManager +{ + private: + std::mutex resourceMutex; + std::vector commandPools; + std::vector commandBuffers; + + public: + void createThreadCommandPools(vk::raii::Device &device, uint32_t queueFamilyIndex, uint32_t threadCount) + { + std::lock_guard lock(resourceMutex); + + commandBuffers.clear(); + commandPools.clear(); + + for (uint32_t i = 0; i < threadCount; i++) + { + vk::CommandPoolCreateInfo poolInfo{ + .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueFamilyIndex}; + try + { + commandPools.emplace_back(device, poolInfo); + } + catch (const std::exception &) + { + throw; // Re-throw the exception to be caught by the caller + } + } + } + + vk::raii::CommandPool &getCommandPool(uint32_t threadIndex) + { + std::lock_guard lock(resourceMutex); + return commandPools[threadIndex]; + } + + void allocateCommandBuffers(vk::raii::Device &device, uint32_t threadCount, uint32_t buffersPerThread) + { + std::lock_guard lock(resourceMutex); + + commandBuffers.clear(); + + if (commandPools.size() < threadCount) + { + throw std::runtime_error("Not enough command pools for thread count"); + } + + for (uint32_t i = 0; i < threadCount; i++) + { + vk::CommandBufferAllocateInfo allocInfo{ + .commandPool = *commandPools[i], + .level = vk::CommandBufferLevel::ePrimary, + .commandBufferCount = buffersPerThread}; + try + { + auto threadBuffers = device.allocateCommandBuffers(allocInfo); + for (auto &buffer : threadBuffers) + { + commandBuffers.emplace_back(std::move(buffer)); + } + } + catch (const std::exception &) + { + throw; // Re-throw the exception to be caught by the caller + } + } + } + + vk::raii::CommandBuffer &getCommandBuffer(uint32_t index) + { + // No need for mutex here as each thread accesses its own command buffer + if (index >= commandBuffers.size()) + { + throw std::runtime_error("Command buffer index out of range: " + std::to_string(index) + + " (available: " + std::to_string(commandBuffers.size()) + ")"); + } + return commandBuffers[index]; + } }; -class MultithreadedApplication { -public: - void run() { - initWindow(); - initVulkan(); - initThreads(); - mainLoop(); - cleanup(); - } - -private: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::SurfaceFormatKHR swapChainImageFormat; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - - vk::raii::DescriptorSetLayout computeDescriptorSetLayout = nullptr; - vk::raii::PipelineLayout computePipelineLayout = nullptr; - vk::raii::Pipeline computePipeline = nullptr; - - std::vector shaderStorageBuffers; - std::vector shaderStorageBuffersMemory; - - std::vector uniformBuffers; - std::vector uniformBuffersMemory; - std::vector uniformBuffersMapped; - - vk::raii::DescriptorPool descriptorPool = nullptr; - std::vector computeDescriptorSets; - - vk::raii::CommandPool commandPool = nullptr; - std::vector graphicsCommandBuffers; - - vk::raii::Semaphore timelineSemaphore = nullptr; - uint64_t timelineValue = 0; - std::vector imageAvailableSemaphores; - std::vector inFlightFences; - uint32_t currentFrame = 0; - - double lastFrameTime = 0.0; - - // Removed resize-related variables and FSM state management as per simplification request - - double lastTime = 0.0f; - - uint32_t threadCount = 0; - std::vector workerThreads; - std::atomic shouldExit{false}; - std::vector> threadWorkReady; - std::vector> threadWorkDone; - - std::mutex queueSubmitMutex; - std::mutex workCompleteMutex; - std::condition_variable workCompleteCv; - - ThreadSafeResourceManager resourceManager; - struct ParticleGroup { - uint32_t startIndex; - uint32_t count; - }; - std::vector particleGroups; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - // Helper functions - [[nodiscard]] static std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - return extensions; - } - static 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]; - } - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - for (const auto& availablePresentMode : availablePresentModes) { - if (availablePresentMode == vk::PresentModeKHR::eMailbox) { - return availablePresentMode; - } - } - return vk::PresentModeKHR::eFifo; - } - [[nodiscard]] vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) const { - 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) - }; - } - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size(), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - 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; - } - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan Multithreading", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - - lastTime = glfwGetTime(); - } - - void initVulkan() { - createInstance(); - createSurface(); - pickPhysicalDevice(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createComputeDescriptorSetLayout(); - createGraphicsPipeline(); - createComputePipeline(); - createCommandPool(); - createShaderStorageBuffers(); - createUniformBuffers(); - createDescriptorPool(); - createComputeDescriptorSets(); - createGraphicsCommandBuffers(); - createSyncObjects(); - } - - void initThreads() { - // Increase thread count for better parallelism - threadCount = 8u; - log("Initializing ", threadCount, " threads for sequential execution"); - - threadWorkReady = std::vector>(threadCount); - threadWorkDone = std::vector>(threadCount); - - for (uint32_t i = 0; i < threadCount; i++) { - threadWorkReady[i] = false; - threadWorkDone[i] = true; - } - - initThreadResources(); - - const uint32_t particlesPerThread = PARTICLE_COUNT / threadCount; - particleGroups.resize(threadCount); - - for (uint32_t i = 0; i < threadCount; i++) { - particleGroups[i].startIndex = i * particlesPerThread; - particleGroups[i].count = (i == threadCount - 1) ? - (PARTICLE_COUNT - i * particlesPerThread) : particlesPerThread; - log("Thread ", i, " will process particles ", - particleGroups[i].startIndex, " to ", - (particleGroups[i].startIndex + particleGroups[i].count - 1), - " (count: ", particleGroups[i].count, ")"); - } - - for (uint32_t i = 0; i < threadCount; i++) { - workerThreads.emplace_back(&MultithreadedApplication::workerThreadFunc, this, i); - log("Started worker thread ", i); - } - } - - void workerThreadFunc(uint32_t threadIndex) { - while (!shouldExit) { - // Wait for work using condition variable - { - std::unique_lock lock(workCompleteMutex); - workCompleteCv.wait(lock, [this, threadIndex]() { - return shouldExit || threadWorkReady[threadIndex].load(std::memory_order_acquire); - }); - - if (shouldExit) { - break; - } - - if (!threadWorkReady[threadIndex].load(std::memory_order_acquire)) { - continue; - } - } - - const ParticleGroup& group = particleGroups[threadIndex]; - bool workCompleted = false; - - try { - // Get command buffer and record commands - vk::raii::CommandBuffer* cmdBuffer = &resourceManager.getCommandBuffer(threadIndex); - recordComputeCommandBuffer(*cmdBuffer, group.startIndex, group.count); - workCompleted = true; - } catch (const std::exception&) { - workCompleted = false; - } - - // Mark work as done - threadWorkDone[threadIndex].store(true, std::memory_order_release); - threadWorkReady[threadIndex].store(false, std::memory_order_release); - - // If this is not the last thread, signal the next thread to start - if (threadIndex < threadCount - 1) { - threadWorkReady[threadIndex + 1].store(true, std::memory_order_release); - } - - // Notify main thread and other threads - { - std::lock_guard lock(workCompleteMutex); - workCompleteCv.notify_all(); - } - } - } - - void mainLoop() { - const double targetFrameTime = 1.0 / 60.0; - - while (!glfwWindowShouldClose(window)) { - double frameStartTime = glfwGetTime(); - - glfwPollEvents(); - drawFrame(); - - double currentTime = glfwGetTime(); - lastFrameTime = (currentTime - lastTime) * 1000.0; - lastTime = currentTime; - - double frameTime = currentTime - frameStartTime; - - if (frameTime < targetFrameTime) { - double sleepTime = targetFrameTime - frameTime; - std::this_thread::sleep_for(std::chrono::duration(sleepTime)); - } - } - - device.waitIdle(); - } - - void cleanupSwapChain() { - swapChainImageViews.clear(); - graphicsPipeline = nullptr; - pipelineLayout = nullptr; - computePipeline = nullptr; - computePipelineLayout = nullptr; - computeDescriptorSets.clear(); - computeDescriptorSetLayout = nullptr; - descriptorPool = nullptr; - - // Unmap and clean up uniform buffers - for (size_t i = 0; i < uniformBuffersMapped.size(); i++) { - uniformBuffersMemory[i].unmapMemory(); - } - uniformBuffers.clear(); - uniformBuffersMemory.clear(); - uniformBuffersMapped.clear(); - - // Clean up shader storage buffers - shaderStorageBuffers.clear(); - shaderStorageBuffersMemory.clear(); - - swapChain = nullptr; - } - - void stopThreads() { - shouldExit.store(true, std::memory_order_release); - - for (uint32_t i = 0; i < threadCount; i++) { - threadWorkDone[i].store(true, std::memory_order_release); - threadWorkReady[i].store(false, std::memory_order_release); - } - - // Notify all threads in case they're waiting on the condition variable - { - std::lock_guard lock(workCompleteMutex); - workCompleteCv.notify_all(); - } - - for (auto& thread : workerThreads) { - if (thread.joinable()) { - thread.join(); - } - } - - workerThreads.clear(); - } - - void initThreadResources() { - resourceManager.createThreadCommandPools(device, queueIndex, threadCount); - resourceManager.allocateCommandBuffers(device, threadCount, 1); - } - - void cleanup() { - stopThreads(); - - glfwDestroyWindow(window); - glfwTerminate(); - } - - - void createInstance() { - constexpr vk::ApplicationInfo appInfo{ .pApplicationName = "Vulkan Multithreading", - .applicationVersion = VK_MAKE_VERSION( 1, 0, 0 ), - .pEngineName = "No Engine", - .engineVersion = VK_MAKE_VERSION( 1, 0, 0 ), - .apiVersion = vk::ApiVersion14 }; - auto extensions = getRequiredExtensions(); - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = 0, - .ppEnabledLayerNames = nullptr, - .enabledExtensionCount = static_cast(extensions.size()), - .ppEnabledExtensionNames = extensions.data() }; - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&](auto const & device) - { - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of(queueFamilies, [](auto const & qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); - - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of(requiredDeviceExtension, - [&availableDeviceExtensions](auto const & requiredDeviceExtension) - { - return std::ranges::any_of(availableDeviceExtensions, - [requiredDeviceExtension](auto const & availableDeviceExtension) - { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); - }); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - }); - if (devIter != devices.end()) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error("failed to find a suitable GPU!"); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && +class MultithreadedApplication +{ + public: + void run() + { + initWindow(); + initVulkan(); + initThreads(); + mainLoop(); + cleanup(); + } + + private: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::SurfaceFormatKHR swapChainImageFormat; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + + vk::raii::DescriptorSetLayout computeDescriptorSetLayout = nullptr; + vk::raii::PipelineLayout computePipelineLayout = nullptr; + vk::raii::Pipeline computePipeline = nullptr; + + std::vector shaderStorageBuffers; + std::vector shaderStorageBuffersMemory; + + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + std::vector uniformBuffersMapped; + + vk::raii::DescriptorPool descriptorPool = nullptr; + std::vector computeDescriptorSets; + + vk::raii::CommandPool commandPool = nullptr; + std::vector graphicsCommandBuffers; + + vk::raii::Semaphore timelineSemaphore = nullptr; + uint64_t timelineValue = 0; + std::vector imageAvailableSemaphores; + std::vector inFlightFences; + uint32_t currentFrame = 0; + + double lastFrameTime = 0.0; + + // Removed resize-related variables and FSM state management as per simplification request + + double lastTime = 0.0f; + + uint32_t threadCount = 0; + std::vector workerThreads; + std::atomic shouldExit{false}; + std::vector> threadWorkReady; + std::vector> threadWorkDone; + + std::mutex queueSubmitMutex; + std::mutex workCompleteMutex; + std::condition_variable workCompleteCv; + + ThreadSafeResourceManager resourceManager; + struct ParticleGroup + { + uint32_t startIndex; + uint32_t count; + }; + std::vector particleGroups; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + // Helper functions + [[nodiscard]] static std::vector getRequiredExtensions() + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + return extensions; + } + static 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]; + } + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + for (const auto &availablePresentMode : availablePresentModes) + { + if (availablePresentMode == vk::PresentModeKHR::eMailbox) + { + return availablePresentMode; + } + } + return vk::PresentModeKHR::eFifo; + } + [[nodiscard]] vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR &capabilities) const + { + 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)}; + } + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size(), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + 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; + } + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan Multithreading", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + + lastTime = glfwGetTime(); + } + + void initVulkan() + { + createInstance(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createComputeDescriptorSetLayout(); + createGraphicsPipeline(); + createComputePipeline(); + createCommandPool(); + createShaderStorageBuffers(); + createUniformBuffers(); + createDescriptorPool(); + createComputeDescriptorSets(); + createGraphicsCommandBuffers(); + createSyncObjects(); + } + + void initThreads() + { + // Increase thread count for better parallelism + threadCount = 8u; + log("Initializing ", threadCount, " threads for sequential execution"); + + threadWorkReady = std::vector>(threadCount); + threadWorkDone = std::vector>(threadCount); + + for (uint32_t i = 0; i < threadCount; i++) + { + threadWorkReady[i] = false; + threadWorkDone[i] = true; + } + + initThreadResources(); + + const uint32_t particlesPerThread = PARTICLE_COUNT / threadCount; + particleGroups.resize(threadCount); + + for (uint32_t i = 0; i < threadCount; i++) + { + particleGroups[i].startIndex = i * particlesPerThread; + particleGroups[i].count = (i == threadCount - 1) ? + (PARTICLE_COUNT - i * particlesPerThread) : + particlesPerThread; + log("Thread ", i, " will process particles ", + particleGroups[i].startIndex, " to ", + (particleGroups[i].startIndex + particleGroups[i].count - 1), + " (count: ", particleGroups[i].count, ")"); + } + + for (uint32_t i = 0; i < threadCount; i++) + { + workerThreads.emplace_back(&MultithreadedApplication::workerThreadFunc, this, i); + log("Started worker thread ", i); + } + } + + void workerThreadFunc(uint32_t threadIndex) + { + while (!shouldExit) + { + // Wait for work using condition variable + { + std::unique_lock lock(workCompleteMutex); + workCompleteCv.wait(lock, [this, threadIndex]() { + return shouldExit || threadWorkReady[threadIndex].load(std::memory_order_acquire); + }); + + if (shouldExit) + { + break; + } + + if (!threadWorkReady[threadIndex].load(std::memory_order_acquire)) + { + continue; + } + } + + const ParticleGroup &group = particleGroups[threadIndex]; + bool workCompleted = false; + + try + { + // Get command buffer and record commands + vk::raii::CommandBuffer *cmdBuffer = &resourceManager.getCommandBuffer(threadIndex); + recordComputeCommandBuffer(*cmdBuffer, group.startIndex, group.count); + workCompleted = true; + } + catch (const std::exception &) + { + workCompleted = false; + } + + // Mark work as done + threadWorkDone[threadIndex].store(true, std::memory_order_release); + threadWorkReady[threadIndex].store(false, std::memory_order_release); + + // If this is not the last thread, signal the next thread to start + if (threadIndex < threadCount - 1) + { + threadWorkReady[threadIndex + 1].store(true, std::memory_order_release); + } + + // Notify main thread and other threads + { + std::lock_guard lock(workCompleteMutex); + workCompleteCv.notify_all(); + } + } + } + + void mainLoop() + { + const double targetFrameTime = 1.0 / 60.0; + + while (!glfwWindowShouldClose(window)) + { + double frameStartTime = glfwGetTime(); + + glfwPollEvents(); + drawFrame(); + + double currentTime = glfwGetTime(); + lastFrameTime = (currentTime - lastTime) * 1000.0; + lastTime = currentTime; + + double frameTime = currentTime - frameStartTime; + + if (frameTime < targetFrameTime) + { + double sleepTime = targetFrameTime - frameTime; + std::this_thread::sleep_for(std::chrono::duration(sleepTime)); + } + } + + device.waitIdle(); + } + + void cleanupSwapChain() + { + swapChainImageViews.clear(); + graphicsPipeline = nullptr; + pipelineLayout = nullptr; + computePipeline = nullptr; + computePipelineLayout = nullptr; + computeDescriptorSets.clear(); + computeDescriptorSetLayout = nullptr; + descriptorPool = nullptr; + + // Unmap and clean up uniform buffers + for (size_t i = 0; i < uniformBuffersMapped.size(); i++) + { + uniformBuffersMemory[i].unmapMemory(); + } + uniformBuffers.clear(); + uniformBuffersMemory.clear(); + uniformBuffersMapped.clear(); + + // Clean up shader storage buffers + shaderStorageBuffers.clear(); + shaderStorageBuffersMemory.clear(); + + swapChain = nullptr; + } + + void stopThreads() + { + shouldExit.store(true, std::memory_order_release); + + for (uint32_t i = 0; i < threadCount; i++) + { + threadWorkDone[i].store(true, std::memory_order_release); + threadWorkReady[i].store(false, std::memory_order_release); + } + + // Notify all threads in case they're waiting on the condition variable + { + std::lock_guard lock(workCompleteMutex); + workCompleteCv.notify_all(); + } + + for (auto &thread : workerThreads) + { + if (thread.joinable()) + { + thread.join(); + } + } + + workerThreads.clear(); + } + + void initThreadResources() + { + resourceManager.createThreadCommandPools(device, queueIndex, threadCount); + resourceManager.allocateCommandBuffers(device, threadCount, 1); + } + + void cleanup() + { + stopThreads(); + + glfwDestroyWindow(window); + glfwTerminate(); + } + + void createInstance() + { + constexpr vk::ApplicationInfo appInfo{.pApplicationName = "Vulkan Multithreading", + .applicationVersion = VK_MAKE_VERSION(1, 0, 0), + .pEngineName = "No Engine", + .engineVersion = VK_MAKE_VERSION(1, 0, 0), + .apiVersion = vk::ApiVersion14}; + auto extensions = getRequiredExtensions(); + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = 0, + .ppEnabledLayerNames = nullptr, + .enabledExtensionCount = static_cast(extensions.size()), + .ppEnabledExtensionNames = extensions.data()}; + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && (queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eCompute) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - auto features = physicalDevice.getFeatures2(); - features.features.samplerAnisotropy = vk::True; - vk::PhysicalDeviceVulkan13Features vulkan13Features; - vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT extendedDynamicStateFeatures; - vk::PhysicalDeviceTimelineSemaphoreFeaturesKHR timelineSemaphoreFeatures; - timelineSemaphoreFeatures.timelineSemaphore = vk::True; - vulkan13Features.dynamicRendering = vk::True; - vulkan13Features.synchronization2 = vk::True; - extendedDynamicStateFeatures.extendedDynamicState = vk::True; - extendedDynamicStateFeatures.pNext = &timelineSemaphoreFeatures; - vulkan13Features.pNext = &extendedDynamicStateFeatures; - features.pNext = &vulkan13Features; - - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; - vk::DeviceCreateInfo deviceCreateInfo{ - .pNext = &features, - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() - }; - - device = vk::raii::Device(physicalDevice, deviceCreateInfo); - queue = vk::raii::Queue(device, queueIndex, 0); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface); - swapChainImageFormat = 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::raii::SwapchainKHR oldSwapChain = std::move(swapChain); - - vk::SwapchainCreateInfoKHR swapChainCreateInfo{ - .flags = vk::SwapchainCreateFlagsKHR(), - .surface = surface, .minImageCount = minImageCount, - .imageFormat = swapChainImageFormat.format, .imageColorSpace = swapChainImageFormat.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 = *oldSwapChain ? *oldSwapChain : nullptr }; - - swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); - oldSwapChain = nullptr; - swapChainImages = swapChain.getImages(); - } - - void createImageViews() { - vk::ImageViewCreateInfo imageViewCreateInfo{ - .viewType = vk::ImageViewType::e2D, - .format = swapChainImageFormat.format, - .components = {vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity}, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } - }; - for ( auto image : swapChainImages ) - { - imageViewCreateInfo.image = image; - swapChainImageViews.emplace_back( device, imageViewCreateInfo ); - } - } - - void createComputeDescriptorSetLayout() { - std::array layoutBindings{ - vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eCompute, nullptr), - vk::DescriptorSetLayoutBinding(1, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eCompute, nullptr), - vk::DescriptorSetLayoutBinding(2, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eCompute, nullptr) - }; - - vk::DescriptorSetLayoutCreateInfo layoutInfo{ .bindingCount = static_cast(layoutBindings.size()), .pBindings = layoutBindings.data() }; - computeDescriptorSetLayout = vk::raii::DescriptorSetLayout( device, layoutInfo ); - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; - - auto bindingDescription = Particle::getBindingDescription(); - auto attributeDescriptions = Particle::getAttributeDescriptions(); - - vk::PipelineVertexInputStateCreateInfo vertexInputInfo{ .vertexBindingDescriptionCount = 1, .pVertexBindingDescriptions = &bindingDescription, .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), .pVertexAttributeDescriptions = attributeDescriptions.data() }; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ .topology = vk::PrimitiveTopology::ePointList, .primitiveRestartEnable = vk::False }; - vk::PipelineViewportStateCreateInfo viewportState{ .viewportCount = 1, .scissorCount = 1 }; - vk::PipelineRasterizationStateCreateInfo rasterizer{ - .depthClampEnable = vk::False, - .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, - .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eCounterClockwise, - .depthBiasEnable = vk::False, - .lineWidth = 1.0f - }; - vk::PipelineMultisampleStateCreateInfo multisampling{ .rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False }; - - vk::PipelineColorBlendAttachmentState colorBlendAttachment{ - .blendEnable = vk::True, - .srcColorBlendFactor = vk::BlendFactor::eSrcAlpha, - .dstColorBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha, - .colorBlendOp = vk::BlendOp::eAdd, - .srcAlphaBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha, - .dstAlphaBlendFactor = vk::BlendFactor::eZero, - .alphaBlendOp = vk::BlendOp::eAdd, - .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA, - }; - - vk::PipelineColorBlendStateCreateInfo colorBlending{ .logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo; - pipelineLayout = vk::raii::PipelineLayout( device, pipelineLayoutInfo ); - - vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ .colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainImageFormat.format }; - vk::GraphicsPipelineCreateInfo pipelineInfo{ .pNext = &pipelineRenderingCreateInfo, - .stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = *pipelineLayout, - .subpass = 0 - }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - void createComputePipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.spv")); - - // Create push constant range for particle group information - vk::PushConstantRange pushConstantRange{ - .stageFlags = vk::ShaderStageFlagBits::eCompute, - .offset = 0, - .size = sizeof(uint32_t) * 2 // startIndex and count - }; - - vk::PipelineShaderStageCreateInfo computeShaderStageInfo{ .stage = vk::ShaderStageFlagBits::eCompute, .module = shaderModule, .pName = "compMain" }; - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ - .setLayoutCount = 1, - .pSetLayouts = &*computeDescriptorSetLayout, - .pushConstantRangeCount = 1, - .pPushConstantRanges = &pushConstantRange - }; - computePipelineLayout = vk::raii::PipelineLayout( device, pipelineLayoutInfo ); - vk::ComputePipelineCreateInfo pipelineInfo{ .stage = computeShaderStageInfo, .layout = *computePipelineLayout }; - computePipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{}; - poolInfo.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer; - poolInfo.queueFamilyIndex = queueIndex; - commandPool = vk::raii::CommandPool(device, poolInfo); - } - - void createShaderStorageBuffers() { - std::default_random_engine rndEngine(static_cast(time(nullptr))); - std::uniform_real_distribution rndDist(0.0f, 1.0f); - - std::vector particles(PARTICLE_COUNT); - for (auto& particle : particles) { - // Generate a random position for the particle - float theta = rndDist(rndEngine) * 2.0f * 3.14159265358979323846f; - - // Use square root of random value to ensure uniform distribution across the area - // This prevents clustering near the center (which causes the donut effect) - float r = sqrtf(rndDist(rndEngine)) * 0.25f; - - float x = r * cosf(theta) * HEIGHT / WIDTH; - float y = r * sinf(theta); - particle.position = glm::vec2(x, y); - - // Ensure a minimum velocity and scale based on distance from center - float minVelocity = 0.001f; - float velocityScale = 0.003f; - float velocityMagnitude = std::max(minVelocity, r * velocityScale); - particle.velocity = normalize(glm::vec2(x,y)) * velocityMagnitude; - particle.color = glm::vec4(rndDist(rndEngine), rndDist(rndEngine), rndDist(rndEngine), 1.0f); - } - - vk::DeviceSize bufferSize = sizeof(Particle) * PARTICLE_COUNT; - - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(dataStaging, particles.data(), (size_t)bufferSize); - stagingBufferMemory.unmapMemory(); - - shaderStorageBuffers.clear(); - shaderStorageBuffersMemory.clear(); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::raii::Buffer shaderStorageBufferTemp({}); - vk::raii::DeviceMemory shaderStorageBufferTempMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eStorageBuffer | vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eTransferDst, vk::MemoryPropertyFlagBits::eDeviceLocal, shaderStorageBufferTemp, shaderStorageBufferTempMemory); - copyBuffer(stagingBuffer, shaderStorageBufferTemp, bufferSize); - shaderStorageBuffers.emplace_back(std::move(shaderStorageBufferTemp)); - shaderStorageBuffersMemory.emplace_back(std::move(shaderStorageBufferTempMemory)); - } - } - - void createUniformBuffers() { - uniformBuffers.clear(); - uniformBuffersMemory.clear(); - uniformBuffersMapped.clear(); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DeviceSize bufferSize = sizeof(UniformBufferObject); - vk::raii::Buffer buffer({}); - vk::raii::DeviceMemory bufferMem({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); - uniformBuffers.emplace_back(std::move(buffer)); - uniformBuffersMemory.emplace_back(std::move(bufferMem)); - uniformBuffersMapped.emplace_back( uniformBuffersMemory[i].mapMemory(0, bufferSize)); - } - } - - void createDescriptorPool() { - std::array poolSize { - vk::DescriptorPoolSize( vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT), - vk::DescriptorPoolSize( vk::DescriptorType::eStorageBuffer, MAX_FRAMES_IN_FLIGHT * 2) - }; - vk::DescriptorPoolCreateInfo poolInfo{}; - poolInfo.flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet; - poolInfo.maxSets = MAX_FRAMES_IN_FLIGHT; - poolInfo.poolSizeCount = poolSize.size(); - poolInfo.pPoolSizes = poolSize.data(); - descriptorPool = vk::raii::DescriptorPool(device, poolInfo); - } - - void createComputeDescriptorSets() { - std::vector layouts(MAX_FRAMES_IN_FLIGHT, computeDescriptorSetLayout); - vk::DescriptorSetAllocateInfo allocInfo{}; - allocInfo.descriptorPool = *descriptorPool; - allocInfo.descriptorSetCount = MAX_FRAMES_IN_FLIGHT; - allocInfo.pSetLayouts = layouts.data(); - computeDescriptorSets.clear(); - computeDescriptorSets = device.allocateDescriptorSets(allocInfo); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DescriptorBufferInfo bufferInfo(uniformBuffers[i], 0, sizeof(UniformBufferObject)); - - vk::DescriptorBufferInfo storageBufferInfoLastFrame(shaderStorageBuffers[(i + MAX_FRAMES_IN_FLIGHT - 1) % MAX_FRAMES_IN_FLIGHT], 0, sizeof(Particle) * PARTICLE_COUNT); - vk::DescriptorBufferInfo storageBufferInfoCurrentFrame(shaderStorageBuffers[i], 0, sizeof(Particle) * PARTICLE_COUNT); - std::array descriptorWrites{ - vk::WriteDescriptorSet{ .dstSet = *computeDescriptorSets[i], .dstBinding = 0, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eUniformBuffer, .pImageInfo = nullptr, .pBufferInfo = &bufferInfo, .pTexelBufferView = nullptr }, - vk::WriteDescriptorSet{ .dstSet = *computeDescriptorSets[i], .dstBinding = 1, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eStorageBuffer, .pImageInfo = nullptr, .pBufferInfo = &storageBufferInfoLastFrame, .pTexelBufferView = nullptr }, - vk::WriteDescriptorSet{ .dstSet = *computeDescriptorSets[i], .dstBinding = 2, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eStorageBuffer, .pImageInfo = nullptr, .pBufferInfo = &storageBufferInfoCurrentFrame, .pTexelBufferView = nullptr }, + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + auto features = physicalDevice.getFeatures2(); + features.features.samplerAnisotropy = vk::True; + vk::PhysicalDeviceVulkan13Features vulkan13Features; + vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT extendedDynamicStateFeatures; + vk::PhysicalDeviceTimelineSemaphoreFeaturesKHR timelineSemaphoreFeatures; + timelineSemaphoreFeatures.timelineSemaphore = vk::True; + vulkan13Features.dynamicRendering = vk::True; + vulkan13Features.synchronization2 = vk::True; + extendedDynamicStateFeatures.extendedDynamicState = vk::True; + extendedDynamicStateFeatures.pNext = &timelineSemaphoreFeatures; + vulkan13Features.pNext = &extendedDynamicStateFeatures; + features.pNext = &vulkan13Features; + + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{ + .pNext = &features, + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface); + swapChainImageFormat = 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::raii::SwapchainKHR oldSwapChain = std::move(swapChain); + + vk::SwapchainCreateInfoKHR swapChainCreateInfo{ + .flags = vk::SwapchainCreateFlagsKHR(), + .surface = surface, + .minImageCount = minImageCount, + .imageFormat = swapChainImageFormat.format, + .imageColorSpace = swapChainImageFormat.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 = *oldSwapChain ? *oldSwapChain : nullptr}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + oldSwapChain = nullptr; + swapChainImages = swapChain.getImages(); + } + + void createImageViews() + { + vk::ImageViewCreateInfo imageViewCreateInfo{ + .viewType = vk::ImageViewType::e2D, + .format = swapChainImageFormat.format, + .components = {vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity}, + .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + for (auto image : swapChainImages) + { + imageViewCreateInfo.image = image; + swapChainImageViews.emplace_back(device, imageViewCreateInfo); + } + } + + void createComputeDescriptorSetLayout() + { + std::array layoutBindings{ + vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eCompute, nullptr), + vk::DescriptorSetLayoutBinding(1, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eCompute, nullptr), + vk::DescriptorSetLayoutBinding(2, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eCompute, nullptr)}; + + vk::DescriptorSetLayoutCreateInfo layoutInfo{.bindingCount = static_cast(layoutBindings.size()), .pBindings = layoutBindings.data()}; + computeDescriptorSetLayout = vk::raii::DescriptorSetLayout(device, layoutInfo); + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; + + auto bindingDescription = Particle::getBindingDescription(); + auto attributeDescriptions = Particle::getAttributeDescriptions(); + + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{.vertexBindingDescriptionCount = 1, .pVertexBindingDescriptions = &bindingDescription, .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), .pVertexAttributeDescriptions = attributeDescriptions.data()}; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{.topology = vk::PrimitiveTopology::ePointList, .primitiveRestartEnable = vk::False}; + vk::PipelineViewportStateCreateInfo viewportState{.viewportCount = 1, .scissorCount = 1}; + vk::PipelineRasterizationStateCreateInfo rasterizer{ + .depthClampEnable = vk::False, + .rasterizerDiscardEnable = vk::False, + .polygonMode = vk::PolygonMode::eFill, + .cullMode = vk::CullModeFlagBits::eBack, + .frontFace = vk::FrontFace::eCounterClockwise, + .depthBiasEnable = vk::False, + .lineWidth = 1.0f}; + vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; + + vk::PipelineColorBlendAttachmentState colorBlendAttachment{ + .blendEnable = vk::True, + .srcColorBlendFactor = vk::BlendFactor::eSrcAlpha, + .dstColorBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha, + .colorBlendOp = vk::BlendOp::eAdd, + .srcAlphaBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha, + .dstAlphaBlendFactor = vk::BlendFactor::eZero, + .alphaBlendOp = vk::BlendOp::eAdd, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA, + }; + + vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo; + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainImageFormat.format}; + vk::GraphicsPipelineCreateInfo pipelineInfo{.pNext = &pipelineRenderingCreateInfo, + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = *pipelineLayout, + .subpass = 0}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + void createComputePipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.spv")); + + // Create push constant range for particle group information + vk::PushConstantRange pushConstantRange{ + .stageFlags = vk::ShaderStageFlagBits::eCompute, + .offset = 0, + .size = sizeof(uint32_t) * 2 // startIndex and count + }; + + vk::PipelineShaderStageCreateInfo computeShaderStageInfo{.stage = vk::ShaderStageFlagBits::eCompute, .module = shaderModule, .pName = "compMain"}; + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ + .setLayoutCount = 1, + .pSetLayouts = &*computeDescriptorSetLayout, + .pushConstantRangeCount = 1, + .pPushConstantRanges = &pushConstantRange}; + computePipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + vk::ComputePipelineCreateInfo pipelineInfo{.stage = computeShaderStageInfo, .layout = *computePipelineLayout}; + computePipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{}; + poolInfo.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer; + poolInfo.queueFamilyIndex = queueIndex; + commandPool = vk::raii::CommandPool(device, poolInfo); + } + + void createShaderStorageBuffers() + { + std::default_random_engine rndEngine(static_cast(time(nullptr))); + std::uniform_real_distribution rndDist(0.0f, 1.0f); + + std::vector particles(PARTICLE_COUNT); + for (auto &particle : particles) + { + // Generate a random position for the particle + float theta = rndDist(rndEngine) * 2.0f * 3.14159265358979323846f; + + // Use square root of random value to ensure uniform distribution across the area + // This prevents clustering near the center (which causes the donut effect) + float r = sqrtf(rndDist(rndEngine)) * 0.25f; + + float x = r * cosf(theta) * HEIGHT / WIDTH; + float y = r * sinf(theta); + particle.position = glm::vec2(x, y); + + // Ensure a minimum velocity and scale based on distance from center + float minVelocity = 0.001f; + float velocityScale = 0.003f; + float velocityMagnitude = std::max(minVelocity, r * velocityScale); + particle.velocity = normalize(glm::vec2(x, y)) * velocityMagnitude; + particle.color = glm::vec4(rndDist(rndEngine), rndDist(rndEngine), rndDist(rndEngine), 1.0f); + } + + vk::DeviceSize bufferSize = sizeof(Particle) * PARTICLE_COUNT; + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(dataStaging, particles.data(), (size_t) bufferSize); + stagingBufferMemory.unmapMemory(); + + shaderStorageBuffers.clear(); + shaderStorageBuffersMemory.clear(); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::raii::Buffer shaderStorageBufferTemp({}); + vk::raii::DeviceMemory shaderStorageBufferTempMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eStorageBuffer | vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eTransferDst, vk::MemoryPropertyFlagBits::eDeviceLocal, shaderStorageBufferTemp, shaderStorageBufferTempMemory); + copyBuffer(stagingBuffer, shaderStorageBufferTemp, bufferSize); + shaderStorageBuffers.emplace_back(std::move(shaderStorageBufferTemp)); + shaderStorageBuffersMemory.emplace_back(std::move(shaderStorageBufferTempMemory)); + } + } + + void createUniformBuffers() + { + uniformBuffers.clear(); + uniformBuffersMemory.clear(); + uniformBuffersMapped.clear(); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DeviceSize bufferSize = sizeof(UniformBufferObject); + vk::raii::Buffer buffer({}); + vk::raii::DeviceMemory bufferMem({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); + uniformBuffers.emplace_back(std::move(buffer)); + uniformBuffersMemory.emplace_back(std::move(bufferMem)); + uniformBuffersMapped.emplace_back(uniformBuffersMemory[i].mapMemory(0, bufferSize)); + } + } + + void createDescriptorPool() + { + std::array poolSize{ + vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT), + vk::DescriptorPoolSize(vk::DescriptorType::eStorageBuffer, MAX_FRAMES_IN_FLIGHT * 2)}; + vk::DescriptorPoolCreateInfo poolInfo{}; + poolInfo.flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet; + poolInfo.maxSets = MAX_FRAMES_IN_FLIGHT; + poolInfo.poolSizeCount = poolSize.size(); + poolInfo.pPoolSizes = poolSize.data(); + descriptorPool = vk::raii::DescriptorPool(device, poolInfo); + } + + void createComputeDescriptorSets() + { + std::vector layouts(MAX_FRAMES_IN_FLIGHT, computeDescriptorSetLayout); + vk::DescriptorSetAllocateInfo allocInfo{}; + allocInfo.descriptorPool = *descriptorPool; + allocInfo.descriptorSetCount = MAX_FRAMES_IN_FLIGHT; + allocInfo.pSetLayouts = layouts.data(); + computeDescriptorSets.clear(); + computeDescriptorSets = device.allocateDescriptorSets(allocInfo); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DescriptorBufferInfo bufferInfo(uniformBuffers[i], 0, sizeof(UniformBufferObject)); + + vk::DescriptorBufferInfo storageBufferInfoLastFrame(shaderStorageBuffers[(i + MAX_FRAMES_IN_FLIGHT - 1) % MAX_FRAMES_IN_FLIGHT], 0, sizeof(Particle) * PARTICLE_COUNT); + vk::DescriptorBufferInfo storageBufferInfoCurrentFrame(shaderStorageBuffers[i], 0, sizeof(Particle) * PARTICLE_COUNT); + std::array descriptorWrites{ + vk::WriteDescriptorSet{.dstSet = *computeDescriptorSets[i], .dstBinding = 0, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eUniformBuffer, .pImageInfo = nullptr, .pBufferInfo = &bufferInfo, .pTexelBufferView = nullptr}, + vk::WriteDescriptorSet{.dstSet = *computeDescriptorSets[i], .dstBinding = 1, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eStorageBuffer, .pImageInfo = nullptr, .pBufferInfo = &storageBufferInfoLastFrame, .pTexelBufferView = nullptr}, + vk::WriteDescriptorSet{.dstSet = *computeDescriptorSets[i], .dstBinding = 2, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eStorageBuffer, .pImageInfo = nullptr, .pBufferInfo = &storageBufferInfoCurrentFrame, .pTexelBufferView = nullptr}, }; - device.updateDescriptorSets(descriptorWrites, {}); - } - } - - void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer& buffer, vk::raii::DeviceMemory& bufferMemory) const { - vk::BufferCreateInfo bufferInfo{}; - bufferInfo.size = size; - bufferInfo.usage = usage; - bufferInfo.sharingMode = vk::SharingMode::eExclusive; - buffer = vk::raii::Buffer(device, bufferInfo); - vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{}; - allocInfo.allocationSize = memRequirements.size; - allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); - bufferMemory = vk::raii::DeviceMemory(device, allocInfo); - buffer.bindMemory(bufferMemory, 0); - } - - [[nodiscard]] vk::raii::CommandBuffer beginSingleTimeCommands() const { - vk::CommandBufferAllocateInfo allocInfo{}; - allocInfo.commandPool = *commandPool; - allocInfo.level = vk::CommandBufferLevel::ePrimary; - allocInfo.commandBufferCount = 1; - vk::raii::CommandBuffer commandBuffer = std::move(vk::raii::CommandBuffers( device, allocInfo ).front()); - - vk::CommandBufferBeginInfo beginInfo{ .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit }; - commandBuffer.begin(beginInfo); - - return commandBuffer; - } - - void endSingleTimeCommands(const vk::raii::CommandBuffer& commandBuffer) const { - commandBuffer.end(); - - vk::SubmitInfo submitInfo{}; - submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &*commandBuffer; - queue.submit(submitInfo, nullptr); - queue.waitIdle(); - } - - void copyBuffer(const vk::raii::Buffer & srcBuffer, const vk::raii::Buffer & dstBuffer, vk::DeviceSize size) const { - vk::raii::CommandBuffer commandCopyBuffer = beginSingleTimeCommands(); - commandCopyBuffer.copyBuffer(srcBuffer, dstBuffer, vk::BufferCopy(0, 0, size)); - endSingleTimeCommands(commandCopyBuffer); - } - - [[nodiscard]] uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) const { - vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); - - for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { - if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { - return i; - } - } - - throw std::runtime_error("failed to find suitable memory type!"); - } - - void createGraphicsCommandBuffers() { - graphicsCommandBuffers.clear(); - vk::CommandBufferAllocateInfo allocInfo{}; - allocInfo.commandPool = *commandPool; - allocInfo.level = vk::CommandBufferLevel::ePrimary; - allocInfo.commandBufferCount = MAX_FRAMES_IN_FLIGHT; - graphicsCommandBuffers = vk::raii::CommandBuffers(device, allocInfo); - } - - void recordComputeCommandBuffer(vk::raii::CommandBuffer& cmdBuffer, uint32_t startIndex, uint32_t count) { - cmdBuffer.reset(); - - vk::CommandBufferBeginInfo beginInfo{ - .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit - }; - cmdBuffer.begin(beginInfo); - - cmdBuffer.bindPipeline(vk::PipelineBindPoint::eCompute, *computePipeline); - cmdBuffer.bindDescriptorSets(vk::PipelineBindPoint::eCompute, *computePipelineLayout, 0, {*computeDescriptorSets[currentFrame]}, {}); - - struct PushConstants { - uint32_t startIndex; - uint32_t count; - } pushConstants{startIndex, count}; - - cmdBuffer.pushConstants(*computePipelineLayout, vk::ShaderStageFlagBits::eCompute, 0, pushConstants); - - uint32_t groupCount = (count + 255) / 256; - cmdBuffer.dispatch(groupCount, 1, 1); - - cmdBuffer.end(); - } - - void recordGraphicsCommandBuffer(uint32_t imageIndex) { - graphicsCommandBuffers[currentFrame].reset(); - - vk::CommandBufferBeginInfo beginInfo{ - .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit - }; - graphicsCommandBuffers[currentFrame].begin(beginInfo); - - transition_image_layout( - imageIndex, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, - vk::AccessFlagBits2::eColorAttachmentWrite, - vk::PipelineStageFlagBits2::eTopOfPipe, - vk::PipelineStageFlagBits2::eColorAttachmentOutput - ); - - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::RenderingAttachmentInfo attachmentInfo = { - .imageView = swapChainImageViews[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor - }; - vk::RenderingInfo renderingInfo = { - .renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &attachmentInfo - }; - - graphicsCommandBuffers[currentFrame].beginRendering(renderingInfo); - - graphicsCommandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - graphicsCommandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - graphicsCommandBuffers[currentFrame].setScissor( 0, vk::Rect2D( vk::Offset2D( 0, 0 ), swapChainExtent ) ); - graphicsCommandBuffers[currentFrame].bindVertexBuffers(0, { shaderStorageBuffers[currentFrame] }, {0}); - graphicsCommandBuffers[currentFrame].draw( PARTICLE_COUNT, 1, 0, 0 ); - graphicsCommandBuffers[currentFrame].endRendering(); - - transition_image_layout( - imageIndex, - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, - {}, - vk::PipelineStageFlagBits2::eColorAttachmentOutput, - vk::PipelineStageFlagBits2::eBottomOfPipe - ); - - graphicsCommandBuffers[currentFrame].end(); - } - - void transition_image_layout( - uint32_t imageIndex, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::AccessFlags2 src_access_mask, - vk::AccessFlags2 dst_access_mask, - vk::PipelineStageFlags2 src_stage_mask, - vk::PipelineStageFlags2 dst_stage_mask - ) { - vk::ImageMemoryBarrier2 barrier = { - .srcStageMask = src_stage_mask, - .srcAccessMask = src_access_mask, - .dstStageMask = dst_stage_mask, - .dstAccessMask = dst_access_mask, - .oldLayout = old_layout, - .newLayout = new_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - graphicsCommandBuffers[currentFrame].pipelineBarrier2(dependency_info); - } - - void signalThreadsToWork() { - // Mark all threads as not done - for (uint32_t i = 0; i < threadCount; i++) { - threadWorkDone[i].store(false, std::memory_order_release); - } - - // Memory barrier to ensure all threads see the updated threadWorkDone values - std::atomic_thread_fence(std::memory_order_seq_cst); - - // Only signal the first thread to start work - threadWorkReady[0].store(true, std::memory_order_release); - - // Notify all threads in case they're waiting on the condition variable - { - std::lock_guard lock(workCompleteMutex); - workCompleteCv.notify_all(); - } - } - - void waitForThreadsToComplete() { - std::unique_lock lock(workCompleteMutex); - - // Wait for the last thread to complete with a timeout - auto waitResult = workCompleteCv.wait_for(lock, std::chrono::milliseconds(3000), [this]() { - return threadWorkDone[threadCount - 1].load(std::memory_order_acquire); - }); - - // If we timed out, force completion - if (!waitResult) { - // Force all threads to complete - for (uint32_t i = 0; i < threadCount; i++) { - threadWorkDone[i].store(true, std::memory_order_release); - threadWorkReady[i].store(false, std::memory_order_release); - } - - // Notify all threads - workCompleteCv.notify_all(); - lock.unlock(); - - // Give threads a chance to respond to the forced completion - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } - } - - void createSyncObjects() { - imageAvailableSemaphores.clear(); - inFlightFences.clear(); - - vk::SemaphoreTypeCreateInfo semaphoreType{ .semaphoreType = vk::SemaphoreType::eTimeline, .initialValue = 0 }; - timelineSemaphore = vk::raii::Semaphore(device, {.pNext = &semaphoreType}); - timelineValue = 0; - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - imageAvailableSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); - - vk::FenceCreateInfo fenceInfo; - fenceInfo.flags = vk::FenceCreateFlagBits::eSignaled; - inFlightFences.emplace_back(device, fenceInfo); - } - } - - void updateUniformBuffer(uint32_t currentImage) { - UniformBufferObject ubo{}; - ubo.deltaTime = static_cast(lastFrameTime) * 2.0f; - - memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); - } - - void drawFrame() { - // Wait for the previous frame to finish - while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) - ; - device.resetFences(*inFlightFences[currentFrame]); - - // Acquire the next image - auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *imageAvailableSemaphores[currentFrame], nullptr); - - // Update timeline values for synchronization - uint64_t computeWaitValue = timelineValue; - uint64_t computeSignalValue = ++timelineValue; - uint64_t graphicsWaitValue = computeSignalValue; - uint64_t graphicsSignalValue = ++timelineValue; - - // Update uniform buffer with the latest delta time - updateUniformBuffer(currentFrame); - - // Signal worker threads to start processing particles - signalThreadsToWork(); - - // Record graphics command buffer while worker threads are busy - recordGraphicsCommandBuffer(imageIndex); - - // Wait for all worker threads to complete - waitForThreadsToComplete(); - - // Collect command buffers from all threads - std::vector computeCmdBuffers; - computeCmdBuffers.reserve(threadCount); - for (uint32_t i = 0; i < threadCount; i++) { - try { - computeCmdBuffers.push_back(*resourceManager.getCommandBuffer(i)); - } catch (const std::exception&) { - // Skip this thread's command buffer if there was an error - } - } - - // Ensure we have at least one command buffer - if (computeCmdBuffers.empty()) { - return; - } - - // Set up compute submission - vk::TimelineSemaphoreSubmitInfo computeTimelineInfo{ - .waitSemaphoreValueCount = 1, - .pWaitSemaphoreValues = &computeWaitValue, - .signalSemaphoreValueCount = 1, - .pSignalSemaphoreValues = &computeSignalValue - }; - - vk::PipelineStageFlags waitStages[] = {vk::PipelineStageFlagBits::eComputeShader}; - - vk::SubmitInfo computeSubmitInfo{ - .pNext = &computeTimelineInfo, - .waitSemaphoreCount = 1, - .pWaitSemaphores = &*timelineSemaphore, - .pWaitDstStageMask = waitStages, - .commandBufferCount = static_cast(computeCmdBuffers.size()), - .pCommandBuffers = computeCmdBuffers.data(), - .signalSemaphoreCount = 1, - .pSignalSemaphores = &*timelineSemaphore - }; - - // Submit compute work - { - std::lock_guard lock(queueSubmitMutex); - queue.submit(computeSubmitInfo, nullptr); - } - - // Set up graphics submission - vk::PipelineStageFlags graphicsWaitStages[] = {vk::PipelineStageFlagBits::eVertexInput, vk::PipelineStageFlagBits::eColorAttachmentOutput}; - - std::array waitSemaphores = {*timelineSemaphore, *imageAvailableSemaphores[currentFrame]}; - std::array waitSemaphoreValues = {graphicsWaitValue, 0}; - - vk::TimelineSemaphoreSubmitInfo graphicsTimelineInfo{ - .waitSemaphoreValueCount = static_cast(waitSemaphoreValues.size()), - .pWaitSemaphoreValues = waitSemaphoreValues.data(), - .signalSemaphoreValueCount = 1, - .pSignalSemaphoreValues = &graphicsSignalValue - }; - - vk::SubmitInfo graphicsSubmitInfo{ - .pNext = &graphicsTimelineInfo, - .waitSemaphoreCount = static_cast(waitSemaphores.size()), - .pWaitSemaphores = waitSemaphores.data(), - .pWaitDstStageMask = graphicsWaitStages, - .commandBufferCount = 1, - .pCommandBuffers = &*graphicsCommandBuffers[currentFrame], - .signalSemaphoreCount = 1, - .pSignalSemaphores = &*timelineSemaphore - }; - - // Submit graphics work - { - std::lock_guard lock(queueSubmitMutex); - queue.submit(graphicsSubmitInfo, *inFlightFences[currentFrame]); - } - - // Wait for graphics to complete before presenting - vk::SemaphoreWaitInfo waitInfo{ - .semaphoreCount = 1, - .pSemaphores = &*timelineSemaphore, - .pValues = &graphicsSignalValue - }; - - auto waitResult = device.waitSemaphores(waitInfo, 5000000000); - if (waitResult == vk::Result::eTimeout) { - device.waitIdle(); - return; - } - - // Present the image - vk::PresentInfoKHR presentInfo{ - .waitSemaphoreCount = 0, - .pWaitSemaphores = nullptr, - .swapchainCount = 1, - .pSwapchains = &*swapChain, - .pImageIndices = &imageIndex - }; - - result = queue.presentKHR(presentInfo); - - // Move to the next frame - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; - } - + device.updateDescriptorSets(descriptorWrites, {}); + } + } + + void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer &buffer, vk::raii::DeviceMemory &bufferMemory) const + { + vk::BufferCreateInfo bufferInfo{}; + bufferInfo.size = size; + bufferInfo.usage = usage; + bufferInfo.sharingMode = vk::SharingMode::eExclusive; + buffer = vk::raii::Buffer(device, bufferInfo); + vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{}; + allocInfo.allocationSize = memRequirements.size; + allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); + bufferMemory = vk::raii::DeviceMemory(device, allocInfo); + buffer.bindMemory(bufferMemory, 0); + } + + [[nodiscard]] vk::raii::CommandBuffer beginSingleTimeCommands() const + { + vk::CommandBufferAllocateInfo allocInfo{}; + allocInfo.commandPool = *commandPool; + allocInfo.level = vk::CommandBufferLevel::ePrimary; + allocInfo.commandBufferCount = 1; + vk::raii::CommandBuffer commandBuffer = std::move(vk::raii::CommandBuffers(device, allocInfo).front()); + + vk::CommandBufferBeginInfo beginInfo{.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; + commandBuffer.begin(beginInfo); + + return commandBuffer; + } + + void endSingleTimeCommands(const vk::raii::CommandBuffer &commandBuffer) const + { + commandBuffer.end(); + + vk::SubmitInfo submitInfo{}; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &*commandBuffer; + queue.submit(submitInfo, nullptr); + queue.waitIdle(); + } + + void copyBuffer(const vk::raii::Buffer &srcBuffer, const vk::raii::Buffer &dstBuffer, vk::DeviceSize size) const + { + vk::raii::CommandBuffer commandCopyBuffer = beginSingleTimeCommands(); + commandCopyBuffer.copyBuffer(srcBuffer, dstBuffer, vk::BufferCopy(0, 0, size)); + endSingleTimeCommands(commandCopyBuffer); + } + + [[nodiscard]] uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) const + { + vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) + { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) + { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + void createGraphicsCommandBuffers() + { + graphicsCommandBuffers.clear(); + vk::CommandBufferAllocateInfo allocInfo{}; + allocInfo.commandPool = *commandPool; + allocInfo.level = vk::CommandBufferLevel::ePrimary; + allocInfo.commandBufferCount = MAX_FRAMES_IN_FLIGHT; + graphicsCommandBuffers = vk::raii::CommandBuffers(device, allocInfo); + } + + void recordComputeCommandBuffer(vk::raii::CommandBuffer &cmdBuffer, uint32_t startIndex, uint32_t count) + { + cmdBuffer.reset(); + + vk::CommandBufferBeginInfo beginInfo{ + .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; + cmdBuffer.begin(beginInfo); + + cmdBuffer.bindPipeline(vk::PipelineBindPoint::eCompute, *computePipeline); + cmdBuffer.bindDescriptorSets(vk::PipelineBindPoint::eCompute, *computePipelineLayout, 0, {*computeDescriptorSets[currentFrame]}, {}); + + struct PushConstants + { + uint32_t startIndex; + uint32_t count; + } pushConstants{startIndex, count}; + + cmdBuffer.pushConstants(*computePipelineLayout, vk::ShaderStageFlagBits::eCompute, 0, pushConstants); + + uint32_t groupCount = (count + 255) / 256; + cmdBuffer.dispatch(groupCount, 1, 1); + + cmdBuffer.end(); + } + + void recordGraphicsCommandBuffer(uint32_t imageIndex) + { + graphicsCommandBuffers[currentFrame].reset(); + + vk::CommandBufferBeginInfo beginInfo{ + .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; + graphicsCommandBuffers[currentFrame].begin(beginInfo); + + transition_image_layout( + imageIndex, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, + vk::AccessFlagBits2::eColorAttachmentWrite, + vk::PipelineStageFlagBits2::eTopOfPipe, + vk::PipelineStageFlagBits2::eColorAttachmentOutput); + + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::RenderingAttachmentInfo attachmentInfo = { + .imageView = swapChainImageViews[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor}; + vk::RenderingInfo renderingInfo = { + .renderArea = {.offset = {0, 0}, .extent = swapChainExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &attachmentInfo}; + + graphicsCommandBuffers[currentFrame].beginRendering(renderingInfo); + + graphicsCommandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + graphicsCommandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + graphicsCommandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + graphicsCommandBuffers[currentFrame].bindVertexBuffers(0, {shaderStorageBuffers[currentFrame]}, {0}); + graphicsCommandBuffers[currentFrame].draw(PARTICLE_COUNT, 1, 0, 0); + graphicsCommandBuffers[currentFrame].endRendering(); + + transition_image_layout( + imageIndex, + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, + {}, + vk::PipelineStageFlagBits2::eColorAttachmentOutput, + vk::PipelineStageFlagBits2::eBottomOfPipe); + + graphicsCommandBuffers[currentFrame].end(); + } + + void transition_image_layout( + uint32_t imageIndex, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout, + vk::AccessFlags2 src_access_mask, + vk::AccessFlags2 dst_access_mask, + vk::PipelineStageFlags2 src_stage_mask, + vk::PipelineStageFlags2 dst_stage_mask) + { + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = src_stage_mask, + .srcAccessMask = src_access_mask, + .dstStageMask = dst_stage_mask, + .dstAccessMask = dst_access_mask, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo dependency_info = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + graphicsCommandBuffers[currentFrame].pipelineBarrier2(dependency_info); + } + + void signalThreadsToWork() + { + // Mark all threads as not done + for (uint32_t i = 0; i < threadCount; i++) + { + threadWorkDone[i].store(false, std::memory_order_release); + } + + // Memory barrier to ensure all threads see the updated threadWorkDone values + std::atomic_thread_fence(std::memory_order_seq_cst); + + // Only signal the first thread to start work + threadWorkReady[0].store(true, std::memory_order_release); + + // Notify all threads in case they're waiting on the condition variable + { + std::lock_guard lock(workCompleteMutex); + workCompleteCv.notify_all(); + } + } + + void waitForThreadsToComplete() + { + std::unique_lock lock(workCompleteMutex); + + // Wait for the last thread to complete with a timeout + auto waitResult = workCompleteCv.wait_for(lock, std::chrono::milliseconds(3000), [this]() { + return threadWorkDone[threadCount - 1].load(std::memory_order_acquire); + }); + + // If we timed out, force completion + if (!waitResult) + { + // Force all threads to complete + for (uint32_t i = 0; i < threadCount; i++) + { + threadWorkDone[i].store(true, std::memory_order_release); + threadWorkReady[i].store(false, std::memory_order_release); + } + + // Notify all threads + workCompleteCv.notify_all(); + lock.unlock(); + + // Give threads a chance to respond to the forced completion + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + } + + void createSyncObjects() + { + imageAvailableSemaphores.clear(); + inFlightFences.clear(); + + vk::SemaphoreTypeCreateInfo semaphoreType{.semaphoreType = vk::SemaphoreType::eTimeline, .initialValue = 0}; + timelineSemaphore = vk::raii::Semaphore(device, {.pNext = &semaphoreType}); + timelineValue = 0; + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + imageAvailableSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); + + vk::FenceCreateInfo fenceInfo; + fenceInfo.flags = vk::FenceCreateFlagBits::eSignaled; + inFlightFences.emplace_back(device, fenceInfo); + } + } + + void updateUniformBuffer(uint32_t currentImage) + { + UniformBufferObject ubo{}; + ubo.deltaTime = static_cast(lastFrameTime) * 2.0f; + + memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); + } + + void drawFrame() + { + // Wait for the previous frame to finish + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + ; + device.resetFences(*inFlightFences[currentFrame]); + + // Acquire the next image + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *imageAvailableSemaphores[currentFrame], nullptr); + + // Update timeline values for synchronization + uint64_t computeWaitValue = timelineValue; + uint64_t computeSignalValue = ++timelineValue; + uint64_t graphicsWaitValue = computeSignalValue; + uint64_t graphicsSignalValue = ++timelineValue; + + // Update uniform buffer with the latest delta time + updateUniformBuffer(currentFrame); + + // Signal worker threads to start processing particles + signalThreadsToWork(); + + // Record graphics command buffer while worker threads are busy + recordGraphicsCommandBuffer(imageIndex); + + // Wait for all worker threads to complete + waitForThreadsToComplete(); + + // Collect command buffers from all threads + std::vector computeCmdBuffers; + computeCmdBuffers.reserve(threadCount); + for (uint32_t i = 0; i < threadCount; i++) + { + try + { + computeCmdBuffers.push_back(*resourceManager.getCommandBuffer(i)); + } + catch (const std::exception &) + { + // Skip this thread's command buffer if there was an error + } + } + + // Ensure we have at least one command buffer + if (computeCmdBuffers.empty()) + { + return; + } + + // Set up compute submission + vk::TimelineSemaphoreSubmitInfo computeTimelineInfo{ + .waitSemaphoreValueCount = 1, + .pWaitSemaphoreValues = &computeWaitValue, + .signalSemaphoreValueCount = 1, + .pSignalSemaphoreValues = &computeSignalValue}; + + vk::PipelineStageFlags waitStages[] = {vk::PipelineStageFlagBits::eComputeShader}; + + vk::SubmitInfo computeSubmitInfo{ + .pNext = &computeTimelineInfo, + .waitSemaphoreCount = 1, + .pWaitSemaphores = &*timelineSemaphore, + .pWaitDstStageMask = waitStages, + .commandBufferCount = static_cast(computeCmdBuffers.size()), + .pCommandBuffers = computeCmdBuffers.data(), + .signalSemaphoreCount = 1, + .pSignalSemaphores = &*timelineSemaphore}; + + // Submit compute work + { + std::lock_guard lock(queueSubmitMutex); + queue.submit(computeSubmitInfo, nullptr); + } + + // Set up graphics submission + vk::PipelineStageFlags graphicsWaitStages[] = {vk::PipelineStageFlagBits::eVertexInput, vk::PipelineStageFlagBits::eColorAttachmentOutput}; + + std::array waitSemaphores = {*timelineSemaphore, *imageAvailableSemaphores[currentFrame]}; + std::array waitSemaphoreValues = {graphicsWaitValue, 0}; + + vk::TimelineSemaphoreSubmitInfo graphicsTimelineInfo{ + .waitSemaphoreValueCount = static_cast(waitSemaphoreValues.size()), + .pWaitSemaphoreValues = waitSemaphoreValues.data(), + .signalSemaphoreValueCount = 1, + .pSignalSemaphoreValues = &graphicsSignalValue}; + + vk::SubmitInfo graphicsSubmitInfo{ + .pNext = &graphicsTimelineInfo, + .waitSemaphoreCount = static_cast(waitSemaphores.size()), + .pWaitSemaphores = waitSemaphores.data(), + .pWaitDstStageMask = graphicsWaitStages, + .commandBufferCount = 1, + .pCommandBuffers = &*graphicsCommandBuffers[currentFrame], + .signalSemaphoreCount = 1, + .pSignalSemaphores = &*timelineSemaphore}; + + // Submit graphics work + { + std::lock_guard lock(queueSubmitMutex); + queue.submit(graphicsSubmitInfo, *inFlightFences[currentFrame]); + } + + // Wait for graphics to complete before presenting + vk::SemaphoreWaitInfo waitInfo{ + .semaphoreCount = 1, + .pSemaphores = &*timelineSemaphore, + .pValues = &graphicsSignalValue}; + + auto waitResult = device.waitSemaphores(waitInfo, 5000000000); + if (waitResult == vk::Result::eTimeout) + { + device.waitIdle(); + return; + } + + // Present the image + vk::PresentInfoKHR presentInfo{ + .waitSemaphoreCount = 0, + .pWaitSemaphores = nullptr, + .swapchainCount = 1, + .pSwapchains = &*swapChain, + .pImageIndices = &imageIndex}; + + result = queue.presentKHR(presentInfo); + + // Move to the next frame + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } }; - -int main() { - try { - MultithreadedApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + MultithreadedApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } From df01a11297a3986e8d6498d28de654e6dcd9d6be Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Mon, 18 Aug 2025 18:36:38 +0200 Subject: [PATCH 3/6] Fix include order on Android --- attachments/34_android.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/attachments/34_android.cpp b/attachments/34_android.cpp index 3f8dc6c6..083103cb 100644 --- a/attachments/34_android.cpp +++ b/attachments/34_android.cpp @@ -18,8 +18,8 @@ import vulkan_hpp; #endif #include #if defined(__ANDROID__) -# include # include +# include #endif #include From f1b37b51d2a6d4591967c9392f6596a0c28fd126 Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Mon, 18 Aug 2025 18:48:34 +0200 Subject: [PATCH 4/6] Fix include order on Android --- attachments/35_gltf_ktx.cpp | 2 +- attachments/36_multiple_objects.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/attachments/35_gltf_ktx.cpp b/attachments/35_gltf_ktx.cpp index e248d83c..a2a99a5f 100644 --- a/attachments/35_gltf_ktx.cpp +++ b/attachments/35_gltf_ktx.cpp @@ -18,8 +18,8 @@ import vulkan_hpp; #endif #include #if defined(__ANDROID__) -# include # include +# include #endif #include diff --git a/attachments/36_multiple_objects.cpp b/attachments/36_multiple_objects.cpp index 79c68f8a..f37c3922 100644 --- a/attachments/36_multiple_objects.cpp +++ b/attachments/36_multiple_objects.cpp @@ -18,8 +18,8 @@ import vulkan_hpp; #endif #include #if defined(__ANDROID__) -# include # include +# include #endif #include From dc941ed4eddebd7fc6a02a6dfb3d2cc32a516340 Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Sat, 23 Aug 2025 16:01:45 +0200 Subject: [PATCH 5/6] Reapply clang format after merge --- attachments/06_swap_chain_creation.cpp | 627 +-- attachments/07_image_views.cpp | 653 ++-- attachments/08_graphics_pipeline.cpp | 663 ++-- attachments/09_shader_modules.cpp | 713 ++-- attachments/10_fixed_functions.cpp | 766 ++-- attachments/12_graphics_pipeline_complete.cpp | 794 ++-- attachments/14_command_buffers.cpp | 983 ++--- attachments/15_hello_triangle.cpp | 1070 +++--- attachments/16_frames_in_flight.cpp | 1103 +++--- attachments/17_swap_chain_recreation.cpp | 1181 +++--- attachments/18_vertex_input.cpp | 1221 +++--- attachments/19_vertex_buffer.cpp | 1285 ++++--- attachments/20_staging_buffer.cpp | 1324 +++---- attachments/21_index_buffer.cpp | 1369 +++---- attachments/22_descriptor_layout.cpp | 1472 +++---- attachments/23_descriptor_sets.cpp | 1525 ++++---- attachments/24_texture_image.cpp | 1732 +++++---- attachments/25_sampler.cpp | 1805 ++++----- attachments/26_texture_mapping.cpp | 1947 +++++----- attachments/27_depth_buffering.cpp | 2177 +++++------ attachments/28_model_loading.cpp | 2289 +++++------ attachments/29_mipmapping.cpp | 2421 ++++++------ attachments/30_multisampling.cpp | 2589 +++++++------ attachments/31_compute_shader.cpp | 1867 ++++----- attachments/32_ecosystem_utilities.cpp | 3416 +++++++++-------- attachments/33_vulkan_profiles.cpp | 3379 ++++++++-------- attachments/34_android.cpp | 3156 +++++++-------- attachments/35_gltf_ktx.cpp | 2706 ++++++------- attachments/36_multiple_objects.cpp | 2978 +++++++------- attachments/37_multithreading.cpp | 2404 ++++++------ 30 files changed, 26502 insertions(+), 25113 deletions(-) diff --git a/attachments/06_swap_chain_creation.cpp b/attachments/06_swap_chain_creation.cpp index 3fff257c..41a3ea73 100644 --- a/attachments/06_swap_chain_creation.cpp +++ b/attachments/06_swap_chain_creation.cpp @@ -9,22 +9,21 @@ #include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include -constexpr uint32_t WIDTH = 800; +constexpr uint32_t WIDTH = 800; constexpr uint32_t HEIGHT = 600; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -32,303 +31,327 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -class HelloTriangleApplication { -public: - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } - -private: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - vk::raii::Queue queue = nullptr; - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::SurfaceFormatKHR swapChainSurfaceFormat; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - } ); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error( "failed to find a suitable GPU!" ); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both 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)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain featureChain = { - {}, // vk::PhysicalDeviceFeatures2 - {.dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( *surface ); - swapChainExtent = chooseSwapExtent( surfaceCapabilities ); - swapChainSurfaceFormat = chooseSwapSurfaceFormat( physicalDevice.getSurfaceFormatsKHR( *surface ) ); - vk::SwapchainCreateInfoKHR swapChainCreateInfo{ .surface = *surface, - .minImageCount = chooseSwapMinImageCount( surfaceCapabilities ), - .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 }; - - swapChain = vk::raii::SwapchainKHR( device, swapChainCreateInfo ); - swapChainImages = swapChain.getImages(); - } - - static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const & surfaceCapabilities) { - auto minImageCount = std::max( 3u, surfaceCapabilities.minImageCount ); - if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) { - minImageCount = surfaceCapabilities.maxImageCount; - } - return minImageCount; - } - - static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(std::vector const & availableFormats) { - assert(!availableFormats.empty()); - const auto formatIt = std::ranges::find_if( - availableFormats, - []( const auto & format ) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; } ); - return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - assert(std::ranges::any_of(availablePresentModes, [](auto presentMode){ return presentMode == vk::PresentModeKHR::eFifo; })); - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eFifo; - } - - vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) { - if (capabilities.currentExtent.width != 0xFFFFFFFF) { - 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) - }; - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } +class HelloTriangleApplication +{ + public: + void run() + { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + + private: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + vk::raii::Queue queue = nullptr; + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::SurfaceFormatKHR swapChainSurfaceFormat; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both 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)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain featureChain = { + {}, // vk::PhysicalDeviceFeatures2 + {.dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(*surface); + swapChainExtent = chooseSwapExtent(surfaceCapabilities); + swapChainSurfaceFormat = chooseSwapSurfaceFormat(physicalDevice.getSurfaceFormatsKHR(*surface)); + vk::SwapchainCreateInfoKHR swapChainCreateInfo{.surface = *surface, + .minImageCount = chooseSwapMinImageCount(surfaceCapabilities), + .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const &surfaceCapabilities) + { + auto minImageCount = std::max(3u, surfaceCapabilities.minImageCount); + if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) + { + minImageCount = surfaceCapabilities.maxImageCount; + } + return minImageCount; + } + + static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(std::vector const &availableFormats) + { + assert(!availableFormats.empty()); + const auto formatIt = std::ranges::find_if( + availableFormats, + [](const auto &format) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; }); + return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + assert(std::ranges::any_of(availablePresentModes, [](auto presentMode) { return presentMode == vk::PresentModeKHR::eFifo; })); + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + vk::PresentModeKHR::eFifo; + } + + vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR &capabilities) + { + if (capabilities.currentExtent.width != 0xFFFFFFFF) + { + 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)}; + } + + std::vector getRequiredExtensions() + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } }; -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/07_image_views.cpp b/attachments/07_image_views.cpp index 8d6e740c..9f91479f 100644 --- a/attachments/07_image_views.cpp +++ b/attachments/07_image_views.cpp @@ -9,22 +9,21 @@ #include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include -constexpr uint32_t WIDTH = 800; +constexpr uint32_t WIDTH = 800; constexpr uint32_t HEIGHT = 600; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -32,316 +31,340 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -class HelloTriangleApplication { -public: - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } - -private: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - vk::raii::Queue queue = nullptr; - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::SurfaceFormatKHR swapChainSurfaceFormat; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - } ); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error( "failed to find a suitable GPU!" ); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both 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)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain featureChain = { - {}, // vk::PhysicalDeviceFeatures2 - {.dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( *surface ); - swapChainExtent = chooseSwapExtent( surfaceCapabilities ); - swapChainSurfaceFormat = chooseSwapSurfaceFormat( physicalDevice.getSurfaceFormatsKHR( *surface ) ); - vk::SwapchainCreateInfoKHR swapChainCreateInfo{ .surface = *surface, - .minImageCount = chooseSwapMinImageCount( surfaceCapabilities ), - .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 }; - - swapChain = vk::raii::SwapchainKHR( device, swapChainCreateInfo ); - swapChainImages = swapChain.getImages(); - } - - void createImageViews() { - swapChainImageViews.clear(); - - vk::ImageViewCreateInfo imageViewCreateInfo{ .viewType = vk::ImageViewType::e2D, .format = swapChainSurfaceFormat.format, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } }; - for ( auto image : swapChainImages ) - { - imageViewCreateInfo.image = image; - swapChainImageViews.emplace_back( device, imageViewCreateInfo ); - } - } - - static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const & surfaceCapabilities) { - auto minImageCount = std::max( 3u, surfaceCapabilities.minImageCount ); - if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) { - minImageCount = surfaceCapabilities.maxImageCount; - } - return minImageCount; - } - - static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { - assert(!availableFormats.empty()); - const auto formatIt = std::ranges::find_if( - availableFormats, - []( const auto & format ) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; } ); - return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - assert(std::ranges::any_of(availablePresentModes, [](auto presentMode){ return presentMode == vk::PresentModeKHR::eFifo; })); - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eFifo; - } - - vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) { - if (capabilities.currentExtent.width != 0xFFFFFFFF) { - 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) - }; - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } +class HelloTriangleApplication +{ + public: + void run() + { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + + private: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + vk::raii::Queue queue = nullptr; + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::SurfaceFormatKHR swapChainSurfaceFormat; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both 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)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain featureChain = { + {}, // vk::PhysicalDeviceFeatures2 + {.dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(*surface); + swapChainExtent = chooseSwapExtent(surfaceCapabilities); + swapChainSurfaceFormat = chooseSwapSurfaceFormat(physicalDevice.getSurfaceFormatsKHR(*surface)); + vk::SwapchainCreateInfoKHR swapChainCreateInfo{.surface = *surface, + .minImageCount = chooseSwapMinImageCount(surfaceCapabilities), + .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + void createImageViews() + { + swapChainImageViews.clear(); + + vk::ImageViewCreateInfo imageViewCreateInfo{.viewType = vk::ImageViewType::e2D, .format = swapChainSurfaceFormat.format, .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + for (auto image : swapChainImages) + { + imageViewCreateInfo.image = image; + swapChainImageViews.emplace_back(device, imageViewCreateInfo); + } + } + + static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const &surfaceCapabilities) + { + auto minImageCount = std::max(3u, surfaceCapabilities.minImageCount); + if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) + { + minImageCount = surfaceCapabilities.maxImageCount; + } + return minImageCount; + } + + static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + assert(!availableFormats.empty()); + const auto formatIt = std::ranges::find_if( + availableFormats, + [](const auto &format) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; }); + return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + assert(std::ranges::any_of(availablePresentModes, [](auto presentMode) { return presentMode == vk::PresentModeKHR::eFifo; })); + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + vk::PresentModeKHR::eFifo; + } + + vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR &capabilities) + { + if (capabilities.currentExtent.width != 0xFFFFFFFF) + { + 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)}; + } + + std::vector getRequiredExtensions() + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } }; -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/08_graphics_pipeline.cpp b/attachments/08_graphics_pipeline.cpp index c17ac744..4b60b434 100644 --- a/attachments/08_graphics_pipeline.cpp +++ b/attachments/08_graphics_pipeline.cpp @@ -9,22 +9,21 @@ #include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include -constexpr uint32_t WIDTH = 800; +constexpr uint32_t WIDTH = 800; constexpr uint32_t HEIGHT = 600; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -32,321 +31,345 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -class HelloTriangleApplication { -public: - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } - -private: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - vk::raii::Queue queue = nullptr; - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::SurfaceFormatKHR swapChainSurfaceFormat; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - } ); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error( "failed to find a suitable GPU!" ); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both 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)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain featureChain = { - {}, // vk::PhysicalDeviceFeatures2 - {.dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( *surface ); - swapChainExtent = chooseSwapExtent( surfaceCapabilities ); - swapChainSurfaceFormat = chooseSwapSurfaceFormat( physicalDevice.getSurfaceFormatsKHR( *surface ) ); - vk::SwapchainCreateInfoKHR swapChainCreateInfo{ .surface = *surface, - .minImageCount = chooseSwapMinImageCount( surfaceCapabilities ), - .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 }; - - swapChain = vk::raii::SwapchainKHR( device, swapChainCreateInfo ); - swapChainImages = swapChain.getImages(); - } - - void createImageViews() { - swapChainImageViews.clear(); - - vk::ImageViewCreateInfo imageViewCreateInfo{ .viewType = vk::ImageViewType::e2D, .format = swapChainSurfaceFormat.format, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } }; - for ( auto image : swapChainImages ) - { - imageViewCreateInfo.image = image; - swapChainImageViews.emplace_back( device, imageViewCreateInfo ); - } - } - - void createGraphicsPipeline() { - - } - - static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const & surfaceCapabilities) { - auto minImageCount = std::max( 3u, surfaceCapabilities.minImageCount ); - if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) { - minImageCount = surfaceCapabilities.maxImageCount; - } - return minImageCount; - } - - static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { - assert(!availableFormats.empty()); - const auto formatIt = std::ranges::find_if( - availableFormats, - []( const auto & format ) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; } ); - return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - assert(std::ranges::any_of(availablePresentModes, [](auto presentMode){ return presentMode == vk::PresentModeKHR::eFifo; })); - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eFifo; - } - - vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) { - if (capabilities.currentExtent.width != 0xFFFFFFFF) { - 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) - }; - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } +class HelloTriangleApplication +{ + public: + void run() + { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + + private: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + vk::raii::Queue queue = nullptr; + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::SurfaceFormatKHR swapChainSurfaceFormat; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both 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)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain featureChain = { + {}, // vk::PhysicalDeviceFeatures2 + {.dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(*surface); + swapChainExtent = chooseSwapExtent(surfaceCapabilities); + swapChainSurfaceFormat = chooseSwapSurfaceFormat(physicalDevice.getSurfaceFormatsKHR(*surface)); + vk::SwapchainCreateInfoKHR swapChainCreateInfo{.surface = *surface, + .minImageCount = chooseSwapMinImageCount(surfaceCapabilities), + .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + void createImageViews() + { + swapChainImageViews.clear(); + + vk::ImageViewCreateInfo imageViewCreateInfo{.viewType = vk::ImageViewType::e2D, .format = swapChainSurfaceFormat.format, .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + for (auto image : swapChainImages) + { + imageViewCreateInfo.image = image; + swapChainImageViews.emplace_back(device, imageViewCreateInfo); + } + } + + void createGraphicsPipeline() + { + } + + static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const &surfaceCapabilities) + { + auto minImageCount = std::max(3u, surfaceCapabilities.minImageCount); + if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) + { + minImageCount = surfaceCapabilities.maxImageCount; + } + return minImageCount; + } + + static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + assert(!availableFormats.empty()); + const auto formatIt = std::ranges::find_if( + availableFormats, + [](const auto &format) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; }); + return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + assert(std::ranges::any_of(availablePresentModes, [](auto presentMode) { return presentMode == vk::PresentModeKHR::eFifo; })); + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + vk::PresentModeKHR::eFifo; + } + + vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR &capabilities) + { + if (capabilities.currentExtent.width != 0xFFFFFFFF) + { + 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)}; + } + + std::vector getRequiredExtensions() + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } }; -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/09_shader_modules.cpp b/attachments/09_shader_modules.cpp index 44c02678..bbb518f0 100644 --- a/attachments/09_shader_modules.cpp +++ b/attachments/09_shader_modules.cpp @@ -10,22 +10,21 @@ #include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include -constexpr uint32_t WIDTH = 800; +constexpr uint32_t WIDTH = 800; constexpr uint32_t HEIGHT = 600; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -33,344 +32,372 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -class HelloTriangleApplication { -public: - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } - -private: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - vk::raii::Queue queue = nullptr; - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::SurfaceFormatKHR swapChainSurfaceFormat; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - } ); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error( "failed to find a suitable GPU!" ); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both 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)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain featureChain = { - {}, // vk::PhysicalDeviceFeatures2 - {.dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( *surface ); - swapChainExtent = chooseSwapExtent( surfaceCapabilities ); - swapChainSurfaceFormat = chooseSwapSurfaceFormat( physicalDevice.getSurfaceFormatsKHR( *surface ) ); - vk::SwapchainCreateInfoKHR swapChainCreateInfo{ .surface = *surface, - .minImageCount = chooseSwapMinImageCount( surfaceCapabilities ), - .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 }; - - swapChain = vk::raii::SwapchainKHR( device, swapChainCreateInfo ); - swapChainImages = swapChain.getImages(); - } - - void createImageViews() { - swapChainImageViews.clear(); - - vk::ImageViewCreateInfo imageViewCreateInfo{ .viewType = vk::ImageViewType::e2D, .format = swapChainSurfaceFormat.format, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } }; - for ( auto image : swapChainImages ) - { - imageViewCreateInfo.image = image; - swapChainImageViews.emplace_back( device, imageViewCreateInfo ); - } - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const & surfaceCapabilities) { - auto minImageCount = std::max( 3u, surfaceCapabilities.minImageCount ); - if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) { - minImageCount = surfaceCapabilities.maxImageCount; - } - return minImageCount; - } - - static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { - assert(!availableFormats.empty()); - const auto formatIt = std::ranges::find_if( - availableFormats, - []( const auto & format ) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; } ); - return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - assert(std::ranges::any_of(availablePresentModes, [](auto presentMode){ return presentMode == vk::PresentModeKHR::eFifo; })); - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eFifo; - } - - vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) { - if (capabilities.currentExtent.width != 0xFFFFFFFF) { - 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) - }; - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - 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: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + vk::raii::Queue queue = nullptr; + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::SurfaceFormatKHR swapChainSurfaceFormat; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both 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)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain featureChain = { + {}, // vk::PhysicalDeviceFeatures2 + {.dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(*surface); + swapChainExtent = chooseSwapExtent(surfaceCapabilities); + swapChainSurfaceFormat = chooseSwapSurfaceFormat(physicalDevice.getSurfaceFormatsKHR(*surface)); + vk::SwapchainCreateInfoKHR swapChainCreateInfo{.surface = *surface, + .minImageCount = chooseSwapMinImageCount(surfaceCapabilities), + .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + void createImageViews() + { + swapChainImageViews.clear(); + + vk::ImageViewCreateInfo imageViewCreateInfo{.viewType = vk::ImageViewType::e2D, .format = swapChainSurfaceFormat.format, .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + for (auto image : swapChainImages) + { + imageViewCreateInfo.image = image; + swapChainImageViews.emplace_back(device, imageViewCreateInfo); + } + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const &surfaceCapabilities) + { + auto minImageCount = std::max(3u, surfaceCapabilities.minImageCount); + if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) + { + minImageCount = surfaceCapabilities.maxImageCount; + } + return minImageCount; + } + + static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + assert(!availableFormats.empty()); + const auto formatIt = std::ranges::find_if( + availableFormats, + [](const auto &format) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; }); + return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + assert(std::ranges::any_of(availablePresentModes, [](auto presentMode) { return presentMode == vk::PresentModeKHR::eFifo; })); + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + vk::PresentModeKHR::eFifo; + } + + vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR &capabilities) + { + if (capabilities.currentExtent.width != 0xFFFFFFFF) + { + 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)}; + } + + std::vector getRequiredExtensions() + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + 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; + } }; -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/10_fixed_functions.cpp b/attachments/10_fixed_functions.cpp index 7b1ca2d6..8c0f919e 100644 --- a/attachments/10_fixed_functions.cpp +++ b/attachments/10_fixed_functions.cpp @@ -10,22 +10,21 @@ #include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include -constexpr uint32_t WIDTH = 800; +constexpr uint32_t WIDTH = 800; constexpr uint32_t HEIGHT = 600; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -33,373 +32,396 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -class HelloTriangleApplication { -public: - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } - -private: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - vk::raii::Queue queue = nullptr; - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::SurfaceFormatKHR swapChainSurfaceFormat; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::PipelineLayout pipelineLayout = nullptr; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - } ); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error( "failed to find a suitable GPU!" ); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both 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)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain featureChain = { - {}, // vk::PhysicalDeviceFeatures2 - {.dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( *surface ); - swapChainExtent = chooseSwapExtent( surfaceCapabilities ); - swapChainSurfaceFormat = chooseSwapSurfaceFormat( physicalDevice.getSurfaceFormatsKHR( *surface ) ); - vk::SwapchainCreateInfoKHR swapChainCreateInfo{ .surface = *surface, - .minImageCount = chooseSwapMinImageCount( surfaceCapabilities ), - .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 }; - - swapChain = vk::raii::SwapchainKHR( device, swapChainCreateInfo ); - swapChainImages = swapChain.getImages(); - } - - void createImageViews() { - swapChainImageViews.clear(); - - vk::ImageViewCreateInfo imageViewCreateInfo{ .viewType = vk::ImageViewType::e2D, .format = swapChainSurfaceFormat.format, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } }; - for ( auto image : swapChainImages ) - { - imageViewCreateInfo.image = image; - swapChainImageViews.emplace_back( device, imageViewCreateInfo ); - } - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; - - vk::PipelineVertexInputStateCreateInfo vertexInputInfo; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ .topology = vk::PrimitiveTopology::eTriangleList }; - vk::PipelineViewportStateCreateInfo viewportState{ .viewportCount = 1, .scissorCount = 1 }; - - vk::PipelineRasterizationStateCreateInfo rasterizer{ .depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = vk::False, - .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f }; - - vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; - - vk::PipelineColorBlendAttachmentState colorBlendAttachment{ .blendEnable = vk::False, - .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA - }; - - vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo; - - pipelineLayout = vk::raii::PipelineLayout( device, pipelineLayoutInfo ); - } - - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size() * sizeof(char), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const & surfaceCapabilities) { - auto minImageCount = std::max( 3u, surfaceCapabilities.minImageCount ); - if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) { - minImageCount = surfaceCapabilities.maxImageCount; - } - return minImageCount; - } - - static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { - assert(!availableFormats.empty()); - const auto formatIt = std::ranges::find_if( - availableFormats, - []( const auto & format ) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; } ); - return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - assert(std::ranges::any_of(availablePresentModes, [](auto presentMode){ return presentMode == vk::PresentModeKHR::eFifo; })); - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eFifo; - } - - vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) { - if (capabilities.currentExtent.width != 0xFFFFFFFF) { - 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) - }; - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - 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: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + vk::raii::Queue queue = nullptr; + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::SurfaceFormatKHR swapChainSurfaceFormat; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::PipelineLayout pipelineLayout = nullptr; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both 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)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain featureChain = { + {}, // vk::PhysicalDeviceFeatures2 + {.dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(*surface); + swapChainExtent = chooseSwapExtent(surfaceCapabilities); + swapChainSurfaceFormat = chooseSwapSurfaceFormat(physicalDevice.getSurfaceFormatsKHR(*surface)); + vk::SwapchainCreateInfoKHR swapChainCreateInfo{.surface = *surface, + .minImageCount = chooseSwapMinImageCount(surfaceCapabilities), + .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + void createImageViews() + { + swapChainImageViews.clear(); + + vk::ImageViewCreateInfo imageViewCreateInfo{.viewType = vk::ImageViewType::e2D, .format = swapChainSurfaceFormat.format, .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + for (auto image : swapChainImages) + { + imageViewCreateInfo.image = image; + swapChainImageViews.emplace_back(device, imageViewCreateInfo); + } + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; + + vk::PipelineVertexInputStateCreateInfo vertexInputInfo; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{.topology = vk::PrimitiveTopology::eTriangleList}; + vk::PipelineViewportStateCreateInfo viewportState{.viewportCount = 1, .scissorCount = 1}; + + vk::PipelineRasterizationStateCreateInfo rasterizer{.depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = vk::False, .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f}; + + vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; + + vk::PipelineColorBlendAttachmentState colorBlendAttachment{.blendEnable = vk::False, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA}; + + vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo; + + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size() * sizeof(char), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const &surfaceCapabilities) + { + auto minImageCount = std::max(3u, surfaceCapabilities.minImageCount); + if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) + { + minImageCount = surfaceCapabilities.maxImageCount; + } + return minImageCount; + } + + static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + assert(!availableFormats.empty()); + const auto formatIt = std::ranges::find_if( + availableFormats, + [](const auto &format) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; }); + return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + assert(std::ranges::any_of(availablePresentModes, [](auto presentMode) { return presentMode == vk::PresentModeKHR::eFifo; })); + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + vk::PresentModeKHR::eFifo; + } + + vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR &capabilities) + { + if (capabilities.currentExtent.width != 0xFFFFFFFF) + { + 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)}; + } + + std::vector getRequiredExtensions() + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + 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; + } }; -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/12_graphics_pipeline_complete.cpp b/attachments/12_graphics_pipeline_complete.cpp index 21444daf..a22076a9 100644 --- a/attachments/12_graphics_pipeline_complete.cpp +++ b/attachments/12_graphics_pipeline_complete.cpp @@ -10,22 +10,21 @@ #include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include -constexpr uint32_t WIDTH = 800; +constexpr uint32_t WIDTH = 800; constexpr uint32_t HEIGHT = 600; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -33,384 +32,413 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -class HelloTriangleApplication { -public: - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } - -private: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - vk::raii::Queue queue = nullptr; - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::SurfaceFormatKHR swapChainSurfaceFormat; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - } ); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error( "failed to find a suitable GPU!" ); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both 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)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain featureChain = { - {}, // vk::PhysicalDeviceFeatures2 - {.dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( *surface ); - swapChainExtent = chooseSwapExtent( surfaceCapabilities ); - swapChainSurfaceFormat = chooseSwapSurfaceFormat( physicalDevice.getSurfaceFormatsKHR( *surface ) ); - vk::SwapchainCreateInfoKHR swapChainCreateInfo{ .surface = *surface, - .minImageCount = chooseSwapMinImageCount( surfaceCapabilities ), - .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 }; - - swapChain = vk::raii::SwapchainKHR( device, swapChainCreateInfo ); - swapChainImages = swapChain.getImages(); - } - - void createImageViews() { - swapChainImageViews.clear(); - - vk::ImageViewCreateInfo imageViewCreateInfo{ .viewType = vk::ImageViewType::e2D, .format = swapChainSurfaceFormat.format, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } }; - for ( auto image : swapChainImages ) - { - imageViewCreateInfo.image = image; - swapChainImageViews.emplace_back( device, imageViewCreateInfo ); - } - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; - - vk::PipelineVertexInputStateCreateInfo vertexInputInfo; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ .topology = vk::PrimitiveTopology::eTriangleList }; - vk::PipelineViewportStateCreateInfo viewportState{ .viewportCount = 1, .scissorCount = 1 }; - - vk::PipelineRasterizationStateCreateInfo rasterizer{ .depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = vk::False, - .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f }; - - vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; - - vk::PipelineColorBlendAttachmentState colorBlendAttachment{ .blendEnable = vk::False, - .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA - }; - - vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 0, .pushConstantRangeCount = 0 }; - - pipelineLayout = vk::raii::PipelineLayout( device, pipelineLayoutInfo ); - - vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ .colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format }; - vk::GraphicsPipelineCreateInfo pipelineInfo{ .pNext = &pipelineRenderingCreateInfo, - .stageCount = 2, .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, .layout = pipelineLayout, .renderPass = nullptr }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size() * sizeof(char), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const & surfaceCapabilities) { - auto minImageCount = std::max( 3u, surfaceCapabilities.minImageCount ); - if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) { - minImageCount = surfaceCapabilities.maxImageCount; - } - return minImageCount; - } - - static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { - assert(!availableFormats.empty()); - const auto formatIt = std::ranges::find_if( - availableFormats, - []( const auto & format ) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; } ); - return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - assert(std::ranges::any_of(availablePresentModes, [](auto presentMode){ return presentMode == vk::PresentModeKHR::eFifo; })); - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eFifo; - } - - vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) { - if (capabilities.currentExtent.width != 0xFFFFFFFF) { - 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) - }; - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - 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: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + vk::raii::Queue queue = nullptr; + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::SurfaceFormatKHR swapChainSurfaceFormat; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both 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)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain featureChain = { + {}, // vk::PhysicalDeviceFeatures2 + {.dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(*surface); + swapChainExtent = chooseSwapExtent(surfaceCapabilities); + swapChainSurfaceFormat = chooseSwapSurfaceFormat(physicalDevice.getSurfaceFormatsKHR(*surface)); + vk::SwapchainCreateInfoKHR swapChainCreateInfo{.surface = *surface, + .minImageCount = chooseSwapMinImageCount(surfaceCapabilities), + .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + void createImageViews() + { + swapChainImageViews.clear(); + + vk::ImageViewCreateInfo imageViewCreateInfo{.viewType = vk::ImageViewType::e2D, .format = swapChainSurfaceFormat.format, .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + for (auto image : swapChainImages) + { + imageViewCreateInfo.image = image; + swapChainImageViews.emplace_back(device, imageViewCreateInfo); + } + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; + + vk::PipelineVertexInputStateCreateInfo vertexInputInfo; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{.topology = vk::PrimitiveTopology::eTriangleList}; + vk::PipelineViewportStateCreateInfo viewportState{.viewportCount = 1, .scissorCount = 1}; + + vk::PipelineRasterizationStateCreateInfo rasterizer{.depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = vk::False, .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f}; + + vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; + + vk::PipelineColorBlendAttachmentState colorBlendAttachment{.blendEnable = vk::False, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA}; + + vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 0, .pushConstantRangeCount = 0}; + + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format}; + vk::GraphicsPipelineCreateInfo pipelineInfo{.pNext = &pipelineRenderingCreateInfo, + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size() * sizeof(char), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const &surfaceCapabilities) + { + auto minImageCount = std::max(3u, surfaceCapabilities.minImageCount); + if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) + { + minImageCount = surfaceCapabilities.maxImageCount; + } + return minImageCount; + } + + static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + assert(!availableFormats.empty()); + const auto formatIt = std::ranges::find_if( + availableFormats, + [](const auto &format) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; }); + return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + assert(std::ranges::any_of(availablePresentModes, [](auto presentMode) { return presentMode == vk::PresentModeKHR::eFifo; })); + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + vk::PresentModeKHR::eFifo; + } + + vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR &capabilities) + { + if (capabilities.currentExtent.width != 0xFFFFFFFF) + { + 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)}; + } + + std::vector getRequiredExtensions() + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + 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; + } }; -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/14_command_buffers.cpp b/attachments/14_command_buffers.cpp index 00c9b90a..93ea92ab 100644 --- a/attachments/14_command_buffers.cpp +++ b/attachments/14_command_buffers.cpp @@ -10,22 +10,21 @@ #include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include -constexpr uint32_t WIDTH = 800; +constexpr uint32_t WIDTH = 800; constexpr uint32_t HEIGHT = 600; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -33,480 +32,506 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -class HelloTriangleApplication { -public: - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } - -private: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::SurfaceFormatKHR swapChainSurfaceFormat; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - vk::raii::CommandPool commandPool = nullptr; - vk::raii::CommandBuffer commandBuffer = nullptr; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - createSurface(); - pickPhysicalDevice(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createGraphicsPipeline(); - createCommandPool(); - createCommandBuffer(); - } - - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - } ); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error( "failed to find a suitable GPU!" ); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain featureChain = { - {}, // vk::PhysicalDeviceFeatures2 - {.dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( *surface ); - swapChainExtent = chooseSwapExtent( surfaceCapabilities ); - swapChainSurfaceFormat = chooseSwapSurfaceFormat( physicalDevice.getSurfaceFormatsKHR( *surface ) ); - vk::SwapchainCreateInfoKHR swapChainCreateInfo{ .surface = *surface, - .minImageCount = chooseSwapMinImageCount( surfaceCapabilities ), - .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 }; - - swapChain = vk::raii::SwapchainKHR( device, swapChainCreateInfo ); - swapChainImages = swapChain.getImages(); - } - - void createImageViews() { - swapChainImageViews.clear(); - - vk::ImageViewCreateInfo imageViewCreateInfo{ .viewType = vk::ImageViewType::e2D, .format = swapChainSurfaceFormat.format, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } }; - for ( auto image : swapChainImages ) - { - imageViewCreateInfo.image = image; - swapChainImageViews.emplace_back( device, imageViewCreateInfo ); - } - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; - - vk::PipelineVertexInputStateCreateInfo vertexInputInfo; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ .topology = vk::PrimitiveTopology::eTriangleList }; - vk::PipelineViewportStateCreateInfo viewportState{ .viewportCount = 1, .scissorCount = 1 }; - - vk::PipelineRasterizationStateCreateInfo rasterizer{ .depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = vk::False, - .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f }; - - vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; - - vk::PipelineColorBlendAttachmentState colorBlendAttachment{ .blendEnable = vk::False, - .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA - }; - - vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 0, .pushConstantRangeCount = 0 }; - - pipelineLayout = vk::raii::PipelineLayout( device, pipelineLayoutInfo ); - - vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ .colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format }; - vk::GraphicsPipelineCreateInfo pipelineInfo{ .pNext = &pipelineRenderingCreateInfo, - .stageCount = 2, .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, .layout = pipelineLayout, .renderPass = nullptr }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{ .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex }; - commandPool = vk::raii::CommandPool(device, poolInfo); - } - - void createCommandBuffer() { - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = 1 }; - commandBuffer = std::move(vk::raii::CommandBuffers( device, allocInfo ).front()); - } - - void recordCommandBuffer(uint32_t imageIndex) { - commandBuffer.begin( {} ); - // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL - transition_image_layout( - imageIndex, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage - ); - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::RenderingAttachmentInfo attachmentInfo = { - .imageView = swapChainImageViews[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor - }; - vk::RenderingInfo renderingInfo = { - .renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &attachmentInfo - }; - - commandBuffer.beginRendering(renderingInfo); - commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffer.setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffer.setScissor( 0, vk::Rect2D( vk::Offset2D( 0, 0 ), swapChainExtent ) ); - commandBuffer.draw(3, 1, 0, 0); - commandBuffer.endRendering(); - // After rendering, transition the swapchain image to PRESENT_SRC - transition_image_layout( - imageIndex, - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage - ); - commandBuffer.end(); - } - - void transition_image_layout( - uint32_t currentFrame, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::AccessFlags2 src_access_mask, - vk::AccessFlags2 dst_access_mask, - vk::PipelineStageFlags2 src_stage_mask, - vk::PipelineStageFlags2 dst_stage_mask - ) { - vk::ImageMemoryBarrier2 barrier = { - .srcStageMask = src_stage_mask, - .srcAccessMask = src_access_mask, - .dstStageMask = dst_stage_mask, - .dstAccessMask = dst_access_mask, - .oldLayout = old_layout, - .newLayout = new_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[currentFrame], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - commandBuffer.pipelineBarrier2(dependency_info); - } - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size() * sizeof(char), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const & surfaceCapabilities) { - auto minImageCount = std::max( 3u, surfaceCapabilities.minImageCount ); - if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) { - minImageCount = surfaceCapabilities.maxImageCount; - } - return minImageCount; - } - - static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { - assert(!availableFormats.empty()); - const auto formatIt = std::ranges::find_if( - availableFormats, - []( const auto & format ) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; } ); - return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - assert(std::ranges::any_of(availablePresentModes, [](auto presentMode){ return presentMode == vk::PresentModeKHR::eFifo; })); - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eFifo; - } - - vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) { - if (capabilities.currentExtent.width != 0xFFFFFFFF) { - 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) - }; - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - 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: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::SurfaceFormatKHR swapChainSurfaceFormat; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + vk::raii::CommandPool commandPool = nullptr; + vk::raii::CommandBuffer commandBuffer = nullptr; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createGraphicsPipeline(); + createCommandPool(); + createCommandBuffer(); + } + + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain featureChain = { + {}, // vk::PhysicalDeviceFeatures2 + {.dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(*surface); + swapChainExtent = chooseSwapExtent(surfaceCapabilities); + swapChainSurfaceFormat = chooseSwapSurfaceFormat(physicalDevice.getSurfaceFormatsKHR(*surface)); + vk::SwapchainCreateInfoKHR swapChainCreateInfo{.surface = *surface, + .minImageCount = chooseSwapMinImageCount(surfaceCapabilities), + .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + void createImageViews() + { + swapChainImageViews.clear(); + + vk::ImageViewCreateInfo imageViewCreateInfo{.viewType = vk::ImageViewType::e2D, .format = swapChainSurfaceFormat.format, .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + for (auto image : swapChainImages) + { + imageViewCreateInfo.image = image; + swapChainImageViews.emplace_back(device, imageViewCreateInfo); + } + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; + + vk::PipelineVertexInputStateCreateInfo vertexInputInfo; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{.topology = vk::PrimitiveTopology::eTriangleList}; + vk::PipelineViewportStateCreateInfo viewportState{.viewportCount = 1, .scissorCount = 1}; + + vk::PipelineRasterizationStateCreateInfo rasterizer{.depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = vk::False, .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f}; + + vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; + + vk::PipelineColorBlendAttachmentState colorBlendAttachment{.blendEnable = vk::False, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA}; + + vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 0, .pushConstantRangeCount = 0}; + + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format}; + vk::GraphicsPipelineCreateInfo pipelineInfo{.pNext = &pipelineRenderingCreateInfo, + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex}; + commandPool = vk::raii::CommandPool(device, poolInfo); + } + + void createCommandBuffer() + { + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1}; + commandBuffer = std::move(vk::raii::CommandBuffers(device, allocInfo).front()); + } + + void recordCommandBuffer(uint32_t imageIndex) + { + commandBuffer.begin({}); + // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL + transition_image_layout( + imageIndex, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage + ); + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::RenderingAttachmentInfo attachmentInfo = { + .imageView = swapChainImageViews[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor}; + vk::RenderingInfo renderingInfo = { + .renderArea = {.offset = {0, 0}, .extent = swapChainExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &attachmentInfo}; + + commandBuffer.beginRendering(renderingInfo); + commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffer.setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffer.setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffer.draw(3, 1, 0, 0); + commandBuffer.endRendering(); + // After rendering, transition the swapchain image to PRESENT_SRC + transition_image_layout( + imageIndex, + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage + ); + commandBuffer.end(); + } + + void transition_image_layout( + uint32_t currentFrame, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout, + vk::AccessFlags2 src_access_mask, + vk::AccessFlags2 dst_access_mask, + vk::PipelineStageFlags2 src_stage_mask, + vk::PipelineStageFlags2 dst_stage_mask) + { + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = src_stage_mask, + .srcAccessMask = src_access_mask, + .dstStageMask = dst_stage_mask, + .dstAccessMask = dst_access_mask, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[currentFrame], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo dependency_info = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + commandBuffer.pipelineBarrier2(dependency_info); + } + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size() * sizeof(char), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const &surfaceCapabilities) + { + auto minImageCount = std::max(3u, surfaceCapabilities.minImageCount); + if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) + { + minImageCount = surfaceCapabilities.maxImageCount; + } + return minImageCount; + } + + static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + assert(!availableFormats.empty()); + const auto formatIt = std::ranges::find_if( + availableFormats, + [](const auto &format) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; }); + return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + assert(std::ranges::any_of(availablePresentModes, [](auto presentMode) { return presentMode == vk::PresentModeKHR::eFifo; })); + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + vk::PresentModeKHR::eFifo; + } + + vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR &capabilities) + { + if (capabilities.currentExtent.width != 0xFFFFFFFF) + { + 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)}; + } + + std::vector getRequiredExtensions() + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + 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; + } }; -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/15_hello_triangle.cpp b/attachments/15_hello_triangle.cpp index 2b1ea167..e7e533f6 100644 --- a/attachments/15_hello_triangle.cpp +++ b/attachments/15_hello_triangle.cpp @@ -10,22 +10,21 @@ #include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include -constexpr uint32_t WIDTH = 800; +constexpr uint32_t WIDTH = 800; constexpr uint32_t HEIGHT = 600; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -33,522 +32,551 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -class HelloTriangleApplication { -public: - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } - -private: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::SurfaceFormatKHR swapChainSurfaceFormat; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - - vk::raii::CommandPool commandPool = nullptr; - vk::raii::CommandBuffer commandBuffer = nullptr; - - vk::raii::Semaphore presentCompleteSemaphore = nullptr; - vk::raii::Semaphore renderFinishedSemaphore = nullptr; - vk::raii::Fence drawFence = nullptr; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - createSurface(); - pickPhysicalDevice(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createGraphicsPipeline(); - createCommandPool(); - createCommandBuffer(); - createSyncObjects(); - } - - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - drawFrame(); - } - - device.waitIdle(); - } - - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - } ); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error( "failed to find a suitable GPU!" ); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain featureChain = { - {}, // vk::PhysicalDeviceFeatures2 - {.synchronization2 = true, .dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( *surface ); - swapChainExtent = chooseSwapExtent( surfaceCapabilities ); - swapChainSurfaceFormat = chooseSwapSurfaceFormat( physicalDevice.getSurfaceFormatsKHR( *surface ) ); - vk::SwapchainCreateInfoKHR swapChainCreateInfo{ .surface = *surface, - .minImageCount = chooseSwapMinImageCount( surfaceCapabilities ), - .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 }; - - swapChain = vk::raii::SwapchainKHR( device, swapChainCreateInfo ); - swapChainImages = swapChain.getImages(); - } - - void createImageViews() { - swapChainImageViews.clear(); - - vk::ImageViewCreateInfo imageViewCreateInfo{ .viewType = vk::ImageViewType::e2D, .format = swapChainSurfaceFormat.format, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } }; - for ( auto image : swapChainImages ) - { - imageViewCreateInfo.image = image; - swapChainImageViews.emplace_back( device, imageViewCreateInfo ); - } - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; - - vk::PipelineVertexInputStateCreateInfo vertexInputInfo; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ .topology = vk::PrimitiveTopology::eTriangleList }; - vk::PipelineViewportStateCreateInfo viewportState{ .viewportCount = 1, .scissorCount = 1 }; - - vk::PipelineRasterizationStateCreateInfo rasterizer{ .depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = vk::False, - .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f }; - - vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; - - vk::PipelineColorBlendAttachmentState colorBlendAttachment{ .blendEnable = vk::False, - .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA - }; - - vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 0, .pushConstantRangeCount = 0 }; - - pipelineLayout = vk::raii::PipelineLayout( device, pipelineLayoutInfo ); - - vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ .colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format }; - vk::GraphicsPipelineCreateInfo pipelineInfo{ .pNext = &pipelineRenderingCreateInfo, - .stageCount = 2, .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, .layout = pipelineLayout, .renderPass = nullptr }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{ .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex }; - commandPool = vk::raii::CommandPool(device, poolInfo); - } - - void createCommandBuffer() { - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = 1 }; - commandBuffer = std::move(vk::raii::CommandBuffers( device, allocInfo ).front()); - } - - void recordCommandBuffer(uint32_t imageIndex) { - commandBuffer.begin( {} ); - // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL - transition_image_layout( - imageIndex, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage - ); - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::RenderingAttachmentInfo attachmentInfo = { - .imageView = swapChainImageViews[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor - }; - vk::RenderingInfo renderingInfo = { - .renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &attachmentInfo - }; - - commandBuffer.beginRendering(renderingInfo); - commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffer.setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffer.setScissor( 0, vk::Rect2D( vk::Offset2D( 0, 0 ), swapChainExtent ) ); - commandBuffer.draw(3, 1, 0, 0); - commandBuffer.endRendering(); - // After rendering, transition the swapchain image to PRESENT_SRC - transition_image_layout( - imageIndex, - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage - ); - commandBuffer.end(); - } - - void transition_image_layout( - uint32_t currentFrame, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::AccessFlags2 src_access_mask, - vk::AccessFlags2 dst_access_mask, - vk::PipelineStageFlags2 src_stage_mask, - vk::PipelineStageFlags2 dst_stage_mask - ) { - vk::ImageMemoryBarrier2 barrier = { - .srcStageMask = src_stage_mask, - .srcAccessMask = src_access_mask, - .dstStageMask = dst_stage_mask, - .dstAccessMask = dst_access_mask, - .oldLayout = old_layout, - .newLayout = new_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[currentFrame], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - commandBuffer.pipelineBarrier2(dependency_info); - } - - void createSyncObjects() { - presentCompleteSemaphore =vk::raii::Semaphore(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore = vk::raii::Semaphore(device, vk::SemaphoreCreateInfo()); - drawFence = vk::raii::Fence(device, {.flags = vk::FenceCreateFlagBits::eSignaled}); - } - - void drawFrame() { - queue.waitIdle(); - - auto [result, imageIndex] = swapChain.acquireNextImage( UINT64_MAX, *presentCompleteSemaphore, nullptr ); - recordCommandBuffer(imageIndex); - - device.resetFences( *drawFence ); - vk::PipelineStageFlags waitDestinationStageMask( vk::PipelineStageFlagBits::eColorAttachmentOutput ); - const vk::SubmitInfo submitInfo{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore, - .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffer, - .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore }; - queue.submit(submitInfo, *drawFence); - while ( vk::Result::eTimeout == device.waitForFences( *drawFence, vk::True, UINT64_MAX ) ) - ; - - const vk::PresentInfoKHR presentInfoKHR{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore, - .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex }; - result = queue.presentKHR( presentInfoKHR ); - switch ( result ) - { - case vk::Result::eSuccess: break; - case vk::Result::eSuboptimalKHR: std::cout << "vk::Queue::presentKHR returned vk::Result::eSuboptimalKHR !\n"; break; - default: break; // an unexpected result is returned! - } - } - - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size() * sizeof(char), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const & surfaceCapabilities) { - auto minImageCount = std::max( 3u, surfaceCapabilities.minImageCount ); - if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) { - minImageCount = surfaceCapabilities.maxImageCount; - } - return minImageCount; - } - - static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { - assert(!availableFormats.empty()); - const auto formatIt = std::ranges::find_if( - availableFormats, - []( const auto & format ) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; } ); - return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - assert(std::ranges::any_of(availablePresentModes, [](auto presentMode){ return presentMode == vk::PresentModeKHR::eFifo; })); - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eFifo; - } - - vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) { - if (capabilities.currentExtent.width != 0xFFFFFFFF) { - 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) - }; - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - 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: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::SurfaceFormatKHR swapChainSurfaceFormat; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + + vk::raii::CommandPool commandPool = nullptr; + vk::raii::CommandBuffer commandBuffer = nullptr; + + vk::raii::Semaphore presentCompleteSemaphore = nullptr; + vk::raii::Semaphore renderFinishedSemaphore = nullptr; + vk::raii::Fence drawFence = nullptr; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createGraphicsPipeline(); + createCommandPool(); + createCommandBuffer(); + createSyncObjects(); + } + + void mainLoop() + { + while (!glfwWindowShouldClose(window)) + { + glfwPollEvents(); + drawFrame(); + } + + device.waitIdle(); + } + + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain featureChain = { + {}, // vk::PhysicalDeviceFeatures2 + {.synchronization2 = true, .dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(*surface); + swapChainExtent = chooseSwapExtent(surfaceCapabilities); + swapChainSurfaceFormat = chooseSwapSurfaceFormat(physicalDevice.getSurfaceFormatsKHR(*surface)); + vk::SwapchainCreateInfoKHR swapChainCreateInfo{.surface = *surface, + .minImageCount = chooseSwapMinImageCount(surfaceCapabilities), + .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + void createImageViews() + { + swapChainImageViews.clear(); + + vk::ImageViewCreateInfo imageViewCreateInfo{.viewType = vk::ImageViewType::e2D, .format = swapChainSurfaceFormat.format, .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + for (auto image : swapChainImages) + { + imageViewCreateInfo.image = image; + swapChainImageViews.emplace_back(device, imageViewCreateInfo); + } + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; + + vk::PipelineVertexInputStateCreateInfo vertexInputInfo; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{.topology = vk::PrimitiveTopology::eTriangleList}; + vk::PipelineViewportStateCreateInfo viewportState{.viewportCount = 1, .scissorCount = 1}; + + vk::PipelineRasterizationStateCreateInfo rasterizer{.depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = vk::False, .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f}; + + vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; + + vk::PipelineColorBlendAttachmentState colorBlendAttachment{.blendEnable = vk::False, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA}; + + vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 0, .pushConstantRangeCount = 0}; + + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format}; + vk::GraphicsPipelineCreateInfo pipelineInfo{.pNext = &pipelineRenderingCreateInfo, + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex}; + commandPool = vk::raii::CommandPool(device, poolInfo); + } + + void createCommandBuffer() + { + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1}; + commandBuffer = std::move(vk::raii::CommandBuffers(device, allocInfo).front()); + } + + void recordCommandBuffer(uint32_t imageIndex) + { + commandBuffer.begin({}); + // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL + transition_image_layout( + imageIndex, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage + ); + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::RenderingAttachmentInfo attachmentInfo = { + .imageView = swapChainImageViews[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor}; + vk::RenderingInfo renderingInfo = { + .renderArea = {.offset = {0, 0}, .extent = swapChainExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &attachmentInfo}; + + commandBuffer.beginRendering(renderingInfo); + commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffer.setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffer.setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffer.draw(3, 1, 0, 0); + commandBuffer.endRendering(); + // After rendering, transition the swapchain image to PRESENT_SRC + transition_image_layout( + imageIndex, + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage + ); + commandBuffer.end(); + } + + void transition_image_layout( + uint32_t currentFrame, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout, + vk::AccessFlags2 src_access_mask, + vk::AccessFlags2 dst_access_mask, + vk::PipelineStageFlags2 src_stage_mask, + vk::PipelineStageFlags2 dst_stage_mask) + { + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = src_stage_mask, + .srcAccessMask = src_access_mask, + .dstStageMask = dst_stage_mask, + .dstAccessMask = dst_access_mask, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[currentFrame], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo dependency_info = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + commandBuffer.pipelineBarrier2(dependency_info); + } + + void createSyncObjects() + { + presentCompleteSemaphore = vk::raii::Semaphore(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphore = vk::raii::Semaphore(device, vk::SemaphoreCreateInfo()); + drawFence = vk::raii::Fence(device, {.flags = vk::FenceCreateFlagBits::eSignaled}); + } + + void drawFrame() + { + queue.waitIdle(); + + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore, nullptr); + recordCommandBuffer(imageIndex); + + device.resetFences(*drawFence); + vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore, .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffer, .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore}; + queue.submit(submitInfo, *drawFence); + while (vk::Result::eTimeout == device.waitForFences(*drawFence, vk::True, UINT64_MAX)) + ; + + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore, .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + result = queue.presentKHR(presentInfoKHR); + switch (result) + { + case vk::Result::eSuccess: + break; + case vk::Result::eSuboptimalKHR: + std::cout << "vk::Queue::presentKHR returned vk::Result::eSuboptimalKHR !\n"; + break; + default: + break; // an unexpected result is returned! + } + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size() * sizeof(char), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const &surfaceCapabilities) + { + auto minImageCount = std::max(3u, surfaceCapabilities.minImageCount); + if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) + { + minImageCount = surfaceCapabilities.maxImageCount; + } + return minImageCount; + } + + static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + assert(!availableFormats.empty()); + const auto formatIt = std::ranges::find_if( + availableFormats, + [](const auto &format) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; }); + return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + assert(std::ranges::any_of(availablePresentModes, [](auto presentMode) { return presentMode == vk::PresentModeKHR::eFifo; })); + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + vk::PresentModeKHR::eFifo; + } + + vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR &capabilities) + { + if (capabilities.currentExtent.width != 0xFFFFFFFF) + { + 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)}; + } + + std::vector getRequiredExtensions() + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + 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; + } }; -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/16_frames_in_flight.cpp b/attachments/16_frames_in_flight.cpp index d98a8031..5b639039 100644 --- a/attachments/16_frames_in_flight.cpp +++ b/attachments/16_frames_in_flight.cpp @@ -10,23 +10,22 @@ #include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -constexpr int MAX_FRAMES_IN_FLIGHT = 2; +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; +constexpr int MAX_FRAMES_IN_FLIGHT = 2; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -34,537 +33,565 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -class HelloTriangleApplication { -public: - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } - -private: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::SurfaceFormatKHR swapChainSurfaceFormat; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - - vk::raii::CommandPool commandPool = nullptr; - std::vector commandBuffers; - - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; - std::vector inFlightFences; - uint32_t semaphoreIndex = 0; - uint32_t currentFrame = 0; - - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - createSurface(); - pickPhysicalDevice(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createGraphicsPipeline(); - createCommandPool(); - createCommandBuffers(); - createSyncObjects(); - } - - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - drawFrame(); - } - - device.waitIdle(); - } - - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - } ); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error( "failed to find a suitable GPU!" ); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain featureChain = { - {}, // vk::PhysicalDeviceFeatures2 - {.synchronization2 = true, .dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( *surface ); - swapChainExtent = chooseSwapExtent( surfaceCapabilities ); - swapChainSurfaceFormat = chooseSwapSurfaceFormat( physicalDevice.getSurfaceFormatsKHR( *surface ) ); - vk::SwapchainCreateInfoKHR swapChainCreateInfo{ .surface = *surface, - .minImageCount = chooseSwapMinImageCount( surfaceCapabilities ), - .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 }; - - swapChain = vk::raii::SwapchainKHR( device, swapChainCreateInfo ); - swapChainImages = swapChain.getImages(); - } - - void createImageViews() { - swapChainImageViews.clear(); - - vk::ImageViewCreateInfo imageViewCreateInfo{ .viewType = vk::ImageViewType::e2D, .format = swapChainSurfaceFormat.format, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } }; - for ( auto image : swapChainImages ) - { - imageViewCreateInfo.image = image; - swapChainImageViews.emplace_back( device, imageViewCreateInfo ); - } - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; - - vk::PipelineVertexInputStateCreateInfo vertexInputInfo; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ .topology = vk::PrimitiveTopology::eTriangleList }; - vk::PipelineViewportStateCreateInfo viewportState{ .viewportCount = 1, .scissorCount = 1 }; - - vk::PipelineRasterizationStateCreateInfo rasterizer{ .depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = vk::False, - .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f }; - - vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; - - vk::PipelineColorBlendAttachmentState colorBlendAttachment{ .blendEnable = vk::False, - .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA - }; - - vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 0, .pushConstantRangeCount = 0 }; - - pipelineLayout = vk::raii::PipelineLayout( device, pipelineLayoutInfo ); - - vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ .colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format }; - vk::GraphicsPipelineCreateInfo pipelineInfo{ .pNext = &pipelineRenderingCreateInfo, - .stageCount = 2, .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, .layout = pipelineLayout, .renderPass = nullptr }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{ .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex }; - commandPool = vk::raii::CommandPool(device, poolInfo); - } - - void createCommandBuffers() { - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = MAX_FRAMES_IN_FLIGHT }; - commandBuffers = vk::raii::CommandBuffers( device, allocInfo ); - } - - void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin( {} ); - // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL - transition_image_layout( - imageIndex, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage - ); - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::RenderingAttachmentInfo attachmentInfo = { - .imageView = swapChainImageViews[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor - }; - vk::RenderingInfo renderingInfo = { - .renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &attachmentInfo - }; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor( 0, vk::Rect2D( vk::Offset2D( 0, 0 ), swapChainExtent ) ); - commandBuffers[currentFrame].draw(3, 1, 0, 0); - commandBuffers[currentFrame].endRendering(); - // After rendering, transition the swapchain image to PRESENT_SRC - transition_image_layout( - imageIndex, - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage - ); - commandBuffers[currentFrame].end(); - } - - void transition_image_layout( - uint32_t imageIndex, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::AccessFlags2 src_access_mask, - vk::AccessFlags2 dst_access_mask, - vk::PipelineStageFlags2 src_stage_mask, - vk::PipelineStageFlags2 dst_stage_mask - ) { - vk::ImageMemoryBarrier2 barrier = { - .srcStageMask = src_stage_mask, - .srcAccessMask = src_access_mask, - .dstStageMask = dst_stage_mask, - .dstAccessMask = dst_access_mask, - .oldLayout = old_layout, - .newLayout = new_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); - } - - void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); - - for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - } - - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - inFlightFences.emplace_back(device, vk::FenceCreateInfo { .flags = vk::FenceCreateFlagBits::eSignaled }); - } - } - - void drawFrame() { - while ( vk::Result::eTimeout == device.waitForFences( *inFlightFences[currentFrame], vk::True, UINT64_MAX ) ) - ; - auto [result, imageIndex] = swapChain.acquireNextImage( UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr ); - - device.resetFences( *inFlightFences[currentFrame] ); - commandBuffers[currentFrame].reset(); - recordCommandBuffer(imageIndex); - - vk::PipelineStageFlags waitDestinationStageMask( vk::PipelineStageFlagBits::eColorAttachmentOutput ); - const vk::SubmitInfo submitInfo{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], - .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], - .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex] }; - queue.submit(submitInfo, *inFlightFences[currentFrame]); - - - const vk::PresentInfoKHR presentInfoKHR{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], - .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex }; - result = queue.presentKHR( presentInfoKHR ); - switch ( result ) - { - case vk::Result::eSuccess: break; - case vk::Result::eSuboptimalKHR: std::cout << "vk::Queue::presentKHR returned vk::Result::eSuboptimalKHR !\n"; break; - default: break; // an unexpected result is returned! - } - semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; - } - - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size() * sizeof(char), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const & surfaceCapabilities) { - auto minImageCount = std::max( 3u, surfaceCapabilities.minImageCount ); - if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) { - minImageCount = surfaceCapabilities.maxImageCount; - } - return minImageCount; - } - - static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { - assert(!availableFormats.empty()); - const auto formatIt = std::ranges::find_if( - availableFormats, - []( const auto & format ) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; } ); - return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - assert(std::ranges::any_of(availablePresentModes, [](auto presentMode){ return presentMode == vk::PresentModeKHR::eFifo; })); - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eFifo; - } - - vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) { - if (capabilities.currentExtent.width != 0xFFFFFFFF) { - 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) - }; - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - 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: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::SurfaceFormatKHR swapChainSurfaceFormat; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + + vk::raii::CommandPool commandPool = nullptr; + std::vector commandBuffers; + + std::vector presentCompleteSemaphore; + std::vector renderFinishedSemaphore; + std::vector inFlightFences; + uint32_t semaphoreIndex = 0; + uint32_t currentFrame = 0; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createGraphicsPipeline(); + createCommandPool(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() + { + while (!glfwWindowShouldClose(window)) + { + glfwPollEvents(); + drawFrame(); + } + + device.waitIdle(); + } + + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain featureChain = { + {}, // vk::PhysicalDeviceFeatures2 + {.synchronization2 = true, .dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(*surface); + swapChainExtent = chooseSwapExtent(surfaceCapabilities); + swapChainSurfaceFormat = chooseSwapSurfaceFormat(physicalDevice.getSurfaceFormatsKHR(*surface)); + vk::SwapchainCreateInfoKHR swapChainCreateInfo{.surface = *surface, + .minImageCount = chooseSwapMinImageCount(surfaceCapabilities), + .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + void createImageViews() + { + swapChainImageViews.clear(); + + vk::ImageViewCreateInfo imageViewCreateInfo{.viewType = vk::ImageViewType::e2D, .format = swapChainSurfaceFormat.format, .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + for (auto image : swapChainImages) + { + imageViewCreateInfo.image = image; + swapChainImageViews.emplace_back(device, imageViewCreateInfo); + } + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; + + vk::PipelineVertexInputStateCreateInfo vertexInputInfo; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{.topology = vk::PrimitiveTopology::eTriangleList}; + vk::PipelineViewportStateCreateInfo viewportState{.viewportCount = 1, .scissorCount = 1}; + + vk::PipelineRasterizationStateCreateInfo rasterizer{.depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = vk::False, .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f}; + + vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; + + vk::PipelineColorBlendAttachmentState colorBlendAttachment{.blendEnable = vk::False, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA}; + + vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 0, .pushConstantRangeCount = 0}; + + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format}; + vk::GraphicsPipelineCreateInfo pipelineInfo{.pNext = &pipelineRenderingCreateInfo, + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex}; + commandPool = vk::raii::CommandPool(device, poolInfo); + } + + void createCommandBuffers() + { + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = MAX_FRAMES_IN_FLIGHT}; + commandBuffers = vk::raii::CommandBuffers(device, allocInfo); + } + + void recordCommandBuffer(uint32_t imageIndex) + { + commandBuffers[currentFrame].begin({}); + // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL + transition_image_layout( + imageIndex, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage + ); + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::RenderingAttachmentInfo attachmentInfo = { + .imageView = swapChainImageViews[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor}; + vk::RenderingInfo renderingInfo = { + .renderArea = {.offset = {0, 0}, .extent = swapChainExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &attachmentInfo}; + commandBuffers[currentFrame].beginRendering(renderingInfo); + commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffers[currentFrame].draw(3, 1, 0, 0); + commandBuffers[currentFrame].endRendering(); + // After rendering, transition the swapchain image to PRESENT_SRC + transition_image_layout( + imageIndex, + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage + ); + commandBuffers[currentFrame].end(); + } + + void transition_image_layout( + uint32_t imageIndex, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout, + vk::AccessFlags2 src_access_mask, + vk::AccessFlags2 dst_access_mask, + vk::PipelineStageFlags2 src_stage_mask, + vk::PipelineStageFlags2 dst_stage_mask) + { + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = src_stage_mask, + .srcAccessMask = src_access_mask, + .dstStageMask = dst_stage_mask, + .dstAccessMask = dst_access_mask, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo dependency_info = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + } + + void createSyncObjects() + { + presentCompleteSemaphore.clear(); + renderFinishedSemaphore.clear(); + inFlightFences.clear(); + + for (size_t i = 0; i < swapChainImages.size(); i++) + { + presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); + } + } + + void drawFrame() + { + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + ; + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr); + + device.resetFences(*inFlightFences[currentFrame]); + commandBuffers[currentFrame].reset(); + recordCommandBuffer(imageIndex); + + vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[currentFrame]); + + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + result = queue.presentKHR(presentInfoKHR); + switch (result) + { + case vk::Result::eSuccess: + break; + case vk::Result::eSuboptimalKHR: + std::cout << "vk::Queue::presentKHR returned vk::Result::eSuboptimalKHR !\n"; + break; + default: + break; // an unexpected result is returned! + } + semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size() * sizeof(char), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const &surfaceCapabilities) + { + auto minImageCount = std::max(3u, surfaceCapabilities.minImageCount); + if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) + { + minImageCount = surfaceCapabilities.maxImageCount; + } + return minImageCount; + } + + static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + assert(!availableFormats.empty()); + const auto formatIt = std::ranges::find_if( + availableFormats, + [](const auto &format) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; }); + return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + assert(std::ranges::any_of(availablePresentModes, [](auto presentMode) { return presentMode == vk::PresentModeKHR::eFifo; })); + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + vk::PresentModeKHR::eFifo; + } + + vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR &capabilities) + { + if (capabilities.currentExtent.width != 0xFFFFFFFF) + { + 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)}; + } + + std::vector getRequiredExtensions() + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + 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; + } }; -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/17_swap_chain_recreation.cpp b/attachments/17_swap_chain_recreation.cpp index 94e1f581..04e8a612 100644 --- a/attachments/17_swap_chain_recreation.cpp +++ b/attachments/17_swap_chain_recreation.cpp @@ -10,23 +10,22 @@ #include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -constexpr int MAX_FRAMES_IN_FLIGHT = 2; +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; +constexpr int MAX_FRAMES_IN_FLIGHT = 2; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -34,573 +33,607 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -class HelloTriangleApplication { -public: - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } - -private: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::SurfaceFormatKHR swapChainSurfaceFormat; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - - vk::raii::CommandPool commandPool = nullptr; - std::vector commandBuffers; - - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; - std::vector inFlightFences; - uint32_t semaphoreIndex = 0; - uint32_t currentFrame = 0; - - bool framebufferResized = false; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); - } - - static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { - auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); - app->framebufferResized = true; - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - createSurface(); - pickPhysicalDevice(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createGraphicsPipeline(); - createCommandPool(); - createCommandBuffers(); - createSyncObjects(); - } - - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - drawFrame(); - } - - device.waitIdle(); - } - - void cleanupSwapChain() { - swapChainImageViews.clear(); - swapChain = nullptr; - } - - void cleanup() { - glfwDestroyWindow(window); - - glfwTerminate(); - } - - void recreateSwapChain() { - int width = 0, height = 0; - glfwGetFramebufferSize(window, &width, &height); - while (width == 0 || height == 0) { - glfwGetFramebufferSize(window, &width, &height); - glfwWaitEvents(); - } - - device.waitIdle(); - - cleanupSwapChain(); - createSwapChain(); - createImageViews(); - } - - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - } ); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error( "failed to find a suitable GPU!" ); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain featureChain = { - {}, // vk::PhysicalDeviceFeatures2 - {.synchronization2 = true, .dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( *surface ); - swapChainExtent = chooseSwapExtent( surfaceCapabilities ); - swapChainSurfaceFormat = chooseSwapSurfaceFormat( physicalDevice.getSurfaceFormatsKHR( *surface ) ); - vk::SwapchainCreateInfoKHR swapChainCreateInfo{ .surface = *surface, - .minImageCount = chooseSwapMinImageCount( surfaceCapabilities ), - .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 }; - - swapChain = vk::raii::SwapchainKHR( device, swapChainCreateInfo ); - swapChainImages = swapChain.getImages(); - } - - void createImageViews() { - swapChainImageViews.clear(); - - vk::ImageViewCreateInfo imageViewCreateInfo{ .viewType = vk::ImageViewType::e2D, .format = swapChainSurfaceFormat.format, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } }; - for ( auto image : swapChainImages ) - { - imageViewCreateInfo.image = image; - swapChainImageViews.emplace_back( device, imageViewCreateInfo ); - } - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; - - vk::PipelineVertexInputStateCreateInfo vertexInputInfo; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ .topology = vk::PrimitiveTopology::eTriangleList }; - vk::PipelineViewportStateCreateInfo viewportState{ .viewportCount = 1, .scissorCount = 1 }; - - vk::PipelineRasterizationStateCreateInfo rasterizer{ .depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = vk::False, - .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f }; - - vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; - - vk::PipelineColorBlendAttachmentState colorBlendAttachment{ .blendEnable = vk::False, - .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA - }; - - vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 0, .pushConstantRangeCount = 0 }; - - pipelineLayout = vk::raii::PipelineLayout( device, pipelineLayoutInfo ); - - vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ .colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format }; - vk::GraphicsPipelineCreateInfo pipelineInfo{ .pNext = &pipelineRenderingCreateInfo, - .stageCount = 2, .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, .layout = pipelineLayout, .renderPass = nullptr }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{ .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex }; - commandPool = vk::raii::CommandPool(device, poolInfo); - } - - void createCommandBuffers() { - commandBuffers.clear(); - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = MAX_FRAMES_IN_FLIGHT }; - commandBuffers = vk::raii::CommandBuffers( device, allocInfo ); - } - - void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin( {} ); - // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL - transition_image_layout( - imageIndex, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage - ); - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::RenderingAttachmentInfo attachmentInfo = { - .imageView = swapChainImageViews[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor - }; - vk::RenderingInfo renderingInfo = { - .renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &attachmentInfo - }; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor( 0, vk::Rect2D( vk::Offset2D( 0, 0 ), swapChainExtent ) ); - commandBuffers[currentFrame].draw(3, 1, 0, 0); - commandBuffers[currentFrame].endRendering(); - // After rendering, transition the swapchain image to PRESENT_SRC - transition_image_layout( - imageIndex, - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage - ); - commandBuffers[currentFrame].end(); - } - - void transition_image_layout( - uint32_t imageIndex, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::AccessFlags2 src_access_mask, - vk::AccessFlags2 dst_access_mask, - vk::PipelineStageFlags2 src_stage_mask, - vk::PipelineStageFlags2 dst_stage_mask - ) { - vk::ImageMemoryBarrier2 barrier = { - .srcStageMask = src_stage_mask, - .srcAccessMask = src_access_mask, - .dstStageMask = dst_stage_mask, - .dstAccessMask = dst_access_mask, - .oldLayout = old_layout, - .newLayout = new_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); - } - - void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); - - for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - } - - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - inFlightFences.emplace_back(device, vk::FenceCreateInfo { .flags = vk::FenceCreateFlagBits::eSignaled }); - } - } - - void drawFrame() { - while ( vk::Result::eTimeout == device.waitForFences( *inFlightFences[currentFrame], vk::True, UINT64_MAX ) ) - ; - auto [result, imageIndex] = swapChain.acquireNextImage( UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr ); - - if (result == vk::Result::eErrorOutOfDateKHR) { - recreateSwapChain(); - return; - } - if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) { - throw std::runtime_error("failed to acquire swap chain image!"); - } - - device.resetFences( *inFlightFences[currentFrame] ); - commandBuffers[currentFrame].reset(); - recordCommandBuffer(imageIndex); - - vk::PipelineStageFlags waitDestinationStageMask( vk::PipelineStageFlagBits::eColorAttachmentOutput ); - const vk::SubmitInfo submitInfo{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], - .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], - .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex] }; - queue.submit(submitInfo, *inFlightFences[currentFrame]); - - - const vk::PresentInfoKHR presentInfoKHR{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], - .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex }; - result = queue.presentKHR( presentInfoKHR ); - if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { - framebufferResized = false; - recreateSwapChain(); - } else if (result != vk::Result::eSuccess) { - throw std::runtime_error("failed to present swap chain image!"); - } - semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; - } - - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size() * sizeof(char), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const & surfaceCapabilities) { - auto minImageCount = std::max( 3u, surfaceCapabilities.minImageCount ); - if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) { - minImageCount = surfaceCapabilities.maxImageCount; - } - return minImageCount; - } - - static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { - assert(!availableFormats.empty()); - const auto formatIt = std::ranges::find_if( - availableFormats, - []( const auto & format ) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; } ); - return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - assert(std::ranges::any_of(availablePresentModes, [](auto presentMode){ return presentMode == vk::PresentModeKHR::eFifo; })); - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eFifo; - } - - vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) { - if (capabilities.currentExtent.width != 0xFFFFFFFF) { - 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) - }; - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - 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: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::SurfaceFormatKHR swapChainSurfaceFormat; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + + vk::raii::CommandPool commandPool = nullptr; + std::vector commandBuffers; + + std::vector presentCompleteSemaphore; + std::vector renderFinishedSemaphore; + std::vector inFlightFences; + uint32_t semaphoreIndex = 0; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow *window, int width, int height) + { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createGraphicsPipeline(); + createCommandPool(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() + { + while (!glfwWindowShouldClose(window)) + { + glfwPollEvents(); + drawFrame(); + } + + device.waitIdle(); + } + + void cleanupSwapChain() + { + swapChainImageViews.clear(); + swapChain = nullptr; + } + + void cleanup() + { + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() + { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) + { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + device.waitIdle(); + + cleanupSwapChain(); + createSwapChain(); + createImageViews(); + } + + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain featureChain = { + {}, // vk::PhysicalDeviceFeatures2 + {.synchronization2 = true, .dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(*surface); + swapChainExtent = chooseSwapExtent(surfaceCapabilities); + swapChainSurfaceFormat = chooseSwapSurfaceFormat(physicalDevice.getSurfaceFormatsKHR(*surface)); + vk::SwapchainCreateInfoKHR swapChainCreateInfo{.surface = *surface, + .minImageCount = chooseSwapMinImageCount(surfaceCapabilities), + .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + void createImageViews() + { + swapChainImageViews.clear(); + + vk::ImageViewCreateInfo imageViewCreateInfo{.viewType = vk::ImageViewType::e2D, .format = swapChainSurfaceFormat.format, .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + for (auto image : swapChainImages) + { + imageViewCreateInfo.image = image; + swapChainImageViews.emplace_back(device, imageViewCreateInfo); + } + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; + + vk::PipelineVertexInputStateCreateInfo vertexInputInfo; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{.topology = vk::PrimitiveTopology::eTriangleList}; + vk::PipelineViewportStateCreateInfo viewportState{.viewportCount = 1, .scissorCount = 1}; + + vk::PipelineRasterizationStateCreateInfo rasterizer{.depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = vk::False, .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f}; + + vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; + + vk::PipelineColorBlendAttachmentState colorBlendAttachment{.blendEnable = vk::False, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA}; + + vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 0, .pushConstantRangeCount = 0}; + + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format}; + vk::GraphicsPipelineCreateInfo pipelineInfo{.pNext = &pipelineRenderingCreateInfo, + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex}; + commandPool = vk::raii::CommandPool(device, poolInfo); + } + + void createCommandBuffers() + { + commandBuffers.clear(); + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = MAX_FRAMES_IN_FLIGHT}; + commandBuffers = vk::raii::CommandBuffers(device, allocInfo); + } + + void recordCommandBuffer(uint32_t imageIndex) + { + commandBuffers[currentFrame].begin({}); + // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL + transition_image_layout( + imageIndex, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage + ); + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::RenderingAttachmentInfo attachmentInfo = { + .imageView = swapChainImageViews[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor}; + vk::RenderingInfo renderingInfo = { + .renderArea = {.offset = {0, 0}, .extent = swapChainExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &attachmentInfo}; + commandBuffers[currentFrame].beginRendering(renderingInfo); + commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffers[currentFrame].draw(3, 1, 0, 0); + commandBuffers[currentFrame].endRendering(); + // After rendering, transition the swapchain image to PRESENT_SRC + transition_image_layout( + imageIndex, + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage + ); + commandBuffers[currentFrame].end(); + } + + void transition_image_layout( + uint32_t imageIndex, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout, + vk::AccessFlags2 src_access_mask, + vk::AccessFlags2 dst_access_mask, + vk::PipelineStageFlags2 src_stage_mask, + vk::PipelineStageFlags2 dst_stage_mask) + { + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = src_stage_mask, + .srcAccessMask = src_access_mask, + .dstStageMask = dst_stage_mask, + .dstAccessMask = dst_access_mask, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo dependency_info = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + } + + void createSyncObjects() + { + presentCompleteSemaphore.clear(); + renderFinishedSemaphore.clear(); + inFlightFences.clear(); + + for (size_t i = 0; i < swapChainImages.size(); i++) + { + presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); + } + } + + void drawFrame() + { + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + ; + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr); + + if (result == vk::Result::eErrorOutOfDateKHR) + { + recreateSwapChain(); + return; + } + if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) + { + throw std::runtime_error("failed to acquire swap chain image!"); + } + + device.resetFences(*inFlightFences[currentFrame]); + commandBuffers[currentFrame].reset(); + recordCommandBuffer(imageIndex); + + vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[currentFrame]); + + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + result = queue.presentKHR(presentInfoKHR); + if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) + { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != vk::Result::eSuccess) + { + throw std::runtime_error("failed to present swap chain image!"); + } + semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size() * sizeof(char), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const &surfaceCapabilities) + { + auto minImageCount = std::max(3u, surfaceCapabilities.minImageCount); + if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) + { + minImageCount = surfaceCapabilities.maxImageCount; + } + return minImageCount; + } + + static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + assert(!availableFormats.empty()); + const auto formatIt = std::ranges::find_if( + availableFormats, + [](const auto &format) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; }); + return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + assert(std::ranges::any_of(availablePresentModes, [](auto presentMode) { return presentMode == vk::PresentModeKHR::eFifo; })); + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + vk::PresentModeKHR::eFifo; + } + + vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR &capabilities) + { + if (capabilities.currentExtent.width != 0xFFFFFFFF) + { + 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)}; + } + + std::vector getRequiredExtensions() + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + 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; + } }; -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/18_vertex_input.cpp b/attachments/18_vertex_input.cpp index e8dd513e..27f8011f 100644 --- a/attachments/18_vertex_input.cpp +++ b/attachments/18_vertex_input.cpp @@ -11,24 +11,23 @@ #include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -constexpr int MAX_FRAMES_IN_FLIGHT = 2; +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; +constexpr int MAX_FRAMES_IN_FLIGHT = 2; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -36,598 +35,632 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -struct Vertex { - glm::vec2 pos; - glm::vec3 color; - - static vk::VertexInputBindingDescription getBindingDescription() { - return { 0, sizeof(Vertex), vk::VertexInputRate::eVertex }; - } - - static std::array getAttributeDescriptions() { - return { - vk::VertexInputAttributeDescription( 0, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, pos) ), - vk::VertexInputAttributeDescription( 1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color) ) - }; - } +struct Vertex +{ + glm::vec2 pos; + glm::vec3 color; + + static vk::VertexInputBindingDescription getBindingDescription() + { + return {0, sizeof(Vertex), vk::VertexInputRate::eVertex}; + } + + static std::array getAttributeDescriptions() + { + return { + vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, pos)), + vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color))}; + } }; const std::vector vertices = { {{0.0f, -0.5f}, {1.0f, 0.0f, 0.0f}}, {{0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}}, - {{-0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}} + {{-0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}}; + +class HelloTriangleApplication +{ + public: + void run() + { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + + private: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::SurfaceFormatKHR swapChainSurfaceFormat; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + + vk::raii::CommandPool commandPool = nullptr; + std::vector commandBuffers; + + std::vector presentCompleteSemaphore; + std::vector renderFinishedSemaphore; + std::vector inFlightFences; + uint32_t semaphoreIndex = 0; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow *window, int width, int height) + { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createGraphicsPipeline(); + createCommandPool(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() + { + while (!glfwWindowShouldClose(window)) + { + glfwPollEvents(); + drawFrame(); + } + + device.waitIdle(); + } + + void cleanupSwapChain() + { + swapChainImageViews.clear(); + swapChain = nullptr; + } + + void cleanup() + { + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() + { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) + { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + device.waitIdle(); + + cleanupSwapChain(); + createSwapChain(); + createImageViews(); + } + + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain featureChain = { + {}, // vk::PhysicalDeviceFeatures2 + {.synchronization2 = true, .dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(*surface); + swapChainExtent = chooseSwapExtent(surfaceCapabilities); + swapChainSurfaceFormat = chooseSwapSurfaceFormat(physicalDevice.getSurfaceFormatsKHR(*surface)); + vk::SwapchainCreateInfoKHR swapChainCreateInfo{.surface = *surface, + .minImageCount = chooseSwapMinImageCount(surfaceCapabilities), + .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + void createImageViews() + { + swapChainImageViews.clear(); + + vk::ImageViewCreateInfo imageViewCreateInfo{.viewType = vk::ImageViewType::e2D, .format = swapChainSurfaceFormat.format, .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + for (auto image : swapChainImages) + { + imageViewCreateInfo.image = image; + swapChainImageViews.emplace_back(device, imageViewCreateInfo); + } + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; + + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{.vertexBindingDescriptionCount = 1, .pVertexBindingDescriptions = &bindingDescription, .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), .pVertexAttributeDescriptions = attributeDescriptions.data()}; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{.topology = vk::PrimitiveTopology::eTriangleList}; + vk::PipelineViewportStateCreateInfo viewportState{.viewportCount = 1, .scissorCount = 1}; + + vk::PipelineRasterizationStateCreateInfo rasterizer{.depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = vk::False, .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f}; + + vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; + + vk::PipelineColorBlendAttachmentState colorBlendAttachment{.blendEnable = vk::False, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA}; + + vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 0, .pushConstantRangeCount = 0}; + + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format}; + vk::GraphicsPipelineCreateInfo pipelineInfo{.pNext = &pipelineRenderingCreateInfo, + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex}; + commandPool = vk::raii::CommandPool(device, poolInfo); + } + + void createCommandBuffers() + { + commandBuffers.clear(); + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = MAX_FRAMES_IN_FLIGHT}; + commandBuffers = vk::raii::CommandBuffers(device, allocInfo); + } + + void recordCommandBuffer(uint32_t imageIndex) + { + commandBuffers[currentFrame].begin({}); + // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL + transition_image_layout( + imageIndex, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage + ); + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::RenderingAttachmentInfo attachmentInfo = { + .imageView = swapChainImageViews[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor}; + vk::RenderingInfo renderingInfo = { + .renderArea = {.offset = {0, 0}, .extent = swapChainExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &attachmentInfo}; + commandBuffers[currentFrame].beginRendering(renderingInfo); + commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffers[currentFrame].draw(3, 1, 0, 0); + commandBuffers[currentFrame].endRendering(); + // After rendering, transition the swapchain image to PRESENT_SRC + transition_image_layout( + imageIndex, + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage + ); + commandBuffers[currentFrame].end(); + } + + void transition_image_layout( + uint32_t imageIndex, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout, + vk::AccessFlags2 src_access_mask, + vk::AccessFlags2 dst_access_mask, + vk::PipelineStageFlags2 src_stage_mask, + vk::PipelineStageFlags2 dst_stage_mask) + { + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = src_stage_mask, + .srcAccessMask = src_access_mask, + .dstStageMask = dst_stage_mask, + .dstAccessMask = dst_access_mask, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo dependency_info = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + } + + void createSyncObjects() + { + presentCompleteSemaphore.clear(); + renderFinishedSemaphore.clear(); + inFlightFences.clear(); + + for (size_t i = 0; i < swapChainImages.size(); i++) + { + presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); + } + } + + void drawFrame() + { + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + ; + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr); + + if (result == vk::Result::eErrorOutOfDateKHR) + { + recreateSwapChain(); + return; + } + if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) + { + throw std::runtime_error("failed to acquire swap chain image!"); + } + + device.resetFences(*inFlightFences[currentFrame]); + commandBuffers[currentFrame].reset(); + recordCommandBuffer(imageIndex); + + vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[currentFrame]); + + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + result = queue.presentKHR(presentInfoKHR); + if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) + { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != vk::Result::eSuccess) + { + throw std::runtime_error("failed to present swap chain image!"); + } + semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size() * sizeof(char), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const &surfaceCapabilities) + { + auto minImageCount = std::max(3u, surfaceCapabilities.minImageCount); + if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) + { + minImageCount = surfaceCapabilities.maxImageCount; + } + return minImageCount; + } + + static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + assert(!availableFormats.empty()); + const auto formatIt = std::ranges::find_if( + availableFormats, + [](const auto &format) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; }); + return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + assert(std::ranges::any_of(availablePresentModes, [](auto presentMode) { return presentMode == vk::PresentModeKHR::eFifo; })); + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + vk::PresentModeKHR::eFifo; + } + + vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR &capabilities) + { + if (capabilities.currentExtent.width != 0xFFFFFFFF) + { + 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)}; + } + + std::vector getRequiredExtensions() + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + 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: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::SurfaceFormatKHR swapChainSurfaceFormat; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - - vk::raii::CommandPool commandPool = nullptr; - std::vector commandBuffers; - - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; - std::vector inFlightFences; - uint32_t semaphoreIndex = 0; - uint32_t currentFrame = 0; - - bool framebufferResized = false; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); - } - - static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { - auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); - app->framebufferResized = true; - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - createSurface(); - pickPhysicalDevice(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createGraphicsPipeline(); - createCommandPool(); - createCommandBuffers(); - createSyncObjects(); - } - - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - drawFrame(); - } - - device.waitIdle(); - } - - void cleanupSwapChain() { - swapChainImageViews.clear(); - swapChain = nullptr; - } - - void cleanup() { - glfwDestroyWindow(window); - - glfwTerminate(); - } - - void recreateSwapChain() { - int width = 0, height = 0; - glfwGetFramebufferSize(window, &width, &height); - while (width == 0 || height == 0) { - glfwGetFramebufferSize(window, &width, &height); - glfwWaitEvents(); - } - - device.waitIdle(); - - cleanupSwapChain(); - createSwapChain(); - createImageViews(); - } - - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - } ); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error( "failed to find a suitable GPU!" ); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain featureChain = { - {}, // vk::PhysicalDeviceFeatures2 - {.synchronization2 = true, .dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( *surface ); - swapChainExtent = chooseSwapExtent( surfaceCapabilities ); - swapChainSurfaceFormat = chooseSwapSurfaceFormat( physicalDevice.getSurfaceFormatsKHR( *surface ) ); - vk::SwapchainCreateInfoKHR swapChainCreateInfo{ .surface = *surface, - .minImageCount = chooseSwapMinImageCount( surfaceCapabilities ), - .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 }; - - swapChain = vk::raii::SwapchainKHR( device, swapChainCreateInfo ); - swapChainImages = swapChain.getImages(); - } - - void createImageViews() { - swapChainImageViews.clear(); - - vk::ImageViewCreateInfo imageViewCreateInfo{ .viewType = vk::ImageViewType::e2D, .format = swapChainSurfaceFormat.format, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } }; - for ( auto image : swapChainImages ) - { - imageViewCreateInfo.image = image; - swapChainImageViews.emplace_back( device, imageViewCreateInfo ); - } - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; - - auto bindingDescription = Vertex::getBindingDescription(); - auto attributeDescriptions = Vertex::getAttributeDescriptions(); - vk::PipelineVertexInputStateCreateInfo vertexInputInfo { .vertexBindingDescriptionCount =1, .pVertexBindingDescriptions = &bindingDescription, - .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), .pVertexAttributeDescriptions = attributeDescriptions.data() }; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ .topology = vk::PrimitiveTopology::eTriangleList }; - vk::PipelineViewportStateCreateInfo viewportState{ .viewportCount = 1, .scissorCount = 1 }; - - vk::PipelineRasterizationStateCreateInfo rasterizer{ .depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = vk::False, - .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f }; - - vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; - - vk::PipelineColorBlendAttachmentState colorBlendAttachment{ .blendEnable = vk::False, - .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA - }; - - vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 0, .pushConstantRangeCount = 0 }; - - pipelineLayout = vk::raii::PipelineLayout( device, pipelineLayoutInfo ); - - vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ .colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format }; - vk::GraphicsPipelineCreateInfo pipelineInfo{ .pNext = &pipelineRenderingCreateInfo, - .stageCount = 2, .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, .layout = pipelineLayout, .renderPass = nullptr }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{ .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex }; - commandPool = vk::raii::CommandPool(device, poolInfo); - } - - void createCommandBuffers() { - commandBuffers.clear(); - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = MAX_FRAMES_IN_FLIGHT }; - commandBuffers = vk::raii::CommandBuffers( device, allocInfo ); - } - - void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin( {} ); - // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL - transition_image_layout( - imageIndex, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage - ); - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::RenderingAttachmentInfo attachmentInfo = { - .imageView = swapChainImageViews[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor - }; - vk::RenderingInfo renderingInfo = { - .renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &attachmentInfo - }; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor( 0, vk::Rect2D( vk::Offset2D( 0, 0 ), swapChainExtent ) ); - commandBuffers[currentFrame].draw(3, 1, 0, 0); - commandBuffers[currentFrame].endRendering(); - // After rendering, transition the swapchain image to PRESENT_SRC - transition_image_layout( - imageIndex, - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage - ); - commandBuffers[currentFrame].end(); - } - - void transition_image_layout( - uint32_t imageIndex, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::AccessFlags2 src_access_mask, - vk::AccessFlags2 dst_access_mask, - vk::PipelineStageFlags2 src_stage_mask, - vk::PipelineStageFlags2 dst_stage_mask - ) { - vk::ImageMemoryBarrier2 barrier = { - .srcStageMask = src_stage_mask, - .srcAccessMask = src_access_mask, - .dstStageMask = dst_stage_mask, - .dstAccessMask = dst_access_mask, - .oldLayout = old_layout, - .newLayout = new_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); - } - - void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); - - for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - } - - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - inFlightFences.emplace_back(device, vk::FenceCreateInfo { .flags = vk::FenceCreateFlagBits::eSignaled }); - } - } - - void drawFrame() { - while ( vk::Result::eTimeout == device.waitForFences( *inFlightFences[currentFrame], vk::True, UINT64_MAX ) ) - ; - auto [result, imageIndex] = swapChain.acquireNextImage( UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr ); - - if (result == vk::Result::eErrorOutOfDateKHR) { - recreateSwapChain(); - return; - } - if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) { - throw std::runtime_error("failed to acquire swap chain image!"); - } - - device.resetFences( *inFlightFences[currentFrame] ); - commandBuffers[currentFrame].reset(); - recordCommandBuffer(imageIndex); - - vk::PipelineStageFlags waitDestinationStageMask( vk::PipelineStageFlagBits::eColorAttachmentOutput ); - const vk::SubmitInfo submitInfo{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], - .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], - .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex] }; - queue.submit(submitInfo, *inFlightFences[currentFrame]); - - - const vk::PresentInfoKHR presentInfoKHR{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], - .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex }; - result = queue.presentKHR( presentInfoKHR ); - if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { - framebufferResized = false; - recreateSwapChain(); - } else if (result != vk::Result::eSuccess) { - throw std::runtime_error("failed to present swap chain image!"); - } - semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; - } - - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size() * sizeof(char), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const & surfaceCapabilities) { - auto minImageCount = std::max( 3u, surfaceCapabilities.minImageCount ); - if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) { - minImageCount = surfaceCapabilities.maxImageCount; - } - return minImageCount; - } - - static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { - assert(!availableFormats.empty()); - const auto formatIt = std::ranges::find_if( - availableFormats, - []( const auto & format ) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; } ); - return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - assert(std::ranges::any_of(availablePresentModes, [](auto presentMode){ return presentMode == vk::PresentModeKHR::eFifo; })); - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eFifo; - } - - vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) { - if (capabilities.currentExtent.width != 0xFFFFFFFF) { - 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) - }; - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - 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; - } -}; - -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/19_vertex_buffer.cpp b/attachments/19_vertex_buffer.cpp index 48032fd1..4b69a8ed 100644 --- a/attachments/19_vertex_buffer.cpp +++ b/attachments/19_vertex_buffer.cpp @@ -11,24 +11,23 @@ #include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -constexpr int MAX_FRAMES_IN_FLIGHT = 2; +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; +constexpr int MAX_FRAMES_IN_FLIGHT = 2; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -36,628 +35,666 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -struct Vertex { - glm::vec2 pos; - glm::vec3 color; - - static vk::VertexInputBindingDescription getBindingDescription() { - return { 0, sizeof(Vertex), vk::VertexInputRate::eVertex }; - } - - static std::array getAttributeDescriptions() { - return { - vk::VertexInputAttributeDescription( 0, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, pos) ), - vk::VertexInputAttributeDescription( 1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color) ) - }; - } +struct Vertex +{ + glm::vec2 pos; + glm::vec3 color; + + static vk::VertexInputBindingDescription getBindingDescription() + { + return {0, sizeof(Vertex), vk::VertexInputRate::eVertex}; + } + + static std::array getAttributeDescriptions() + { + return { + vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, pos)), + vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color))}; + } }; const std::vector vertices = { {{0.0f, -0.5f}, {1.0f, 0.0f, 0.0f}}, {{0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}}, - {{-0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}} + {{-0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}}; + +class HelloTriangleApplication +{ + public: + void run() + { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + + private: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::SurfaceFormatKHR swapChainSurfaceFormat; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + + vk::raii::Buffer vertexBuffer = nullptr; + vk::raii::DeviceMemory vertexBufferMemory = nullptr; + + vk::raii::CommandPool commandPool = nullptr; + std::vector commandBuffers; + + std::vector presentCompleteSemaphore; + std::vector renderFinishedSemaphore; + std::vector inFlightFences; + uint32_t semaphoreIndex = 0; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow *window, int width, int height) + { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createGraphicsPipeline(); + createCommandPool(); + createVertexBuffer(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() + { + while (!glfwWindowShouldClose(window)) + { + glfwPollEvents(); + drawFrame(); + } + + device.waitIdle(); + } + + void cleanupSwapChain() + { + swapChainImageViews.clear(); + swapChain = nullptr; + } + + void cleanup() + { + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() + { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) + { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + device.waitIdle(); + + cleanupSwapChain(); + createSwapChain(); + createImageViews(); + } + + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain featureChain = { + {}, // vk::PhysicalDeviceFeatures2 + {.synchronization2 = true, .dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(*surface); + swapChainExtent = chooseSwapExtent(surfaceCapabilities); + swapChainSurfaceFormat = chooseSwapSurfaceFormat(physicalDevice.getSurfaceFormatsKHR(*surface)); + vk::SwapchainCreateInfoKHR swapChainCreateInfo{.surface = *surface, + .minImageCount = chooseSwapMinImageCount(surfaceCapabilities), + .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + void createImageViews() + { + vk::ImageViewCreateInfo imageViewCreateInfo{.viewType = vk::ImageViewType::e2D, .format = swapChainSurfaceFormat.format, .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + for (auto image : swapChainImages) + { + imageViewCreateInfo.image = image; + swapChainImageViews.emplace_back(device, imageViewCreateInfo); + } + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; + + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{.vertexBindingDescriptionCount = 1, .pVertexBindingDescriptions = &bindingDescription, .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), .pVertexAttributeDescriptions = attributeDescriptions.data()}; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{.topology = vk::PrimitiveTopology::eTriangleList}; + vk::PipelineViewportStateCreateInfo viewportState{.viewportCount = 1, .scissorCount = 1}; + + vk::PipelineRasterizationStateCreateInfo rasterizer{.depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = vk::False, .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f}; + + vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; + + vk::PipelineColorBlendAttachmentState colorBlendAttachment{.blendEnable = vk::False, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA}; + + vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 0, .pushConstantRangeCount = 0}; + + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format}; + vk::GraphicsPipelineCreateInfo pipelineInfo{.pNext = &pipelineRenderingCreateInfo, + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex}; + commandPool = vk::raii::CommandPool(device, poolInfo); + } + + void createVertexBuffer() + { + vk::BufferCreateInfo bufferInfo{.size = sizeof(vertices[0]) * vertices.size(), .usage = vk::BufferUsageFlagBits::eVertexBuffer, .sharingMode = vk::SharingMode::eExclusive}; + vertexBuffer = vk::raii::Buffer(device, bufferInfo); + + vk::MemoryRequirements memRequirements = vertexBuffer.getMemoryRequirements(); + vk::MemoryAllocateInfo memoryAllocateInfo{.allocationSize = memRequirements.size, .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent)}; + vertexBufferMemory = vk::raii::DeviceMemory(device, memoryAllocateInfo); + + vertexBuffer.bindMemory(*vertexBufferMemory, 0); + + void *data = vertexBufferMemory.mapMemory(0, bufferInfo.size); + memcpy(data, vertices.data(), bufferInfo.size); + vertexBufferMemory.unmapMemory(); + } + + uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) + { + vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) + { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) + { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + void createCommandBuffers() + { + commandBuffers.clear(); + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = MAX_FRAMES_IN_FLIGHT}; + commandBuffers = vk::raii::CommandBuffers(device, allocInfo); + } + + void recordCommandBuffer(uint32_t imageIndex) + { + commandBuffers[currentFrame].begin({}); + // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL + transition_image_layout( + imageIndex, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage + ); + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::RenderingAttachmentInfo attachmentInfo = { + .imageView = swapChainImageViews[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor}; + vk::RenderingInfo renderingInfo = { + .renderArea = {.offset = {0, 0}, .extent = swapChainExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &attachmentInfo}; + commandBuffers[currentFrame].beginRendering(renderingInfo); + commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffers[currentFrame].draw(3, 1, 0, 0); + commandBuffers[currentFrame].endRendering(); + // After rendering, transition the swapchain image to PRESENT_SRC + transition_image_layout( + imageIndex, + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage + ); + commandBuffers[currentFrame].end(); + } + + void transition_image_layout( + uint32_t imageIndex, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout, + vk::AccessFlags2 src_access_mask, + vk::AccessFlags2 dst_access_mask, + vk::PipelineStageFlags2 src_stage_mask, + vk::PipelineStageFlags2 dst_stage_mask) + { + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = src_stage_mask, + .srcAccessMask = src_access_mask, + .dstStageMask = dst_stage_mask, + .dstAccessMask = dst_access_mask, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo dependency_info = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + } + + void createSyncObjects() + { + presentCompleteSemaphore.clear(); + renderFinishedSemaphore.clear(); + inFlightFences.clear(); + + for (size_t i = 0; i < swapChainImages.size(); i++) + { + presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); + } + } + + void drawFrame() + { + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + ; + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr); + + if (result == vk::Result::eErrorOutOfDateKHR) + { + recreateSwapChain(); + return; + } + if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) + { + throw std::runtime_error("failed to acquire swap chain image!"); + } + + device.resetFences(*inFlightFences[currentFrame]); + commandBuffers[currentFrame].reset(); + recordCommandBuffer(imageIndex); + + vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[currentFrame]); + + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + result = queue.presentKHR(presentInfoKHR); + if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) + { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != vk::Result::eSuccess) + { + throw std::runtime_error("failed to present swap chain image!"); + } + semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size() * sizeof(char), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const &surfaceCapabilities) + { + auto minImageCount = std::max(3u, surfaceCapabilities.minImageCount); + if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) + { + minImageCount = surfaceCapabilities.maxImageCount; + } + return minImageCount; + } + + static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + assert(!availableFormats.empty()); + const auto formatIt = std::ranges::find_if( + availableFormats, + [](const auto &format) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; }); + return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + assert(std::ranges::any_of(availablePresentModes, [](auto presentMode) { return presentMode == vk::PresentModeKHR::eFifo; })); + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + vk::PresentModeKHR::eFifo; + } + + vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR &capabilities) + { + if (capabilities.currentExtent.width != 0xFFFFFFFF) + { + 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)}; + } + + std::vector getRequiredExtensions() + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + 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: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::SurfaceFormatKHR swapChainSurfaceFormat; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - - vk::raii::Buffer vertexBuffer = nullptr; - vk::raii::DeviceMemory vertexBufferMemory = nullptr; - - vk::raii::CommandPool commandPool = nullptr; - std::vector commandBuffers; - - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; - std::vector inFlightFences; - uint32_t semaphoreIndex = 0; - uint32_t currentFrame = 0; - - bool framebufferResized = false; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); - } - - static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { - auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); - app->framebufferResized = true; - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - createSurface(); - pickPhysicalDevice(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createGraphicsPipeline(); - createCommandPool(); - createVertexBuffer(); - createCommandBuffers(); - createSyncObjects(); - } - - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - drawFrame(); - } - - device.waitIdle(); - } - - void cleanupSwapChain() { - swapChainImageViews.clear(); - swapChain = nullptr; - } - - void cleanup() { - glfwDestroyWindow(window); - - glfwTerminate(); - } - - void recreateSwapChain() { - int width = 0, height = 0; - glfwGetFramebufferSize(window, &width, &height); - while (width == 0 || height == 0) { - glfwGetFramebufferSize(window, &width, &height); - glfwWaitEvents(); - } - - device.waitIdle(); - - cleanupSwapChain(); - createSwapChain(); - createImageViews(); - } - - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - } ); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error( "failed to find a suitable GPU!" ); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain featureChain = { - {}, // vk::PhysicalDeviceFeatures2 - {.synchronization2 = true, .dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( *surface ); - swapChainExtent = chooseSwapExtent( surfaceCapabilities ); - swapChainSurfaceFormat = chooseSwapSurfaceFormat( physicalDevice.getSurfaceFormatsKHR( *surface ) ); - vk::SwapchainCreateInfoKHR swapChainCreateInfo{ .surface = *surface, - .minImageCount = chooseSwapMinImageCount( surfaceCapabilities ), - .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 }; - - swapChain = vk::raii::SwapchainKHR( device, swapChainCreateInfo ); - swapChainImages = swapChain.getImages(); - } - - void createImageViews() { - vk::ImageViewCreateInfo imageViewCreateInfo{ .viewType = vk::ImageViewType::e2D, .format = swapChainSurfaceFormat.format, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } }; - for ( auto image : swapChainImages ) - { - imageViewCreateInfo.image = image; - swapChainImageViews.emplace_back( device, imageViewCreateInfo ); - } - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; - - auto bindingDescription = Vertex::getBindingDescription(); - auto attributeDescriptions = Vertex::getAttributeDescriptions(); - vk::PipelineVertexInputStateCreateInfo vertexInputInfo { .vertexBindingDescriptionCount =1, .pVertexBindingDescriptions = &bindingDescription, - .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), .pVertexAttributeDescriptions = attributeDescriptions.data() }; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ .topology = vk::PrimitiveTopology::eTriangleList }; - vk::PipelineViewportStateCreateInfo viewportState{ .viewportCount = 1, .scissorCount = 1 }; - - vk::PipelineRasterizationStateCreateInfo rasterizer{ .depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = vk::False, - .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f }; - - vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; - - vk::PipelineColorBlendAttachmentState colorBlendAttachment{ .blendEnable = vk::False, - .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA - }; - - vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 0, .pushConstantRangeCount = 0 }; - - pipelineLayout = vk::raii::PipelineLayout( device, pipelineLayoutInfo ); - - vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ .colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format }; - vk::GraphicsPipelineCreateInfo pipelineInfo{ .pNext = &pipelineRenderingCreateInfo, - .stageCount = 2, .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, .layout = pipelineLayout, .renderPass = nullptr }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{ .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex }; - commandPool = vk::raii::CommandPool(device, poolInfo); - } - - void createVertexBuffer() { - vk::BufferCreateInfo bufferInfo{ .size = sizeof(vertices[0]) * vertices.size(), .usage = vk::BufferUsageFlagBits::eVertexBuffer, .sharingMode = vk::SharingMode::eExclusive }; - vertexBuffer = vk::raii::Buffer(device, bufferInfo); - - vk::MemoryRequirements memRequirements = vertexBuffer.getMemoryRequirements(); - vk::MemoryAllocateInfo memoryAllocateInfo{ .allocationSize = memRequirements.size, .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent) }; - vertexBufferMemory = vk::raii::DeviceMemory( device, memoryAllocateInfo ); - - vertexBuffer.bindMemory( *vertexBufferMemory, 0 ); - - void* data = vertexBufferMemory.mapMemory(0, bufferInfo.size); - memcpy(data, vertices.data(), bufferInfo.size); - vertexBufferMemory.unmapMemory(); - } - - uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) { - vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); - - for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { - if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { - return i; - } - } - - throw std::runtime_error("failed to find suitable memory type!"); - } - - void createCommandBuffers() { - commandBuffers.clear(); - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = MAX_FRAMES_IN_FLIGHT }; - commandBuffers = vk::raii::CommandBuffers( device, allocInfo ); - } - - void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin( {} ); - // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL - transition_image_layout( - imageIndex, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage - ); - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::RenderingAttachmentInfo attachmentInfo = { - .imageView = swapChainImageViews[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor - }; - vk::RenderingInfo renderingInfo = { - .renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &attachmentInfo - }; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor( 0, vk::Rect2D( vk::Offset2D( 0, 0 ), swapChainExtent ) ); - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].draw(3, 1, 0, 0); - commandBuffers[currentFrame].endRendering(); - // After rendering, transition the swapchain image to PRESENT_SRC - transition_image_layout( - imageIndex, - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage - ); - commandBuffers[currentFrame].end(); - } - - void transition_image_layout( - uint32_t imageIndex, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::AccessFlags2 src_access_mask, - vk::AccessFlags2 dst_access_mask, - vk::PipelineStageFlags2 src_stage_mask, - vk::PipelineStageFlags2 dst_stage_mask - ) { - vk::ImageMemoryBarrier2 barrier = { - .srcStageMask = src_stage_mask, - .srcAccessMask = src_access_mask, - .dstStageMask = dst_stage_mask, - .dstAccessMask = dst_access_mask, - .oldLayout = old_layout, - .newLayout = new_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); - } - - void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); - - for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - } - - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - inFlightFences.emplace_back(device, vk::FenceCreateInfo { .flags = vk::FenceCreateFlagBits::eSignaled }); - } - } - - void drawFrame() { - while ( vk::Result::eTimeout == device.waitForFences( *inFlightFences[currentFrame], vk::True, UINT64_MAX ) ) - ; - auto [result, imageIndex] = swapChain.acquireNextImage( UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr ); - - if (result == vk::Result::eErrorOutOfDateKHR) { - recreateSwapChain(); - return; - } - if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) { - throw std::runtime_error("failed to acquire swap chain image!"); - } - - device.resetFences( *inFlightFences[currentFrame] ); - commandBuffers[currentFrame].reset(); - recordCommandBuffer(imageIndex); - - vk::PipelineStageFlags waitDestinationStageMask( vk::PipelineStageFlagBits::eColorAttachmentOutput ); - const vk::SubmitInfo submitInfo{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], - .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], - .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex] }; - queue.submit(submitInfo, *inFlightFences[currentFrame]); - - - const vk::PresentInfoKHR presentInfoKHR{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], - .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex }; - result = queue.presentKHR( presentInfoKHR ); - if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { - framebufferResized = false; - recreateSwapChain(); - } else if (result != vk::Result::eSuccess) { - throw std::runtime_error("failed to present swap chain image!"); - } - semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; - } - - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size() * sizeof(char), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const & surfaceCapabilities) { - auto minImageCount = std::max( 3u, surfaceCapabilities.minImageCount ); - if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) { - minImageCount = surfaceCapabilities.maxImageCount; - } - return minImageCount; - } - - static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { - assert(!availableFormats.empty()); - const auto formatIt = std::ranges::find_if( - availableFormats, - []( const auto & format ) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; } ); - return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - assert(std::ranges::any_of(availablePresentModes, [](auto presentMode){ return presentMode == vk::PresentModeKHR::eFifo; })); - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eFifo; - } - - vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) { - if (capabilities.currentExtent.width != 0xFFFFFFFF) { - 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) - }; - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - 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; - } -}; - -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/20_staging_buffer.cpp b/attachments/20_staging_buffer.cpp index 7618e174..465cb93c 100644 --- a/attachments/20_staging_buffer.cpp +++ b/attachments/20_staging_buffer.cpp @@ -11,24 +11,23 @@ #include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -constexpr int MAX_FRAMES_IN_FLIGHT = 2; +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; +constexpr int MAX_FRAMES_IN_FLIGHT = 2; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -36,647 +35,686 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -struct Vertex { - glm::vec2 pos; - glm::vec3 color; - - static vk::VertexInputBindingDescription getBindingDescription() { - return { 0, sizeof(Vertex), vk::VertexInputRate::eVertex }; - } - - static std::array getAttributeDescriptions() { - return { - vk::VertexInputAttributeDescription( 0, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, pos) ), - vk::VertexInputAttributeDescription( 1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color) ) - }; - } +struct Vertex +{ + glm::vec2 pos; + glm::vec3 color; + + static vk::VertexInputBindingDescription getBindingDescription() + { + return {0, sizeof(Vertex), vk::VertexInputRate::eVertex}; + } + + static std::array getAttributeDescriptions() + { + return { + vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, pos)), + vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color))}; + } }; const std::vector vertices = { {{0.0f, -0.5f}, {1.0f, 0.0f, 0.0f}}, {{0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}}, - {{-0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}} + {{-0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}}; + +class HelloTriangleApplication +{ + public: + void run() + { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + + private: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::SurfaceFormatKHR swapChainSurfaceFormat; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + + vk::raii::Buffer vertexBuffer = nullptr; + vk::raii::DeviceMemory vertexBufferMemory = nullptr; + + vk::raii::CommandPool commandPool = nullptr; + std::vector commandBuffers; + + std::vector presentCompleteSemaphore; + std::vector renderFinishedSemaphore; + std::vector inFlightFences; + uint32_t semaphoreIndex = 0; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow *window, int width, int height) + { + auto app = static_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createGraphicsPipeline(); + createCommandPool(); + createVertexBuffer(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() + { + while (!glfwWindowShouldClose(window)) + { + glfwPollEvents(); + drawFrame(); + } + + device.waitIdle(); + } + + void cleanupSwapChain() + { + swapChainImageViews.clear(); + swapChain = nullptr; + } + + void cleanup() + { + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() + { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) + { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + device.waitIdle(); + + cleanupSwapChain(); + createSwapChain(); + createImageViews(); + } + + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain featureChain = { + {}, // vk::PhysicalDeviceFeatures2 + {.synchronization2 = true, .dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(*surface); + swapChainExtent = chooseSwapExtent(surfaceCapabilities); + swapChainSurfaceFormat = chooseSwapSurfaceFormat(physicalDevice.getSurfaceFormatsKHR(*surface)); + vk::SwapchainCreateInfoKHR swapChainCreateInfo{.surface = *surface, + .minImageCount = chooseSwapMinImageCount(surfaceCapabilities), + .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + void createImageViews() + { + vk::ImageViewCreateInfo imageViewCreateInfo{.viewType = vk::ImageViewType::e2D, .format = swapChainSurfaceFormat.format, .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + for (auto image : swapChainImages) + { + imageViewCreateInfo.image = image; + swapChainImageViews.emplace_back(device, imageViewCreateInfo); + } + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; + + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{.vertexBindingDescriptionCount = 1, .pVertexBindingDescriptions = &bindingDescription, .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), .pVertexAttributeDescriptions = attributeDescriptions.data()}; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{.topology = vk::PrimitiveTopology::eTriangleList}; + vk::PipelineViewportStateCreateInfo viewportState{.viewportCount = 1, .scissorCount = 1}; + + vk::PipelineRasterizationStateCreateInfo rasterizer{.depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = vk::False, .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f}; + + vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; + + vk::PipelineColorBlendAttachmentState colorBlendAttachment{.blendEnable = vk::False, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA}; + + vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 0, .pushConstantRangeCount = 0}; + + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format}; + vk::GraphicsPipelineCreateInfo pipelineInfo{.pNext = &pipelineRenderingCreateInfo, + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex}; + commandPool = vk::raii::CommandPool(device, poolInfo); + } + + void createVertexBuffer() + { + vk::BufferCreateInfo stagingInfo{.size = sizeof(vertices[0]) * vertices.size(), .usage = vk::BufferUsageFlagBits::eTransferSrc, .sharingMode = vk::SharingMode::eExclusive}; + vk::raii::Buffer stagingBuffer(device, stagingInfo); + vk::MemoryRequirements memRequirementsStaging = stagingBuffer.getMemoryRequirements(); + vk::MemoryAllocateInfo memoryAllocateInfoStaging{.allocationSize = memRequirementsStaging.size, .memoryTypeIndex = findMemoryType(memRequirementsStaging.memoryTypeBits, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent)}; + vk::raii::DeviceMemory stagingBufferMemory(device, memoryAllocateInfoStaging); + + stagingBuffer.bindMemory(stagingBufferMemory, 0); + void *dataStaging = stagingBufferMemory.mapMemory(0, stagingInfo.size); + memcpy(dataStaging, vertices.data(), stagingInfo.size); + stagingBufferMemory.unmapMemory(); + + vk::BufferCreateInfo bufferInfo{.size = sizeof(vertices[0]) * vertices.size(), .usage = vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eTransferDst, .sharingMode = vk::SharingMode::eExclusive}; + vertexBuffer = vk::raii::Buffer(device, bufferInfo); + + vk::MemoryRequirements memRequirements = vertexBuffer.getMemoryRequirements(); + vk::MemoryAllocateInfo memoryAllocateInfo{.allocationSize = memRequirements.size, .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, vk::MemoryPropertyFlagBits::eDeviceLocal)}; + vertexBufferMemory = vk::raii::DeviceMemory(device, memoryAllocateInfo); + + vertexBuffer.bindMemory(*vertexBufferMemory, 0); + + copyBuffer(stagingBuffer, vertexBuffer, stagingInfo.size); + } + + void copyBuffer(vk::raii::Buffer &srcBuffer, vk::raii::Buffer &dstBuffer, vk::DeviceSize size) + { + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1}; + vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); + commandCopyBuffer.begin(vk::CommandBufferBeginInfo{.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); + commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy(0, 0, size)); + commandCopyBuffer.end(); + queue.submit(vk::SubmitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer}, nullptr); + queue.waitIdle(); + } + + uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) + { + vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) + { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) + { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + void createCommandBuffers() + { + commandBuffers.clear(); + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = MAX_FRAMES_IN_FLIGHT}; + commandBuffers = vk::raii::CommandBuffers(device, allocInfo); + } + + void recordCommandBuffer(uint32_t imageIndex) + { + commandBuffers[currentFrame].begin({}); + // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL + transition_image_layout( + imageIndex, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage + ); + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::RenderingAttachmentInfo attachmentInfo = { + .imageView = swapChainImageViews[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor}; + vk::RenderingInfo renderingInfo = { + .renderArea = {.offset = {0, 0}, .extent = swapChainExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &attachmentInfo}; + commandBuffers[currentFrame].beginRendering(renderingInfo); + commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffers[currentFrame].draw(3, 1, 0, 0); + commandBuffers[currentFrame].endRendering(); + // After rendering, transition the swapchain image to PRESENT_SRC + transition_image_layout( + imageIndex, + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage + ); + commandBuffers[currentFrame].end(); + } + + void transition_image_layout( + uint32_t imageIndex, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout, + vk::AccessFlags2 src_access_mask, + vk::AccessFlags2 dst_access_mask, + vk::PipelineStageFlags2 src_stage_mask, + vk::PipelineStageFlags2 dst_stage_mask) + { + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = src_stage_mask, + .srcAccessMask = src_access_mask, + .dstStageMask = dst_stage_mask, + .dstAccessMask = dst_access_mask, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo dependency_info = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + } + + void createSyncObjects() + { + presentCompleteSemaphore.clear(); + renderFinishedSemaphore.clear(); + inFlightFences.clear(); + + for (size_t i = 0; i < swapChainImages.size(); i++) + { + presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); + } + } + + void drawFrame() + { + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + ; + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr); + + if (result == vk::Result::eErrorOutOfDateKHR) + { + recreateSwapChain(); + return; + } + if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) + { + throw std::runtime_error("failed to acquire swap chain image!"); + } + + device.resetFences(*inFlightFences[currentFrame]); + commandBuffers[currentFrame].reset(); + recordCommandBuffer(imageIndex); + + vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[currentFrame]); + + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + result = queue.presentKHR(presentInfoKHR); + if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) + { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != vk::Result::eSuccess) + { + throw std::runtime_error("failed to present swap chain image!"); + } + semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size() * sizeof(char), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const &surfaceCapabilities) + { + auto minImageCount = std::max(3u, surfaceCapabilities.minImageCount); + if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) + { + minImageCount = surfaceCapabilities.maxImageCount; + } + return minImageCount; + } + + static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + assert(!availableFormats.empty()); + const auto formatIt = std::ranges::find_if( + availableFormats, + [](const auto &format) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; }); + return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + assert(std::ranges::any_of(availablePresentModes, [](auto presentMode) { return presentMode == vk::PresentModeKHR::eFifo; })); + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + vk::PresentModeKHR::eFifo; + } + + vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR &capabilities) + { + if (capabilities.currentExtent.width != 0xFFFFFFFF) + { + 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)}; + } + + std::vector getRequiredExtensions() + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + 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: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::SurfaceFormatKHR swapChainSurfaceFormat; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - - vk::raii::Buffer vertexBuffer = nullptr; - vk::raii::DeviceMemory vertexBufferMemory = nullptr; - - vk::raii::CommandPool commandPool = nullptr; - std::vector commandBuffers; - - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; - std::vector inFlightFences; - uint32_t semaphoreIndex = 0; - uint32_t currentFrame = 0; - - bool framebufferResized = false; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); - } - - static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { - auto app = static_cast(glfwGetWindowUserPointer(window)); - app->framebufferResized = true; - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - createSurface(); - pickPhysicalDevice(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createGraphicsPipeline(); - createCommandPool(); - createVertexBuffer(); - createCommandBuffers(); - createSyncObjects(); - } - - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - drawFrame(); - } - - device.waitIdle(); - } - - void cleanupSwapChain() { - swapChainImageViews.clear(); - swapChain = nullptr; - } - - void cleanup() { - glfwDestroyWindow(window); - - glfwTerminate(); - } - - void recreateSwapChain() { - int width = 0, height = 0; - glfwGetFramebufferSize(window, &width, &height); - while (width == 0 || height == 0) { - glfwGetFramebufferSize(window, &width, &height); - glfwWaitEvents(); - } - - device.waitIdle(); - - cleanupSwapChain(); - createSwapChain(); - createImageViews(); - } - - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - } ); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error( "failed to find a suitable GPU!" ); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain featureChain = { - {}, // vk::PhysicalDeviceFeatures2 - {.synchronization2 = true, .dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( *surface ); - swapChainExtent = chooseSwapExtent( surfaceCapabilities ); - swapChainSurfaceFormat = chooseSwapSurfaceFormat( physicalDevice.getSurfaceFormatsKHR( *surface ) ); - vk::SwapchainCreateInfoKHR swapChainCreateInfo{ .surface = *surface, - .minImageCount = chooseSwapMinImageCount( surfaceCapabilities ), - .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 }; - - swapChain = vk::raii::SwapchainKHR( device, swapChainCreateInfo ); - swapChainImages = swapChain.getImages(); - } - - void createImageViews() { - vk::ImageViewCreateInfo imageViewCreateInfo{ .viewType = vk::ImageViewType::e2D, .format = swapChainSurfaceFormat.format, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } }; - for ( auto image : swapChainImages ) - { - imageViewCreateInfo.image = image; - swapChainImageViews.emplace_back( device, imageViewCreateInfo ); - } - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; - - auto bindingDescription = Vertex::getBindingDescription(); - auto attributeDescriptions = Vertex::getAttributeDescriptions(); - vk::PipelineVertexInputStateCreateInfo vertexInputInfo { .vertexBindingDescriptionCount =1, .pVertexBindingDescriptions = &bindingDescription, - .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), .pVertexAttributeDescriptions = attributeDescriptions.data() }; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ .topology = vk::PrimitiveTopology::eTriangleList }; - vk::PipelineViewportStateCreateInfo viewportState{ .viewportCount = 1, .scissorCount = 1 }; - - vk::PipelineRasterizationStateCreateInfo rasterizer{ .depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = vk::False, - .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f }; - - vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; - - vk::PipelineColorBlendAttachmentState colorBlendAttachment{ .blendEnable = vk::False, - .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA - }; - - vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 0, .pushConstantRangeCount = 0 }; - - pipelineLayout = vk::raii::PipelineLayout( device, pipelineLayoutInfo ); - - vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ .colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format }; - vk::GraphicsPipelineCreateInfo pipelineInfo{ .pNext = &pipelineRenderingCreateInfo, - .stageCount = 2, .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, .layout = pipelineLayout, .renderPass = nullptr }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{ .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex }; - commandPool = vk::raii::CommandPool(device, poolInfo); - } - - void createVertexBuffer() { - vk::BufferCreateInfo stagingInfo{ .size = sizeof(vertices[0]) * vertices.size(), .usage = vk::BufferUsageFlagBits::eTransferSrc, .sharingMode = vk::SharingMode::eExclusive }; - vk::raii::Buffer stagingBuffer(device, stagingInfo); - vk::MemoryRequirements memRequirementsStaging = stagingBuffer.getMemoryRequirements(); - vk::MemoryAllocateInfo memoryAllocateInfoStaging{ .allocationSize = memRequirementsStaging.size, .memoryTypeIndex = findMemoryType(memRequirementsStaging.memoryTypeBits, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent) }; - vk::raii::DeviceMemory stagingBufferMemory(device, memoryAllocateInfoStaging); - - stagingBuffer.bindMemory(stagingBufferMemory, 0); - void* dataStaging = stagingBufferMemory.mapMemory(0, stagingInfo.size); - memcpy(dataStaging, vertices.data(), stagingInfo.size); - stagingBufferMemory.unmapMemory(); - - vk::BufferCreateInfo bufferInfo{ .size = sizeof(vertices[0]) * vertices.size(), .usage = vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eTransferDst, .sharingMode = vk::SharingMode::eExclusive }; - vertexBuffer = vk::raii::Buffer(device, bufferInfo); - - vk::MemoryRequirements memRequirements = vertexBuffer.getMemoryRequirements(); - vk::MemoryAllocateInfo memoryAllocateInfo{ .allocationSize = memRequirements.size, .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, vk::MemoryPropertyFlagBits::eDeviceLocal) }; - vertexBufferMemory = vk::raii::DeviceMemory( device, memoryAllocateInfo ); - - vertexBuffer.bindMemory( *vertexBufferMemory, 0 ); - - copyBuffer(stagingBuffer, vertexBuffer, stagingInfo.size); - } - - void copyBuffer(vk::raii::Buffer & srcBuffer, vk::raii::Buffer & dstBuffer, vk::DeviceSize size) { - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1 }; - vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); - commandCopyBuffer.begin(vk::CommandBufferBeginInfo { .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit }); - commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy(0, 0, size)); - commandCopyBuffer.end(); - queue.submit(vk::SubmitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer }, nullptr); - queue.waitIdle(); - } - - uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) { - vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); - - for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { - if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { - return i; - } - } - - throw std::runtime_error("failed to find suitable memory type!"); - } - - void createCommandBuffers() { - commandBuffers.clear(); - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = MAX_FRAMES_IN_FLIGHT }; - commandBuffers = vk::raii::CommandBuffers( device, allocInfo ); - } - - void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin( {} ); - // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL - transition_image_layout( - imageIndex, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage - ); - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::RenderingAttachmentInfo attachmentInfo = { - .imageView = swapChainImageViews[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor - }; - vk::RenderingInfo renderingInfo = { - .renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &attachmentInfo - }; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor( 0, vk::Rect2D( vk::Offset2D( 0, 0 ), swapChainExtent ) ); - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].draw(3, 1, 0, 0); - commandBuffers[currentFrame].endRendering(); - // After rendering, transition the swapchain image to PRESENT_SRC - transition_image_layout( - imageIndex, - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage - ); - commandBuffers[currentFrame].end(); - } - - void transition_image_layout( - uint32_t imageIndex, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::AccessFlags2 src_access_mask, - vk::AccessFlags2 dst_access_mask, - vk::PipelineStageFlags2 src_stage_mask, - vk::PipelineStageFlags2 dst_stage_mask - ) { - vk::ImageMemoryBarrier2 barrier = { - .srcStageMask = src_stage_mask, - .srcAccessMask = src_access_mask, - .dstStageMask = dst_stage_mask, - .dstAccessMask = dst_access_mask, - .oldLayout = old_layout, - .newLayout = new_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); - } - - void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); - - for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - } - - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - inFlightFences.emplace_back(device, vk::FenceCreateInfo { .flags = vk::FenceCreateFlagBits::eSignaled }); - } - } - - void drawFrame() { - while ( vk::Result::eTimeout == device.waitForFences( *inFlightFences[currentFrame], vk::True, UINT64_MAX ) ) - ; - auto [result, imageIndex] = swapChain.acquireNextImage( UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr ); - - if (result == vk::Result::eErrorOutOfDateKHR) { - recreateSwapChain(); - return; - } - if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) { - throw std::runtime_error("failed to acquire swap chain image!"); - } - - device.resetFences( *inFlightFences[currentFrame] ); - commandBuffers[currentFrame].reset(); - recordCommandBuffer(imageIndex); - - vk::PipelineStageFlags waitDestinationStageMask( vk::PipelineStageFlagBits::eColorAttachmentOutput ); - const vk::SubmitInfo submitInfo{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], - .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], - .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex] }; - queue.submit(submitInfo, *inFlightFences[currentFrame]); - - - const vk::PresentInfoKHR presentInfoKHR{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], - .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex }; - result = queue.presentKHR( presentInfoKHR ); - if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { - framebufferResized = false; - recreateSwapChain(); - } else if (result != vk::Result::eSuccess) { - throw std::runtime_error("failed to present swap chain image!"); - } - semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; - } - - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size() * sizeof(char), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const & surfaceCapabilities) { - auto minImageCount = std::max( 3u, surfaceCapabilities.minImageCount ); - if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) { - minImageCount = surfaceCapabilities.maxImageCount; - } - return minImageCount; - } - - static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { - assert(!availableFormats.empty()); - const auto formatIt = std::ranges::find_if( - availableFormats, - []( const auto & format ) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; } ); - return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - assert(std::ranges::any_of(availablePresentModes, [](auto presentMode){ return presentMode == vk::PresentModeKHR::eFifo; })); - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eFifo; - } - - vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) { - if (capabilities.currentExtent.width != 0xFFFFFFFF) { - 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) - }; - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - 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; - } -}; - -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/21_index_buffer.cpp b/attachments/21_index_buffer.cpp index 7eab95c6..82e2c03c 100644 --- a/attachments/21_index_buffer.cpp +++ b/attachments/21_index_buffer.cpp @@ -11,24 +11,23 @@ #include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -constexpr int MAX_FRAMES_IN_FLIGHT = 2; +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; +constexpr int MAX_FRAMES_IN_FLIGHT = 2; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -36,672 +35,712 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -struct Vertex { - glm::vec2 pos; - glm::vec3 color; - - static vk::VertexInputBindingDescription getBindingDescription() { - return { 0, sizeof(Vertex), vk::VertexInputRate::eVertex }; - } - - static std::array getAttributeDescriptions() { - return { - vk::VertexInputAttributeDescription( 0, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, pos) ), - vk::VertexInputAttributeDescription( 1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color) ) - }; - } +struct Vertex +{ + glm::vec2 pos; + glm::vec3 color; + + static vk::VertexInputBindingDescription getBindingDescription() + { + return {0, sizeof(Vertex), vk::VertexInputRate::eVertex}; + } + + static std::array getAttributeDescriptions() + { + return { + vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, pos)), + vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color))}; + } }; const std::vector vertices = { {{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}}, {{0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}}, {{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}, - {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}} -}; + {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}}}; const std::vector indices = { - 0, 1, 2, 2, 3, 0 + 0, 1, 2, 2, 3, 0}; + +class HelloTriangleApplication +{ + public: + void run() + { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + + private: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::SurfaceFormatKHR swapChainSurfaceFormat; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + + vk::raii::Buffer vertexBuffer = nullptr; + vk::raii::DeviceMemory vertexBufferMemory = nullptr; + vk::raii::Buffer indexBuffer = nullptr; + vk::raii::DeviceMemory indexBufferMemory = nullptr; + + vk::raii::CommandPool commandPool = nullptr; + std::vector commandBuffers; + + std::vector presentCompleteSemaphore; + std::vector renderFinishedSemaphore; + std::vector inFlightFences; + uint32_t semaphoreIndex = 0; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow *window, int width, int height) + { + auto app = static_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createGraphicsPipeline(); + createCommandPool(); + createVertexBuffer(); + createIndexBuffer(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() + { + while (!glfwWindowShouldClose(window)) + { + glfwPollEvents(); + drawFrame(); + } + + device.waitIdle(); + } + + void cleanupSwapChain() + { + swapChainImageViews.clear(); + swapChain = nullptr; + } + + void cleanup() + { + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() + { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) + { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + device.waitIdle(); + + cleanupSwapChain(); + createSwapChain(); + createImageViews(); + } + + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain featureChain = { + {}, // vk::PhysicalDeviceFeatures2 + {.synchronization2 = true, .dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(*surface); + swapChainExtent = chooseSwapExtent(surfaceCapabilities); + swapChainSurfaceFormat = chooseSwapSurfaceFormat(physicalDevice.getSurfaceFormatsKHR(*surface)); + vk::SwapchainCreateInfoKHR swapChainCreateInfo{.surface = *surface, + .minImageCount = chooseSwapMinImageCount(surfaceCapabilities), + .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + void createImageViews() + { + vk::ImageViewCreateInfo imageViewCreateInfo{.viewType = vk::ImageViewType::e2D, .format = swapChainSurfaceFormat.format, .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + for (auto image : swapChainImages) + { + imageViewCreateInfo.image = image; + swapChainImageViews.emplace_back(device, imageViewCreateInfo); + } + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; + + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{.vertexBindingDescriptionCount = 1, .pVertexBindingDescriptions = &bindingDescription, .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), .pVertexAttributeDescriptions = attributeDescriptions.data()}; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{.topology = vk::PrimitiveTopology::eTriangleList}; + vk::PipelineViewportStateCreateInfo viewportState{.viewportCount = 1, .scissorCount = 1}; + + vk::PipelineRasterizationStateCreateInfo rasterizer{.depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = vk::False, .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f}; + + vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; + + vk::PipelineColorBlendAttachmentState colorBlendAttachment{.blendEnable = vk::False, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA}; + + vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 0, .pushConstantRangeCount = 0}; + + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format}; + vk::GraphicsPipelineCreateInfo pipelineInfo{.pNext = &pipelineRenderingCreateInfo, + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex}; + commandPool = vk::raii::CommandPool(device, poolInfo); + } + + void createVertexBuffer() + { + vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(dataStaging, vertices.data(), bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + } + + void createIndexBuffer() + { + vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(data, indices.data(), (size_t) bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); + + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + } + + void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer &buffer, vk::raii::DeviceMemory &bufferMemory) + { + vk::BufferCreateInfo bufferInfo{.size = size, .usage = usage, .sharingMode = vk::SharingMode::eExclusive}; + buffer = vk::raii::Buffer(device, bufferInfo); + vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{.allocationSize = memRequirements.size, .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + bufferMemory = vk::raii::DeviceMemory(device, allocInfo); + buffer.bindMemory(bufferMemory, 0); + } + + void copyBuffer(vk::raii::Buffer &srcBuffer, vk::raii::Buffer &dstBuffer, vk::DeviceSize size) + { + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1}; + vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); + commandCopyBuffer.begin(vk::CommandBufferBeginInfo{.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); + commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy(0, 0, size)); + commandCopyBuffer.end(); + queue.submit(vk::SubmitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer}, nullptr); + queue.waitIdle(); + } + + uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) + { + vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) + { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) + { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + void createCommandBuffers() + { + commandBuffers.clear(); + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = MAX_FRAMES_IN_FLIGHT}; + commandBuffers = vk::raii::CommandBuffers(device, allocInfo); + } + + void recordCommandBuffer(uint32_t imageIndex) + { + commandBuffers[currentFrame].begin({}); + // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL + transition_image_layout( + imageIndex, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage + ); + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::RenderingAttachmentInfo attachmentInfo = { + .imageView = swapChainImageViews[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor}; + vk::RenderingInfo renderingInfo = { + .renderArea = {.offset = {0, 0}, .extent = swapChainExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &attachmentInfo}; + commandBuffers[currentFrame].beginRendering(renderingInfo); + commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexTypeValue::value); + commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); + commandBuffers[currentFrame].endRendering(); + // After rendering, transition the swapchain image to PRESENT_SRC + transition_image_layout( + imageIndex, + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage + ); + commandBuffers[currentFrame].end(); + } + + void transition_image_layout( + uint32_t imageIndex, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout, + vk::AccessFlags2 src_access_mask, + vk::AccessFlags2 dst_access_mask, + vk::PipelineStageFlags2 src_stage_mask, + vk::PipelineStageFlags2 dst_stage_mask) + { + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = src_stage_mask, + .srcAccessMask = src_access_mask, + .dstStageMask = dst_stage_mask, + .dstAccessMask = dst_access_mask, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo dependency_info = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + } + + void createSyncObjects() + { + presentCompleteSemaphore.clear(); + renderFinishedSemaphore.clear(); + inFlightFences.clear(); + + for (size_t i = 0; i < swapChainImages.size(); i++) + { + presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); + } + } + + void drawFrame() + { + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + ; + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr); + + if (result == vk::Result::eErrorOutOfDateKHR) + { + recreateSwapChain(); + return; + } + if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) + { + throw std::runtime_error("failed to acquire swap chain image!"); + } + + device.resetFences(*inFlightFences[currentFrame]); + commandBuffers[currentFrame].reset(); + recordCommandBuffer(imageIndex); + + vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[currentFrame]); + + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + result = queue.presentKHR(presentInfoKHR); + if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) + { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != vk::Result::eSuccess) + { + throw std::runtime_error("failed to present swap chain image!"); + } + semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size(), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const &surfaceCapabilities) + { + auto minImageCount = std::max(3u, surfaceCapabilities.minImageCount); + if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) + { + minImageCount = surfaceCapabilities.maxImageCount; + } + return minImageCount; + } + + static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + assert(!availableFormats.empty()); + const auto formatIt = std::ranges::find_if( + availableFormats, + [](const auto &format) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; }); + return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + assert(std::ranges::any_of(availablePresentModes, [](auto presentMode) { return presentMode == vk::PresentModeKHR::eFifo; })); + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + vk::PresentModeKHR::eFifo; + } + + vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR &capabilities) + { + if (capabilities.currentExtent.width != 0xFFFFFFFF) + { + 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)}; + } + + std::vector getRequiredExtensions() + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + 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: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::SurfaceFormatKHR swapChainSurfaceFormat; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - - vk::raii::Buffer vertexBuffer = nullptr; - vk::raii::DeviceMemory vertexBufferMemory = nullptr; - vk::raii::Buffer indexBuffer = nullptr; - vk::raii::DeviceMemory indexBufferMemory = nullptr; - - vk::raii::CommandPool commandPool = nullptr; - std::vector commandBuffers; - - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; - std::vector inFlightFences; - uint32_t semaphoreIndex = 0; - uint32_t currentFrame = 0; - - bool framebufferResized = false; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); - } - - static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { - auto app = static_cast(glfwGetWindowUserPointer(window)); - app->framebufferResized = true; - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - createSurface(); - pickPhysicalDevice(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createGraphicsPipeline(); - createCommandPool(); - createVertexBuffer(); - createIndexBuffer(); - createCommandBuffers(); - createSyncObjects(); - } - - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - drawFrame(); - } - - device.waitIdle(); - } - - void cleanupSwapChain() { - swapChainImageViews.clear(); - swapChain = nullptr; - } - - void cleanup() { - glfwDestroyWindow(window); - - glfwTerminate(); - } - - void recreateSwapChain() { - int width = 0, height = 0; - glfwGetFramebufferSize(window, &width, &height); - while (width == 0 || height == 0) { - glfwGetFramebufferSize(window, &width, &height); - glfwWaitEvents(); - } - - device.waitIdle(); - - cleanupSwapChain(); - createSwapChain(); - createImageViews(); - } - - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - } ); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error( "failed to find a suitable GPU!" ); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain featureChain = { - {}, // vk::PhysicalDeviceFeatures2 - {.synchronization2 = true, .dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( *surface ); - swapChainExtent = chooseSwapExtent( surfaceCapabilities ); - swapChainSurfaceFormat = chooseSwapSurfaceFormat( physicalDevice.getSurfaceFormatsKHR( *surface ) ); - vk::SwapchainCreateInfoKHR swapChainCreateInfo{ .surface = *surface, - .minImageCount = chooseSwapMinImageCount( surfaceCapabilities ), - .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 }; - - swapChain = vk::raii::SwapchainKHR( device, swapChainCreateInfo ); - swapChainImages = swapChain.getImages(); - } - - void createImageViews() { - vk::ImageViewCreateInfo imageViewCreateInfo{ .viewType = vk::ImageViewType::e2D, .format = swapChainSurfaceFormat.format, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } }; - for ( auto image : swapChainImages ) - { - imageViewCreateInfo.image = image; - swapChainImageViews.emplace_back( device, imageViewCreateInfo ); - } - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; - - auto bindingDescription = Vertex::getBindingDescription(); - auto attributeDescriptions = Vertex::getAttributeDescriptions(); - vk::PipelineVertexInputStateCreateInfo vertexInputInfo { .vertexBindingDescriptionCount =1, .pVertexBindingDescriptions = &bindingDescription, - .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), .pVertexAttributeDescriptions = attributeDescriptions.data() }; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ .topology = vk::PrimitiveTopology::eTriangleList }; - vk::PipelineViewportStateCreateInfo viewportState{ .viewportCount = 1, .scissorCount = 1 }; - - vk::PipelineRasterizationStateCreateInfo rasterizer{ .depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = vk::False, - .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f }; - - vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; - - vk::PipelineColorBlendAttachmentState colorBlendAttachment{ .blendEnable = vk::False, - .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA - }; - - vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 0, .pushConstantRangeCount = 0 }; - - pipelineLayout = vk::raii::PipelineLayout( device, pipelineLayoutInfo ); - - vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ .colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format }; - vk::GraphicsPipelineCreateInfo pipelineInfo{ .pNext = &pipelineRenderingCreateInfo, - .stageCount = 2, .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, .layout = pipelineLayout, .renderPass = nullptr }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{ .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex }; - commandPool = vk::raii::CommandPool(device, poolInfo); - } - - void createVertexBuffer() { - vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(dataStaging, vertices.data(), bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); - - copyBuffer(stagingBuffer, vertexBuffer, bufferSize); - } - - void createIndexBuffer() { - vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); - - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(data, indices.data(), (size_t) bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); - - copyBuffer(stagingBuffer, indexBuffer, bufferSize); - } - - void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer& buffer, vk::raii::DeviceMemory& bufferMemory) { - vk::BufferCreateInfo bufferInfo{ .size = size, .usage = usage, .sharingMode = vk::SharingMode::eExclusive }; - buffer = vk::raii::Buffer(device, bufferInfo); - vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ .allocationSize =memRequirements.size, .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) }; - bufferMemory = vk::raii::DeviceMemory(device, allocInfo); - buffer.bindMemory(bufferMemory, 0); - } - - void copyBuffer(vk::raii::Buffer & srcBuffer, vk::raii::Buffer & dstBuffer, vk::DeviceSize size) { - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1 }; - vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); - commandCopyBuffer.begin(vk::CommandBufferBeginInfo { .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit }); - commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy(0, 0, size)); - commandCopyBuffer.end(); - queue.submit(vk::SubmitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer }, nullptr); - queue.waitIdle(); - } - - uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) { - vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); - - for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { - if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { - return i; - } - } - - throw std::runtime_error("failed to find suitable memory type!"); - } - - void createCommandBuffers() { - commandBuffers.clear(); - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = MAX_FRAMES_IN_FLIGHT }; - commandBuffers = vk::raii::CommandBuffers( device, allocInfo ); - } - - void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin( {} ); - // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL - transition_image_layout( - imageIndex, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage - ); - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::RenderingAttachmentInfo attachmentInfo = { - .imageView = swapChainImageViews[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor - }; - vk::RenderingInfo renderingInfo = { - .renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &attachmentInfo - }; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor( 0, vk::Rect2D( vk::Offset2D( 0, 0 ), swapChainExtent ) ); - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].bindIndexBuffer( *indexBuffer, 0, vk::IndexTypeValue::value ); - commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); - commandBuffers[currentFrame].endRendering(); - // After rendering, transition the swapchain image to PRESENT_SRC - transition_image_layout( - imageIndex, - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage - ); - commandBuffers[currentFrame].end(); - } - - void transition_image_layout( - uint32_t imageIndex, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::AccessFlags2 src_access_mask, - vk::AccessFlags2 dst_access_mask, - vk::PipelineStageFlags2 src_stage_mask, - vk::PipelineStageFlags2 dst_stage_mask - ) { - vk::ImageMemoryBarrier2 barrier = { - .srcStageMask = src_stage_mask, - .srcAccessMask = src_access_mask, - .dstStageMask = dst_stage_mask, - .dstAccessMask = dst_access_mask, - .oldLayout = old_layout, - .newLayout = new_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); - } - - void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); - - for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - } - - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - inFlightFences.emplace_back(device, vk::FenceCreateInfo { .flags = vk::FenceCreateFlagBits::eSignaled }); - } - } - - void drawFrame() { - while ( vk::Result::eTimeout == device.waitForFences( *inFlightFences[currentFrame], vk::True, UINT64_MAX ) ) - ; - auto [result, imageIndex] = swapChain.acquireNextImage( UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr ); - - if (result == vk::Result::eErrorOutOfDateKHR) { - recreateSwapChain(); - return; - } - if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) { - throw std::runtime_error("failed to acquire swap chain image!"); - } - - device.resetFences( *inFlightFences[currentFrame] ); - commandBuffers[currentFrame].reset(); - recordCommandBuffer(imageIndex); - - vk::PipelineStageFlags waitDestinationStageMask( vk::PipelineStageFlagBits::eColorAttachmentOutput ); - const vk::SubmitInfo submitInfo{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], - .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], - .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex] }; - queue.submit(submitInfo, *inFlightFences[currentFrame]); - - - const vk::PresentInfoKHR presentInfoKHR{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], - .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex }; - result = queue.presentKHR( presentInfoKHR ); - if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { - framebufferResized = false; - recreateSwapChain(); - } else if (result != vk::Result::eSuccess) { - throw std::runtime_error("failed to present swap chain image!"); - } - semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; - } - - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size(), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const & surfaceCapabilities) { - auto minImageCount = std::max( 3u, surfaceCapabilities.minImageCount ); - if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) { - minImageCount = surfaceCapabilities.maxImageCount; - } - return minImageCount; - } - - static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { - assert(!availableFormats.empty()); - const auto formatIt = std::ranges::find_if( - availableFormats, - []( const auto & format ) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; } ); - return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - assert(std::ranges::any_of(availablePresentModes, [](auto presentMode){ return presentMode == vk::PresentModeKHR::eFifo; })); - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eFifo; - } - - vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) { - if (capabilities.currentExtent.width != 0xFFFFFFFF) { - 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) - }; - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - 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; - } -}; - -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/22_descriptor_layout.cpp b/attachments/22_descriptor_layout.cpp index 4eb2bdd5..ffbdb01b 100644 --- a/attachments/22_descriptor_layout.cpp +++ b/attachments/22_descriptor_layout.cpp @@ -12,27 +12,26 @@ #include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include #define GLM_FORCE_RADIANS #include #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -constexpr int MAX_FRAMES_IN_FLIGHT = 2; +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; +constexpr int MAX_FRAMES_IN_FLIGHT = 2; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -40,723 +39,768 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -struct Vertex { - glm::vec2 pos; - glm::vec3 color; - - static vk::VertexInputBindingDescription getBindingDescription() { - return { 0, sizeof(Vertex), vk::VertexInputRate::eVertex }; - } - - static std::array getAttributeDescriptions() { - return { - vk::VertexInputAttributeDescription( 0, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, pos) ), - vk::VertexInputAttributeDescription( 1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color) ) - }; - } +struct Vertex +{ + glm::vec2 pos; + glm::vec3 color; + + static vk::VertexInputBindingDescription getBindingDescription() + { + return {0, sizeof(Vertex), vk::VertexInputRate::eVertex}; + } + + static std::array getAttributeDescriptions() + { + return { + vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, pos)), + vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color))}; + } }; -struct UniformBufferObject { - glm::mat4 model; - glm::mat4 view; - glm::mat4 proj; +struct UniformBufferObject +{ + glm::mat4 model; + glm::mat4 view; + glm::mat4 proj; }; const std::vector vertices = { {{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}}, {{0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}}, {{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}, - {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}} -}; + {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}}}; const std::vector indices = { - 0, 1, 2, 2, 3, 0 + 0, 1, 2, 2, 3, 0}; + +class HelloTriangleApplication +{ + public: + void run() + { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + + private: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::SurfaceFormatKHR swapChainSurfaceFormat; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + + vk::raii::Buffer vertexBuffer = nullptr; + vk::raii::DeviceMemory vertexBufferMemory = nullptr; + vk::raii::Buffer indexBuffer = nullptr; + vk::raii::DeviceMemory indexBufferMemory = nullptr; + + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + std::vector uniformBuffersMapped; + + vk::raii::CommandPool commandPool = nullptr; + std::vector commandBuffers; + + std::vector presentCompleteSemaphore; + std::vector renderFinishedSemaphore; + std::vector inFlightFences; + uint32_t semaphoreIndex = 0; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow *window, int width, int height) + { + auto app = static_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createDescriptorSetLayout(); + createGraphicsPipeline(); + createCommandPool(); + createVertexBuffer(); + createIndexBuffer(); + createUniformBuffers(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() + { + while (!glfwWindowShouldClose(window)) + { + glfwPollEvents(); + drawFrame(); + } + + device.waitIdle(); + } + + void cleanupSwapChain() + { + swapChainImageViews.clear(); + swapChain = nullptr; + } + + void cleanup() + { + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() + { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) + { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + device.waitIdle(); + + cleanupSwapChain(); + createSwapChain(); + createImageViews(); + } + + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain featureChain = { + {}, // vk::PhysicalDeviceFeatures2 + {.synchronization2 = true, .dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(*surface); + swapChainExtent = chooseSwapExtent(surfaceCapabilities); + swapChainSurfaceFormat = chooseSwapSurfaceFormat(physicalDevice.getSurfaceFormatsKHR(*surface)); + vk::SwapchainCreateInfoKHR swapChainCreateInfo{.surface = *surface, + .minImageCount = chooseSwapMinImageCount(surfaceCapabilities), + .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + void createImageViews() + { + vk::ImageViewCreateInfo imageViewCreateInfo{.viewType = vk::ImageViewType::e2D, .format = swapChainSurfaceFormat.format, .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + for (auto image : swapChainImages) + { + imageViewCreateInfo.image = image; + swapChainImageViews.emplace_back(device, imageViewCreateInfo); + } + } + + void createDescriptorSetLayout() + { + vk::DescriptorSetLayoutBinding uboLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr); + vk::DescriptorSetLayoutCreateInfo layoutInfo{.bindingCount = 1, .pBindings = &uboLayoutBinding}; + descriptorSetLayout = vk::raii::DescriptorSetLayout(device, layoutInfo); + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; + + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{.vertexBindingDescriptionCount = 1, .pVertexBindingDescriptions = &bindingDescription, .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), .pVertexAttributeDescriptions = attributeDescriptions.data()}; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{.topology = vk::PrimitiveTopology::eTriangleList}; + vk::PipelineViewportStateCreateInfo viewportState{.viewportCount = 1, .scissorCount = 1}; + + vk::PipelineRasterizationStateCreateInfo rasterizer{.depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, .frontFace = vk::FrontFace::eCounterClockwise, .depthBiasEnable = vk::False, .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f}; + + vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; + + vk::PipelineColorBlendAttachmentState colorBlendAttachment{.blendEnable = vk::False, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA}; + + vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0}; + + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format}; + vk::GraphicsPipelineCreateInfo pipelineInfo{.pNext = &pipelineRenderingCreateInfo, + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex}; + commandPool = vk::raii::CommandPool(device, poolInfo); + } + + void createVertexBuffer() + { + vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(dataStaging, vertices.data(), bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + } + + void createIndexBuffer() + { + vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(data, indices.data(), (size_t) bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); + + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + } + + void createUniformBuffers() + { + uniformBuffers.clear(); + uniformBuffersMemory.clear(); + uniformBuffersMapped.clear(); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DeviceSize bufferSize = sizeof(UniformBufferObject); + vk::raii::Buffer buffer({}); + vk::raii::DeviceMemory bufferMem({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); + uniformBuffers.emplace_back(std::move(buffer)); + uniformBuffersMemory.emplace_back(std::move(bufferMem)); + uniformBuffersMapped.emplace_back(uniformBuffersMemory[i].mapMemory(0, bufferSize)); + } + } + + void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer &buffer, vk::raii::DeviceMemory &bufferMemory) + { + vk::BufferCreateInfo bufferInfo{.size = size, .usage = usage, .sharingMode = vk::SharingMode::eExclusive}; + buffer = vk::raii::Buffer(device, bufferInfo); + vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{.allocationSize = memRequirements.size, .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + bufferMemory = vk::raii::DeviceMemory(device, allocInfo); + buffer.bindMemory(bufferMemory, 0); + } + + void copyBuffer(vk::raii::Buffer &srcBuffer, vk::raii::Buffer &dstBuffer, vk::DeviceSize size) + { + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1}; + vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); + commandCopyBuffer.begin(vk::CommandBufferBeginInfo{.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); + commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy(0, 0, size)); + commandCopyBuffer.end(); + queue.submit(vk::SubmitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer}, nullptr); + queue.waitIdle(); + } + + uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) + { + vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) + { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) + { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + void createCommandBuffers() + { + commandBuffers.clear(); + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = MAX_FRAMES_IN_FLIGHT}; + commandBuffers = vk::raii::CommandBuffers(device, allocInfo); + } + + void recordCommandBuffer(uint32_t imageIndex) + { + commandBuffers[currentFrame].begin({}); + // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL + transition_image_layout( + imageIndex, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage + ); + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::RenderingAttachmentInfo attachmentInfo = { + .imageView = swapChainImageViews[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor}; + vk::RenderingInfo renderingInfo = { + .renderArea = {.offset = {0, 0}, .extent = swapChainExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &attachmentInfo}; + commandBuffers[currentFrame].beginRendering(renderingInfo); + commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexTypeValue::value); + commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); + commandBuffers[currentFrame].endRendering(); + // After rendering, transition the swapchain image to PRESENT_SRC + transition_image_layout( + imageIndex, + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage + ); + commandBuffers[currentFrame].end(); + } + + void transition_image_layout( + uint32_t imageIndex, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout, + vk::AccessFlags2 src_access_mask, + vk::AccessFlags2 dst_access_mask, + vk::PipelineStageFlags2 src_stage_mask, + vk::PipelineStageFlags2 dst_stage_mask) + { + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = src_stage_mask, + .srcAccessMask = src_access_mask, + .dstStageMask = dst_stage_mask, + .dstAccessMask = dst_access_mask, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo dependency_info = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + } + + void createSyncObjects() + { + presentCompleteSemaphore.clear(); + renderFinishedSemaphore.clear(); + inFlightFences.clear(); + + for (size_t i = 0; i < swapChainImages.size(); i++) + { + presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); + } + } + + void updateUniformBuffer(uint32_t currentImage) + { + static auto startTime = std::chrono::high_resolution_clock::now(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + UniformBufferObject ubo{}; + ubo.model = rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.view = lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); + ubo.proj[1][1] *= -1; + + memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); + } + + void drawFrame() + { + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + ; + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr); + + if (result == vk::Result::eErrorOutOfDateKHR) + { + recreateSwapChain(); + return; + } + if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) + { + throw std::runtime_error("failed to acquire swap chain image!"); + } + updateUniformBuffer(currentFrame); + + device.resetFences(*inFlightFences[currentFrame]); + commandBuffers[currentFrame].reset(); + recordCommandBuffer(imageIndex); + + vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[currentFrame]); + + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + result = queue.presentKHR(presentInfoKHR); + if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) + { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != vk::Result::eSuccess) + { + throw std::runtime_error("failed to present swap chain image!"); + } + semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size(), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const &surfaceCapabilities) + { + auto minImageCount = std::max(3u, surfaceCapabilities.minImageCount); + if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) + { + minImageCount = surfaceCapabilities.maxImageCount; + } + return minImageCount; + } + + static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + assert(!availableFormats.empty()); + const auto formatIt = std::ranges::find_if( + availableFormats, + [](const auto &format) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; }); + return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + assert(std::ranges::any_of(availablePresentModes, [](auto presentMode) { return presentMode == vk::PresentModeKHR::eFifo; })); + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + vk::PresentModeKHR::eFifo; + } + + vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR &capabilities) + { + if (capabilities.currentExtent.width != 0xFFFFFFFF) + { + 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)}; + } + + std::vector getRequiredExtensions() + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + 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: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::SurfaceFormatKHR swapChainSurfaceFormat; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - - vk::raii::Buffer vertexBuffer = nullptr; - vk::raii::DeviceMemory vertexBufferMemory = nullptr; - vk::raii::Buffer indexBuffer = nullptr; - vk::raii::DeviceMemory indexBufferMemory = nullptr; - - std::vector uniformBuffers; - std::vector uniformBuffersMemory; - std::vector uniformBuffersMapped; - - vk::raii::CommandPool commandPool = nullptr; - std::vector commandBuffers; - - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; - std::vector inFlightFences; - uint32_t semaphoreIndex = 0; - uint32_t currentFrame = 0; - - bool framebufferResized = false; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); - } - - static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { - auto app = static_cast(glfwGetWindowUserPointer(window)); - app->framebufferResized = true; - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - createSurface(); - pickPhysicalDevice(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createDescriptorSetLayout(); - createGraphicsPipeline(); - createCommandPool(); - createVertexBuffer(); - createIndexBuffer(); - createUniformBuffers(); - createCommandBuffers(); - createSyncObjects(); - } - - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - drawFrame(); - } - - device.waitIdle(); - } - - void cleanupSwapChain() { - swapChainImageViews.clear(); - swapChain = nullptr; - } - - void cleanup() { - glfwDestroyWindow(window); - - glfwTerminate(); - } - - void recreateSwapChain() { - int width = 0, height = 0; - glfwGetFramebufferSize(window, &width, &height); - while (width == 0 || height == 0) { - glfwGetFramebufferSize(window, &width, &height); - glfwWaitEvents(); - } - - device.waitIdle(); - - cleanupSwapChain(); - createSwapChain(); - createImageViews(); - } - - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - } ); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error( "failed to find a suitable GPU!" ); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain featureChain = { - {}, // vk::PhysicalDeviceFeatures2 - {.synchronization2 = true, .dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( *surface ); - swapChainExtent = chooseSwapExtent( surfaceCapabilities ); - swapChainSurfaceFormat = chooseSwapSurfaceFormat( physicalDevice.getSurfaceFormatsKHR( *surface ) ); - vk::SwapchainCreateInfoKHR swapChainCreateInfo{ .surface = *surface, - .minImageCount = chooseSwapMinImageCount( surfaceCapabilities ), - .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 }; - - swapChain = vk::raii::SwapchainKHR( device, swapChainCreateInfo ); - swapChainImages = swapChain.getImages(); - } - - void createImageViews() { - vk::ImageViewCreateInfo imageViewCreateInfo{ .viewType = vk::ImageViewType::e2D, .format = swapChainSurfaceFormat.format, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } }; - for ( auto image : swapChainImages ) - { - imageViewCreateInfo.image = image; - swapChainImageViews.emplace_back( device, imageViewCreateInfo ); - } - } - - void createDescriptorSetLayout() { - vk::DescriptorSetLayoutBinding uboLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr); - vk::DescriptorSetLayoutCreateInfo layoutInfo{ .bindingCount = 1, .pBindings = &uboLayoutBinding }; - descriptorSetLayout = vk::raii::DescriptorSetLayout( device, layoutInfo ); - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; - - auto bindingDescription = Vertex::getBindingDescription(); - auto attributeDescriptions = Vertex::getAttributeDescriptions(); - vk::PipelineVertexInputStateCreateInfo vertexInputInfo { .vertexBindingDescriptionCount =1, .pVertexBindingDescriptions = &bindingDescription, - .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), .pVertexAttributeDescriptions = attributeDescriptions.data() }; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ .topology = vk::PrimitiveTopology::eTriangleList }; - vk::PipelineViewportStateCreateInfo viewportState{ .viewportCount = 1, .scissorCount = 1 }; - - vk::PipelineRasterizationStateCreateInfo rasterizer{ .depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eCounterClockwise, .depthBiasEnable = vk::False, - .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f }; - - vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; - - vk::PipelineColorBlendAttachmentState colorBlendAttachment{ .blendEnable = vk::False, - .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA - }; - - vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0 }; - - pipelineLayout = vk::raii::PipelineLayout( device, pipelineLayoutInfo ); - - vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ .colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format }; - vk::GraphicsPipelineCreateInfo pipelineInfo{ .pNext = &pipelineRenderingCreateInfo, - .stageCount = 2, .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, .layout = pipelineLayout, .renderPass = nullptr }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{ .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex }; - commandPool = vk::raii::CommandPool(device, poolInfo); - } - - void createVertexBuffer() { - vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(dataStaging, vertices.data(), bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); - - copyBuffer(stagingBuffer, vertexBuffer, bufferSize); - } - - void createIndexBuffer() { - vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); - - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(data, indices.data(), (size_t) bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); - - copyBuffer(stagingBuffer, indexBuffer, bufferSize); - } - - void createUniformBuffers() { - uniformBuffers.clear(); - uniformBuffersMemory.clear(); - uniformBuffersMapped.clear(); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DeviceSize bufferSize = sizeof(UniformBufferObject); - vk::raii::Buffer buffer({}); - vk::raii::DeviceMemory bufferMem({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); - uniformBuffers.emplace_back(std::move(buffer)); - uniformBuffersMemory.emplace_back(std::move(bufferMem)); - uniformBuffersMapped.emplace_back( uniformBuffersMemory[i].mapMemory(0, bufferSize)); - } - } - - void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer& buffer, vk::raii::DeviceMemory& bufferMemory) { - vk::BufferCreateInfo bufferInfo{ .size = size, .usage = usage, .sharingMode = vk::SharingMode::eExclusive }; - buffer = vk::raii::Buffer(device, bufferInfo); - vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ .allocationSize =memRequirements.size, .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) }; - bufferMemory = vk::raii::DeviceMemory(device, allocInfo); - buffer.bindMemory(bufferMemory, 0); - } - - void copyBuffer(vk::raii::Buffer & srcBuffer, vk::raii::Buffer & dstBuffer, vk::DeviceSize size) { - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1 }; - vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); - commandCopyBuffer.begin(vk::CommandBufferBeginInfo { .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit }); - commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy(0, 0, size)); - commandCopyBuffer.end(); - queue.submit(vk::SubmitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer }, nullptr); - queue.waitIdle(); - } - - uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) { - vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); - - for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { - if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { - return i; - } - } - - throw std::runtime_error("failed to find suitable memory type!"); - } - - void createCommandBuffers() { - commandBuffers.clear(); - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = MAX_FRAMES_IN_FLIGHT }; - commandBuffers = vk::raii::CommandBuffers( device, allocInfo ); - } - - void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin( {} ); - // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL - transition_image_layout( - imageIndex, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage - ); - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::RenderingAttachmentInfo attachmentInfo = { - .imageView = swapChainImageViews[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor - }; - vk::RenderingInfo renderingInfo = { - .renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &attachmentInfo - }; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor( 0, vk::Rect2D( vk::Offset2D( 0, 0 ), swapChainExtent ) ); - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].bindIndexBuffer( *indexBuffer, 0, vk::IndexTypeValue::value ); - commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); - commandBuffers[currentFrame].endRendering(); - // After rendering, transition the swapchain image to PRESENT_SRC - transition_image_layout( - imageIndex, - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage - ); - commandBuffers[currentFrame].end(); - } - - void transition_image_layout( - uint32_t imageIndex, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::AccessFlags2 src_access_mask, - vk::AccessFlags2 dst_access_mask, - vk::PipelineStageFlags2 src_stage_mask, - vk::PipelineStageFlags2 dst_stage_mask - ) { - vk::ImageMemoryBarrier2 barrier = { - .srcStageMask = src_stage_mask, - .srcAccessMask = src_access_mask, - .dstStageMask = dst_stage_mask, - .dstAccessMask = dst_access_mask, - .oldLayout = old_layout, - .newLayout = new_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); - } - - void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); - - for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - } - - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - inFlightFences.emplace_back(device, vk::FenceCreateInfo { .flags = vk::FenceCreateFlagBits::eSignaled }); - } - } - - void updateUniformBuffer(uint32_t currentImage) { - static auto startTime = std::chrono::high_resolution_clock::now(); - - auto currentTime = std::chrono::high_resolution_clock::now(); - float time = std::chrono::duration(currentTime - startTime).count(); - - UniformBufferObject ubo{}; - ubo.model = rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.view = lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); - ubo.proj[1][1] *= -1; - - memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); - } - - void drawFrame() { - while ( vk::Result::eTimeout == device.waitForFences( *inFlightFences[currentFrame], vk::True, UINT64_MAX ) ) - ; - auto [result, imageIndex] = swapChain.acquireNextImage( UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr ); - - if (result == vk::Result::eErrorOutOfDateKHR) { - recreateSwapChain(); - return; - } - if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) { - throw std::runtime_error("failed to acquire swap chain image!"); - } - updateUniformBuffer(currentFrame); - - device.resetFences( *inFlightFences[currentFrame] ); - commandBuffers[currentFrame].reset(); - recordCommandBuffer(imageIndex); - - vk::PipelineStageFlags waitDestinationStageMask( vk::PipelineStageFlagBits::eColorAttachmentOutput ); - const vk::SubmitInfo submitInfo{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], - .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], - .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex] }; - queue.submit(submitInfo, *inFlightFences[currentFrame]); - - - const vk::PresentInfoKHR presentInfoKHR{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], - .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex }; - result = queue.presentKHR( presentInfoKHR ); - if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { - framebufferResized = false; - recreateSwapChain(); - } else if (result != vk::Result::eSuccess) { - throw std::runtime_error("failed to present swap chain image!"); - } - semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; - } - - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size(), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const & surfaceCapabilities) { - auto minImageCount = std::max( 3u, surfaceCapabilities.minImageCount ); - if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) { - minImageCount = surfaceCapabilities.maxImageCount; - } - return minImageCount; - } - - static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { - assert(!availableFormats.empty()); - const auto formatIt = std::ranges::find_if( - availableFormats, - []( const auto & format ) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; } ); - return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - assert(std::ranges::any_of(availablePresentModes, [](auto presentMode){ return presentMode == vk::PresentModeKHR::eFifo; })); - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eFifo; - } - - vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) { - if (capabilities.currentExtent.width != 0xFFFFFFFF) { - 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) - }; - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - 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; - } -}; - -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/23_descriptor_sets.cpp b/attachments/23_descriptor_sets.cpp index 03add3f2..bc884971 100644 --- a/attachments/23_descriptor_sets.cpp +++ b/attachments/23_descriptor_sets.cpp @@ -12,27 +12,26 @@ #include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include #define GLM_FORCE_RADIANS #include #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -constexpr int MAX_FRAMES_IN_FLIGHT = 2; +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; +constexpr int MAX_FRAMES_IN_FLIGHT = 2; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -40,748 +39,796 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -struct Vertex { - glm::vec2 pos; - glm::vec3 color; - - static vk::VertexInputBindingDescription getBindingDescription() { - return { 0, sizeof(Vertex), vk::VertexInputRate::eVertex }; - } - - static std::array getAttributeDescriptions() { - return { - vk::VertexInputAttributeDescription( 0, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, pos) ), - vk::VertexInputAttributeDescription( 1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color) ) - }; - } +struct Vertex +{ + glm::vec2 pos; + glm::vec3 color; + + static vk::VertexInputBindingDescription getBindingDescription() + { + return {0, sizeof(Vertex), vk::VertexInputRate::eVertex}; + } + + static std::array getAttributeDescriptions() + { + return { + vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, pos)), + vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color))}; + } }; -struct UniformBufferObject { - alignas(16) glm::mat4 model; - alignas(16) glm::mat4 view; - alignas(16) glm::mat4 proj; +struct UniformBufferObject +{ + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; }; const std::vector vertices = { {{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}}, {{0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}}, {{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}, - {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}} -}; + {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}}}; const std::vector indices = { - 0, 1, 2, 2, 3, 0 + 0, 1, 2, 2, 3, 0}; + +class HelloTriangleApplication +{ + public: + void run() + { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + + private: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::SurfaceFormatKHR swapChainSurfaceFormat; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + + vk::raii::Buffer vertexBuffer = nullptr; + vk::raii::DeviceMemory vertexBufferMemory = nullptr; + vk::raii::Buffer indexBuffer = nullptr; + vk::raii::DeviceMemory indexBufferMemory = nullptr; + + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + std::vector uniformBuffersMapped; + + vk::raii::DescriptorPool descriptorPool = nullptr; + std::vector descriptorSets; + + vk::raii::CommandPool commandPool = nullptr; + std::vector commandBuffers; + + std::vector presentCompleteSemaphore; + std::vector renderFinishedSemaphore; + std::vector inFlightFences; + uint32_t semaphoreIndex = 0; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow *window, int width, int height) + { + auto app = static_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createDescriptorSetLayout(); + createGraphicsPipeline(); + createCommandPool(); + createVertexBuffer(); + createIndexBuffer(); + createUniformBuffers(); + createDescriptorPool(); + createDescriptorSets(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() + { + while (!glfwWindowShouldClose(window)) + { + glfwPollEvents(); + drawFrame(); + } + + device.waitIdle(); + } + + void cleanupSwapChain() + { + swapChainImageViews.clear(); + swapChain = nullptr; + } + + void cleanup() + { + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() + { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) + { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + device.waitIdle(); + + cleanupSwapChain(); + createSwapChain(); + createImageViews(); + } + + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain featureChain = { + {}, // vk::PhysicalDeviceFeatures2 + {.synchronization2 = true, .dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(*surface); + swapChainExtent = chooseSwapExtent(surfaceCapabilities); + swapChainSurfaceFormat = chooseSwapSurfaceFormat(physicalDevice.getSurfaceFormatsKHR(*surface)); + vk::SwapchainCreateInfoKHR swapChainCreateInfo{.surface = *surface, + .minImageCount = chooseSwapMinImageCount(surfaceCapabilities), + .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + void createImageViews() + { + vk::ImageViewCreateInfo imageViewCreateInfo{.viewType = vk::ImageViewType::e2D, .format = swapChainSurfaceFormat.format, .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + for (auto image : swapChainImages) + { + imageViewCreateInfo.image = image; + swapChainImageViews.emplace_back(device, imageViewCreateInfo); + } + } + + void createDescriptorSetLayout() + { + vk::DescriptorSetLayoutBinding uboLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr); + vk::DescriptorSetLayoutCreateInfo layoutInfo{.bindingCount = 1, .pBindings = &uboLayoutBinding}; + descriptorSetLayout = vk::raii::DescriptorSetLayout(device, layoutInfo); + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; + + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{.vertexBindingDescriptionCount = 1, .pVertexBindingDescriptions = &bindingDescription, .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), .pVertexAttributeDescriptions = attributeDescriptions.data()}; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{.topology = vk::PrimitiveTopology::eTriangleList}; + vk::PipelineViewportStateCreateInfo viewportState{.viewportCount = 1, .scissorCount = 1}; + + vk::PipelineRasterizationStateCreateInfo rasterizer{.depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, .frontFace = vk::FrontFace::eCounterClockwise, .depthBiasEnable = vk::False, .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f}; + + vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; + + vk::PipelineColorBlendAttachmentState colorBlendAttachment{.blendEnable = vk::False, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA}; + + vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0}; + + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format}; + vk::GraphicsPipelineCreateInfo pipelineInfo{.pNext = &pipelineRenderingCreateInfo, + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex}; + commandPool = vk::raii::CommandPool(device, poolInfo); + } + + void createVertexBuffer() + { + vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(dataStaging, vertices.data(), bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + } + + void createIndexBuffer() + { + vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(data, indices.data(), (size_t) bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); + + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + } + + void createUniformBuffers() + { + uniformBuffers.clear(); + uniformBuffersMemory.clear(); + uniformBuffersMapped.clear(); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DeviceSize bufferSize = sizeof(UniformBufferObject); + vk::raii::Buffer buffer({}); + vk::raii::DeviceMemory bufferMem({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); + uniformBuffers.emplace_back(std::move(buffer)); + uniformBuffersMemory.emplace_back(std::move(bufferMem)); + uniformBuffersMapped.emplace_back(uniformBuffersMemory[i].mapMemory(0, bufferSize)); + } + } + + void createDescriptorPool() + { + vk::DescriptorPoolSize poolSize(vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT); + vk::DescriptorPoolCreateInfo poolInfo{.flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, .maxSets = MAX_FRAMES_IN_FLIGHT, .poolSizeCount = 1, .pPoolSizes = &poolSize}; + descriptorPool = vk::raii::DescriptorPool(device, poolInfo); + } + + void createDescriptorSets() + { + std::vector layouts(MAX_FRAMES_IN_FLIGHT, *descriptorSetLayout); + vk::DescriptorSetAllocateInfo allocInfo{.descriptorPool = descriptorPool, .descriptorSetCount = static_cast(layouts.size()), .pSetLayouts = layouts.data()}; + + descriptorSets = device.allocateDescriptorSets(allocInfo); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DescriptorBufferInfo bufferInfo{.buffer = uniformBuffers[i], .offset = 0, .range = sizeof(UniformBufferObject)}; + vk::WriteDescriptorSet descriptorWrite{.dstSet = descriptorSets[i], .dstBinding = 0, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eUniformBuffer, .pBufferInfo = &bufferInfo}; + device.updateDescriptorSets(descriptorWrite, {}); + } + } + + void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer &buffer, vk::raii::DeviceMemory &bufferMemory) + { + vk::BufferCreateInfo bufferInfo{.size = size, .usage = usage, .sharingMode = vk::SharingMode::eExclusive}; + buffer = vk::raii::Buffer(device, bufferInfo); + vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{.allocationSize = memRequirements.size, .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + bufferMemory = vk::raii::DeviceMemory(device, allocInfo); + buffer.bindMemory(bufferMemory, 0); + } + + void copyBuffer(vk::raii::Buffer &srcBuffer, vk::raii::Buffer &dstBuffer, vk::DeviceSize size) + { + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1}; + vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); + commandCopyBuffer.begin(vk::CommandBufferBeginInfo{.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); + commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy(0, 0, size)); + commandCopyBuffer.end(); + queue.submit(vk::SubmitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer}, nullptr); + queue.waitIdle(); + } + + uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) + { + vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) + { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) + { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + void createCommandBuffers() + { + commandBuffers.clear(); + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = MAX_FRAMES_IN_FLIGHT}; + commandBuffers = vk::raii::CommandBuffers(device, allocInfo); + } + + void recordCommandBuffer(uint32_t imageIndex) + { + commandBuffers[currentFrame].begin({}); + // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL + transition_image_layout( + imageIndex, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage + ); + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::RenderingAttachmentInfo attachmentInfo = { + .imageView = swapChainImageViews[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor}; + vk::RenderingInfo renderingInfo = { + .renderArea = {.offset = {0, 0}, .extent = swapChainExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &attachmentInfo}; + commandBuffers[currentFrame].beginRendering(renderingInfo); + commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint16); + commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); + commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); + commandBuffers[currentFrame].endRendering(); + // After rendering, transition the swapchain image to PRESENT_SRC + transition_image_layout( + imageIndex, + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage + ); + commandBuffers[currentFrame].end(); + } + + void transition_image_layout( + uint32_t imageIndex, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout, + vk::AccessFlags2 src_access_mask, + vk::AccessFlags2 dst_access_mask, + vk::PipelineStageFlags2 src_stage_mask, + vk::PipelineStageFlags2 dst_stage_mask) + { + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = src_stage_mask, + .srcAccessMask = src_access_mask, + .dstStageMask = dst_stage_mask, + .dstAccessMask = dst_access_mask, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo dependency_info = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + } + + void createSyncObjects() + { + presentCompleteSemaphore.clear(); + renderFinishedSemaphore.clear(); + inFlightFences.clear(); + + for (size_t i = 0; i < swapChainImages.size(); i++) + { + presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); + } + } + + void updateUniformBuffer(uint32_t currentImage) + { + static auto startTime = std::chrono::high_resolution_clock::now(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + UniformBufferObject ubo{}; + ubo.model = rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.view = lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); + ubo.proj[1][1] *= -1; + + memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); + } + + void drawFrame() + { + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + ; + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr); + + if (result == vk::Result::eErrorOutOfDateKHR) + { + recreateSwapChain(); + return; + } + if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) + { + throw std::runtime_error("failed to acquire swap chain image!"); + } + updateUniformBuffer(currentFrame); + + device.resetFences(*inFlightFences[currentFrame]); + commandBuffers[currentFrame].reset(); + recordCommandBuffer(imageIndex); + + vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[currentFrame]); + + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + result = queue.presentKHR(presentInfoKHR); + if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) + { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != vk::Result::eSuccess) + { + throw std::runtime_error("failed to present swap chain image!"); + } + semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size(), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const &surfaceCapabilities) + { + auto minImageCount = std::max(3u, surfaceCapabilities.minImageCount); + if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) + { + minImageCount = surfaceCapabilities.maxImageCount; + } + return minImageCount; + } + + static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + assert(!availableFormats.empty()); + const auto formatIt = std::ranges::find_if( + availableFormats, + [](const auto &format) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; }); + return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + assert(std::ranges::any_of(availablePresentModes, [](auto presentMode) { return presentMode == vk::PresentModeKHR::eFifo; })); + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + vk::PresentModeKHR::eFifo; + } + + vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR &capabilities) + { + if (capabilities.currentExtent.width != 0xFFFFFFFF) + { + 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)}; + } + + std::vector getRequiredExtensions() + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + 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: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::SurfaceFormatKHR swapChainSurfaceFormat; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - - vk::raii::Buffer vertexBuffer = nullptr; - vk::raii::DeviceMemory vertexBufferMemory = nullptr; - vk::raii::Buffer indexBuffer = nullptr; - vk::raii::DeviceMemory indexBufferMemory = nullptr; - - std::vector uniformBuffers; - std::vector uniformBuffersMemory; - std::vector uniformBuffersMapped; - - vk::raii::DescriptorPool descriptorPool = nullptr; - std::vector descriptorSets; - - vk::raii::CommandPool commandPool = nullptr; - std::vector commandBuffers; - - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; - std::vector inFlightFences; - uint32_t semaphoreIndex = 0; - uint32_t currentFrame = 0; - - bool framebufferResized = false; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); - } - - static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { - auto app = static_cast(glfwGetWindowUserPointer(window)); - app->framebufferResized = true; - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - createSurface(); - pickPhysicalDevice(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createDescriptorSetLayout(); - createGraphicsPipeline(); - createCommandPool(); - createVertexBuffer(); - createIndexBuffer(); - createUniformBuffers(); - createDescriptorPool(); - createDescriptorSets(); - createCommandBuffers(); - createSyncObjects(); - } - - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - drawFrame(); - } - - device.waitIdle(); - } - - void cleanupSwapChain() { - swapChainImageViews.clear(); - swapChain = nullptr; - } - - void cleanup() { - glfwDestroyWindow(window); - - glfwTerminate(); - } - - void recreateSwapChain() { - int width = 0, height = 0; - glfwGetFramebufferSize(window, &width, &height); - while (width == 0 || height == 0) { - glfwGetFramebufferSize(window, &width, &height); - glfwWaitEvents(); - } - - device.waitIdle(); - - cleanupSwapChain(); - createSwapChain(); - createImageViews(); - } - - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - } ); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error( "failed to find a suitable GPU!" ); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain featureChain = { - {}, // vk::PhysicalDeviceFeatures2 - {.synchronization2 = true, .dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( *surface ); - swapChainExtent = chooseSwapExtent( surfaceCapabilities ); - swapChainSurfaceFormat = chooseSwapSurfaceFormat( physicalDevice.getSurfaceFormatsKHR( *surface ) ); - vk::SwapchainCreateInfoKHR swapChainCreateInfo{ .surface = *surface, - .minImageCount = chooseSwapMinImageCount( surfaceCapabilities ), - .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 }; - - swapChain = vk::raii::SwapchainKHR( device, swapChainCreateInfo ); - swapChainImages = swapChain.getImages(); - } - - void createImageViews() { - vk::ImageViewCreateInfo imageViewCreateInfo{ .viewType = vk::ImageViewType::e2D, .format = swapChainSurfaceFormat.format, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } }; - for ( auto image : swapChainImages ) - { - imageViewCreateInfo.image = image; - swapChainImageViews.emplace_back( device, imageViewCreateInfo ); - } - } - - void createDescriptorSetLayout() { - vk::DescriptorSetLayoutBinding uboLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr); - vk::DescriptorSetLayoutCreateInfo layoutInfo{ .bindingCount = 1, .pBindings = &uboLayoutBinding }; - descriptorSetLayout = vk::raii::DescriptorSetLayout( device, layoutInfo ); - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; - - auto bindingDescription = Vertex::getBindingDescription(); - auto attributeDescriptions = Vertex::getAttributeDescriptions(); - vk::PipelineVertexInputStateCreateInfo vertexInputInfo { .vertexBindingDescriptionCount =1, .pVertexBindingDescriptions = &bindingDescription, - .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), .pVertexAttributeDescriptions = attributeDescriptions.data() }; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ .topology = vk::PrimitiveTopology::eTriangleList }; - vk::PipelineViewportStateCreateInfo viewportState{ .viewportCount = 1, .scissorCount = 1 }; - - vk::PipelineRasterizationStateCreateInfo rasterizer{ .depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eCounterClockwise, .depthBiasEnable = vk::False, - .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f }; - - vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; - - vk::PipelineColorBlendAttachmentState colorBlendAttachment{ .blendEnable = vk::False, - .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA - }; - - vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0 }; - - pipelineLayout = vk::raii::PipelineLayout( device, pipelineLayoutInfo ); - - vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ .colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format }; - vk::GraphicsPipelineCreateInfo pipelineInfo{ .pNext = &pipelineRenderingCreateInfo, - .stageCount = 2, .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, .layout = pipelineLayout, .renderPass = nullptr }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{ .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex }; - commandPool = vk::raii::CommandPool(device, poolInfo); - } - - void createVertexBuffer() { - vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(dataStaging, vertices.data(), bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); - - copyBuffer(stagingBuffer, vertexBuffer, bufferSize); - } - - void createIndexBuffer() { - vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); - - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(data, indices.data(), (size_t) bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); - - copyBuffer(stagingBuffer, indexBuffer, bufferSize); - } - - void createUniformBuffers() { - uniformBuffers.clear(); - uniformBuffersMemory.clear(); - uniformBuffersMapped.clear(); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DeviceSize bufferSize = sizeof(UniformBufferObject); - vk::raii::Buffer buffer({}); - vk::raii::DeviceMemory bufferMem({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); - uniformBuffers.emplace_back(std::move(buffer)); - uniformBuffersMemory.emplace_back(std::move(bufferMem)); - uniformBuffersMapped.emplace_back( uniformBuffersMemory[i].mapMemory(0, bufferSize)); - } - } - - void createDescriptorPool() { - vk::DescriptorPoolSize poolSize(vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT); - vk::DescriptorPoolCreateInfo poolInfo{ .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, .maxSets = MAX_FRAMES_IN_FLIGHT, .poolSizeCount = 1, .pPoolSizes = &poolSize }; - descriptorPool = vk::raii::DescriptorPool(device, poolInfo); - } - - void createDescriptorSets() { - std::vector layouts(MAX_FRAMES_IN_FLIGHT, *descriptorSetLayout); - vk::DescriptorSetAllocateInfo allocInfo{ .descriptorPool = descriptorPool, .descriptorSetCount = static_cast(layouts.size()), .pSetLayouts = layouts.data() }; - - descriptorSets = device.allocateDescriptorSets(allocInfo); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DescriptorBufferInfo bufferInfo{ .buffer = uniformBuffers[i], .offset = 0, .range = sizeof(UniformBufferObject) }; - vk::WriteDescriptorSet descriptorWrite{ .dstSet = descriptorSets[i], .dstBinding = 0, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eUniformBuffer, .pBufferInfo = &bufferInfo }; - device.updateDescriptorSets(descriptorWrite, {}); - } - } - - void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer& buffer, vk::raii::DeviceMemory& bufferMemory) { - vk::BufferCreateInfo bufferInfo{ .size = size, .usage = usage, .sharingMode = vk::SharingMode::eExclusive }; - buffer = vk::raii::Buffer(device, bufferInfo); - vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ .allocationSize =memRequirements.size, .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) }; - bufferMemory = vk::raii::DeviceMemory(device, allocInfo); - buffer.bindMemory(bufferMemory, 0); - } - - void copyBuffer(vk::raii::Buffer & srcBuffer, vk::raii::Buffer & dstBuffer, vk::DeviceSize size) { - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1 }; - vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); - commandCopyBuffer.begin(vk::CommandBufferBeginInfo { .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit }); - commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy(0, 0, size)); - commandCopyBuffer.end(); - queue.submit(vk::SubmitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer }, nullptr); - queue.waitIdle(); - } - - uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) { - vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); - - for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { - if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { - return i; - } - } - - throw std::runtime_error("failed to find suitable memory type!"); - } - - void createCommandBuffers() { - commandBuffers.clear(); - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = MAX_FRAMES_IN_FLIGHT }; - commandBuffers = vk::raii::CommandBuffers( device, allocInfo ); - } - - void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin( {} ); - // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL - transition_image_layout( - imageIndex, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage - ); - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::RenderingAttachmentInfo attachmentInfo = { - .imageView = swapChainImageViews[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor - }; - vk::RenderingInfo renderingInfo = { - .renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &attachmentInfo - }; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor( 0, vk::Rect2D( vk::Offset2D( 0, 0 ), swapChainExtent ) ); - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].bindIndexBuffer( *indexBuffer, 0, vk::IndexType::eUint16 ); - commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); - commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); - commandBuffers[currentFrame].endRendering(); - // After rendering, transition the swapchain image to PRESENT_SRC - transition_image_layout( - imageIndex, - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage - ); - commandBuffers[currentFrame].end(); - } - - void transition_image_layout( - uint32_t imageIndex, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::AccessFlags2 src_access_mask, - vk::AccessFlags2 dst_access_mask, - vk::PipelineStageFlags2 src_stage_mask, - vk::PipelineStageFlags2 dst_stage_mask - ) { - vk::ImageMemoryBarrier2 barrier = { - .srcStageMask = src_stage_mask, - .srcAccessMask = src_access_mask, - .dstStageMask = dst_stage_mask, - .dstAccessMask = dst_access_mask, - .oldLayout = old_layout, - .newLayout = new_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); - } - - void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); - - for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - } - - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - inFlightFences.emplace_back(device, vk::FenceCreateInfo { .flags = vk::FenceCreateFlagBits::eSignaled }); - } - } - - void updateUniformBuffer(uint32_t currentImage) { - static auto startTime = std::chrono::high_resolution_clock::now(); - - auto currentTime = std::chrono::high_resolution_clock::now(); - float time = std::chrono::duration(currentTime - startTime).count(); - - UniformBufferObject ubo{}; - ubo.model = rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.view = lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); - ubo.proj[1][1] *= -1; - - memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); - } - - void drawFrame() { - while ( vk::Result::eTimeout == device.waitForFences( *inFlightFences[currentFrame], vk::True, UINT64_MAX ) ) - ; - auto [result, imageIndex] = swapChain.acquireNextImage( UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr ); - - if (result == vk::Result::eErrorOutOfDateKHR) { - recreateSwapChain(); - return; - } - if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) { - throw std::runtime_error("failed to acquire swap chain image!"); - } - updateUniformBuffer(currentFrame); - - device.resetFences( *inFlightFences[currentFrame] ); - commandBuffers[currentFrame].reset(); - recordCommandBuffer(imageIndex); - - vk::PipelineStageFlags waitDestinationStageMask( vk::PipelineStageFlagBits::eColorAttachmentOutput ); - const vk::SubmitInfo submitInfo{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], - .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], - .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex] }; - queue.submit(submitInfo, *inFlightFences[currentFrame]); - - - const vk::PresentInfoKHR presentInfoKHR{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], - .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex }; - result = queue.presentKHR( presentInfoKHR ); - if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { - framebufferResized = false; - recreateSwapChain(); - } else if (result != vk::Result::eSuccess) { - throw std::runtime_error("failed to present swap chain image!"); - } - semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; - } - - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size(), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const & surfaceCapabilities) { - auto minImageCount = std::max( 3u, surfaceCapabilities.minImageCount ); - if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) { - minImageCount = surfaceCapabilities.maxImageCount; - } - return minImageCount; - } - - static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { - assert(!availableFormats.empty()); - const auto formatIt = std::ranges::find_if( - availableFormats, - []( const auto & format ) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; } ); - return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - assert(std::ranges::any_of(availablePresentModes, [](auto presentMode){ return presentMode == vk::PresentModeKHR::eFifo; })); - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eFifo; - } - - vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) { - if (capabilities.currentExtent.width != 0xFFFFFFFF) { - 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) - }; - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - 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; - } -}; - -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/24_texture_image.cpp b/attachments/24_texture_image.cpp index 901bfdcc..e37330b3 100644 --- a/attachments/24_texture_image.cpp +++ b/attachments/24_texture_image.cpp @@ -12,14 +12,14 @@ #include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include #define GLM_FORCE_RADIANS @@ -29,13 +29,12 @@ import vulkan_hpp; #define STB_IMAGE_IMPLEMENTATION #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -constexpr int MAX_FRAMES_IN_FLIGHT = 2; +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; +constexpr int MAX_FRAMES_IN_FLIGHT = 2; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -43,849 +42,902 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -struct Vertex { - glm::vec2 pos; - glm::vec3 color; - - static vk::VertexInputBindingDescription getBindingDescription() { - return { 0, sizeof(Vertex), vk::VertexInputRate::eVertex }; - } - - static std::array getAttributeDescriptions() { - return { - vk::VertexInputAttributeDescription( 0, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, pos) ), - vk::VertexInputAttributeDescription( 1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color) ) - }; - } +struct Vertex +{ + glm::vec2 pos; + glm::vec3 color; + + static vk::VertexInputBindingDescription getBindingDescription() + { + return {0, sizeof(Vertex), vk::VertexInputRate::eVertex}; + } + + static std::array getAttributeDescriptions() + { + return { + vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, pos)), + vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color))}; + } }; -struct UniformBufferObject { - alignas(16) glm::mat4 model; - alignas(16) glm::mat4 view; - alignas(16) glm::mat4 proj; +struct UniformBufferObject +{ + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; }; const std::vector vertices = { {{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}}, {{0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}}, {{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}, - {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}} -}; + {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}}}; const std::vector indices = { - 0, 1, 2, 2, 3, 0 + 0, 1, 2, 2, 3, 0}; + +class HelloTriangleApplication +{ + public: + void run() + { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + + private: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::SurfaceFormatKHR swapChainSurfaceFormat; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + + vk::raii::Image textureImage = nullptr; + vk::raii::DeviceMemory textureImageMemory = nullptr; + + vk::raii::Buffer vertexBuffer = nullptr; + vk::raii::DeviceMemory vertexBufferMemory = nullptr; + vk::raii::Buffer indexBuffer = nullptr; + vk::raii::DeviceMemory indexBufferMemory = nullptr; + + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + std::vector uniformBuffersMapped; + + vk::raii::DescriptorPool descriptorPool = nullptr; + std::vector descriptorSets; + + vk::raii::CommandPool commandPool = nullptr; + std::vector commandBuffers; + + std::vector presentCompleteSemaphore; + std::vector renderFinishedSemaphore; + std::vector inFlightFences; + uint32_t semaphoreIndex = 0; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow *window, int width, int height) + { + auto app = static_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createDescriptorSetLayout(); + createGraphicsPipeline(); + createCommandPool(); + createTextureImage(); + createVertexBuffer(); + createIndexBuffer(); + createUniformBuffers(); + createDescriptorPool(); + createDescriptorSets(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() + { + while (!glfwWindowShouldClose(window)) + { + glfwPollEvents(); + drawFrame(); + } + + device.waitIdle(); + } + + void cleanupSwapChain() + { + swapChainImageViews.clear(); + swapChain = nullptr; + } + + void cleanup() + { + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() + { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) + { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + device.waitIdle(); + + cleanupSwapChain(); + createSwapChain(); + createImageViews(); + } + + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain featureChain = { + {}, // vk::PhysicalDeviceFeatures2 + {.synchronization2 = true, .dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(*surface); + swapChainExtent = chooseSwapExtent(surfaceCapabilities); + swapChainSurfaceFormat = chooseSwapSurfaceFormat(physicalDevice.getSurfaceFormatsKHR(*surface)); + vk::SwapchainCreateInfoKHR swapChainCreateInfo{.surface = *surface, + .minImageCount = chooseSwapMinImageCount(surfaceCapabilities), + .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + void createImageViews() + { + vk::ImageViewCreateInfo imageViewCreateInfo{.viewType = vk::ImageViewType::e2D, .format = swapChainSurfaceFormat.format, .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + for (auto image : swapChainImages) + { + imageViewCreateInfo.image = image; + swapChainImageViews.emplace_back(device, imageViewCreateInfo); + } + } + + void createDescriptorSetLayout() + { + vk::DescriptorSetLayoutBinding uboLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr); + vk::DescriptorSetLayoutCreateInfo layoutInfo{.bindingCount = 1, .pBindings = &uboLayoutBinding}; + descriptorSetLayout = vk::raii::DescriptorSetLayout(device, layoutInfo); + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; + + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{.vertexBindingDescriptionCount = 1, .pVertexBindingDescriptions = &bindingDescription, .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), .pVertexAttributeDescriptions = attributeDescriptions.data()}; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{.topology = vk::PrimitiveTopology::eTriangleList}; + vk::PipelineViewportStateCreateInfo viewportState{.viewportCount = 1, .scissorCount = 1}; + + vk::PipelineRasterizationStateCreateInfo rasterizer{.depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, .frontFace = vk::FrontFace::eCounterClockwise, .depthBiasEnable = vk::False, .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f}; + + vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; + + vk::PipelineColorBlendAttachmentState colorBlendAttachment{.blendEnable = vk::False, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA}; + + vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0}; + + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format}; + vk::GraphicsPipelineCreateInfo pipelineInfo{.pNext = &pipelineRenderingCreateInfo, + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex}; + commandPool = vk::raii::CommandPool(device, poolInfo); + } + + void createTextureImage() + { + int texWidth, texHeight, texChannels; + stbi_uc *pixels = stbi_load("textures/texture.jpg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + vk::DeviceSize imageSize = texWidth * texHeight * 4; + + if (!pixels) + { + throw std::runtime_error("failed to load texture image!"); + } + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, imageSize); + memcpy(data, pixels, imageSize); + stagingBufferMemory.unmapMemory(); + + stbi_image_free(pixels); + + createImage(texWidth, texHeight, vk::Format::eR8G8B8A8Srgb, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory); + + transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); + transitionImageLayout(textureImage, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal); + } + + void createImage(uint32_t width, uint32_t height, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image &image, vk::raii::DeviceMemory &imageMemory) + { + vk::ImageCreateInfo imageInfo{.imageType = vk::ImageType::e2D, .format = format, .extent = {width, height, 1}, .mipLevels = 1, .arrayLayers = 1, .samples = vk::SampleCountFlagBits::e1, .tiling = tiling, .usage = usage, .sharingMode = vk::SharingMode::eExclusive}; + + image = vk::raii::Image(device, imageInfo); + + vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{.allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + imageMemory = vk::raii::DeviceMemory(device, allocInfo); + image.bindMemory(imageMemory, 0); + } + + void transitionImageLayout(const vk::raii::Image &image, vk::ImageLayout oldLayout, vk::ImageLayout newLayout) + { + auto commandBuffer = beginSingleTimeCommands(); + + vk::ImageMemoryBarrier barrier{.oldLayout = oldLayout, .newLayout = newLayout, .image = image, .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + + vk::PipelineStageFlags sourceStage; + vk::PipelineStageFlags destinationStage; + + if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) + { + barrier.srcAccessMask = {}; + barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; + + sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; + destinationStage = vk::PipelineStageFlagBits::eTransfer; + } + else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) + { + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + + sourceStage = vk::PipelineStageFlagBits::eTransfer; + destinationStage = vk::PipelineStageFlagBits::eFragmentShader; + } + else + { + throw std::invalid_argument("unsupported layout transition!"); + } + commandBuffer->pipelineBarrier(sourceStage, destinationStage, {}, {}, nullptr, barrier); + endSingleTimeCommands(*commandBuffer); + } + + void copyBufferToImage(const vk::raii::Buffer &buffer, vk::raii::Image &image, uint32_t width, uint32_t height) + { + std::unique_ptr commandBuffer = beginSingleTimeCommands(); + vk::BufferImageCopy region{.bufferOffset = 0, .bufferRowLength = 0, .bufferImageHeight = 0, .imageSubresource = {vk::ImageAspectFlagBits::eColor, 0, 0, 1}, .imageOffset = {0, 0, 0}, .imageExtent = {width, height, 1}}; + commandBuffer->copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, {region}); + endSingleTimeCommands(*commandBuffer); + } + + void createVertexBuffer() + { + vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(dataStaging, vertices.data(), bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + } + + void createIndexBuffer() + { + vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(data, indices.data(), (size_t) bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); + + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + } + + void createUniformBuffers() + { + uniformBuffers.clear(); + uniformBuffersMemory.clear(); + uniformBuffersMapped.clear(); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DeviceSize bufferSize = sizeof(UniformBufferObject); + vk::raii::Buffer buffer({}); + vk::raii::DeviceMemory bufferMem({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); + uniformBuffers.emplace_back(std::move(buffer)); + uniformBuffersMemory.emplace_back(std::move(bufferMem)); + uniformBuffersMapped.emplace_back(uniformBuffersMemory[i].mapMemory(0, bufferSize)); + } + } + + void createDescriptorPool() + { + vk::DescriptorPoolSize poolSize(vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT); + vk::DescriptorPoolCreateInfo poolInfo{.flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, .maxSets = MAX_FRAMES_IN_FLIGHT, .poolSizeCount = 1, .pPoolSizes = &poolSize}; + descriptorPool = vk::raii::DescriptorPool(device, poolInfo); + } + + void createDescriptorSets() + { + std::vector layouts(MAX_FRAMES_IN_FLIGHT, *descriptorSetLayout); + vk::DescriptorSetAllocateInfo allocInfo{.descriptorPool = descriptorPool, .descriptorSetCount = static_cast(layouts.size()), .pSetLayouts = layouts.data()}; + + descriptorSets = device.allocateDescriptorSets(allocInfo); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DescriptorBufferInfo bufferInfo{.buffer = uniformBuffers[i], .offset = 0, .range = sizeof(UniformBufferObject)}; + vk::WriteDescriptorSet descriptorWrite{.dstSet = descriptorSets[i], .dstBinding = 0, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eUniformBuffer, .pBufferInfo = &bufferInfo}; + device.updateDescriptorSets(descriptorWrite, {}); + } + } + + void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer &buffer, vk::raii::DeviceMemory &bufferMemory) + { + vk::BufferCreateInfo bufferInfo{.size = size, .usage = usage, .sharingMode = vk::SharingMode::eExclusive}; + buffer = vk::raii::Buffer(device, bufferInfo); + vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{.allocationSize = memRequirements.size, .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + bufferMemory = vk::raii::DeviceMemory(device, allocInfo); + buffer.bindMemory(bufferMemory, 0); + } + + std::unique_ptr beginSingleTimeCommands() + { + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1}; + std::unique_ptr commandBuffer = std::make_unique(std::move(vk::raii::CommandBuffers(device, allocInfo).front())); + + vk::CommandBufferBeginInfo beginInfo{.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; + commandBuffer->begin(beginInfo); + + return commandBuffer; + } + + void endSingleTimeCommands(vk::raii::CommandBuffer &commandBuffer) + { + commandBuffer.end(); + + vk::SubmitInfo submitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandBuffer}; + queue.submit(submitInfo, nullptr); + queue.waitIdle(); + } + + void copyBuffer(vk::raii::Buffer &srcBuffer, vk::raii::Buffer &dstBuffer, vk::DeviceSize size) + { + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1}; + vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); + commandCopyBuffer.begin(vk::CommandBufferBeginInfo{.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); + commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy{.size = size}); + commandCopyBuffer.end(); + queue.submit(vk::SubmitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer}, nullptr); + queue.waitIdle(); + } + + uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) + { + vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) + { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) + { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + void createCommandBuffers() + { + commandBuffers.clear(); + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = MAX_FRAMES_IN_FLIGHT}; + commandBuffers = vk::raii::CommandBuffers(device, allocInfo); + } + + void recordCommandBuffer(uint32_t imageIndex) + { + commandBuffers[currentFrame].begin({}); + // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL + transition_image_layout( + imageIndex, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage + ); + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::RenderingAttachmentInfo attachmentInfo = { + .imageView = swapChainImageViews[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor}; + vk::RenderingInfo renderingInfo = { + .renderArea = {.offset = {0, 0}, .extent = swapChainExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &attachmentInfo}; + commandBuffers[currentFrame].beginRendering(renderingInfo); + commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint16); + commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); + commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); + commandBuffers[currentFrame].endRendering(); + // After rendering, transition the swapchain image to PRESENT_SRC + transition_image_layout( + imageIndex, + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage + ); + commandBuffers[currentFrame].end(); + } + + void transition_image_layout( + uint32_t imageIndex, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout, + vk::AccessFlags2 src_access_mask, + vk::AccessFlags2 dst_access_mask, + vk::PipelineStageFlags2 src_stage_mask, + vk::PipelineStageFlags2 dst_stage_mask) + { + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = src_stage_mask, + .srcAccessMask = src_access_mask, + .dstStageMask = dst_stage_mask, + .dstAccessMask = dst_access_mask, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo dependency_info = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + } + + void createSyncObjects() + { + presentCompleteSemaphore.clear(); + renderFinishedSemaphore.clear(); + inFlightFences.clear(); + + for (size_t i = 0; i < swapChainImages.size(); i++) + { + presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); + } + } + + void updateUniformBuffer(uint32_t currentImage) + { + static auto startTime = std::chrono::high_resolution_clock::now(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + UniformBufferObject ubo{}; + ubo.model = rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.view = lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); + ubo.proj[1][1] *= -1; + + memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); + } + + void drawFrame() + { + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + ; + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr); + + if (result == vk::Result::eErrorOutOfDateKHR) + { + recreateSwapChain(); + return; + } + if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) + { + throw std::runtime_error("failed to acquire swap chain image!"); + } + updateUniformBuffer(currentFrame); + + device.resetFences(*inFlightFences[currentFrame]); + commandBuffers[currentFrame].reset(); + recordCommandBuffer(imageIndex); + + vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[currentFrame]); + + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + result = queue.presentKHR(presentInfoKHR); + if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) + { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != vk::Result::eSuccess) + { + throw std::runtime_error("failed to present swap chain image!"); + } + semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size(), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const &surfaceCapabilities) + { + auto minImageCount = std::max(3u, surfaceCapabilities.minImageCount); + if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) + { + minImageCount = surfaceCapabilities.maxImageCount; + } + return minImageCount; + } + + static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + assert(!availableFormats.empty()); + const auto formatIt = std::ranges::find_if( + availableFormats, + [](const auto &format) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; }); + return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + assert(std::ranges::any_of(availablePresentModes, [](auto presentMode) { return presentMode == vk::PresentModeKHR::eFifo; })); + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + vk::PresentModeKHR::eFifo; + } + + vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR &capabilities) + { + if (capabilities.currentExtent.width != 0xFFFFFFFF) + { + 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)}; + } + + std::vector getRequiredExtensions() + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + 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: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::SurfaceFormatKHR swapChainSurfaceFormat; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - - vk::raii::Image textureImage = nullptr; - vk::raii::DeviceMemory textureImageMemory = nullptr; - - vk::raii::Buffer vertexBuffer = nullptr; - vk::raii::DeviceMemory vertexBufferMemory = nullptr; - vk::raii::Buffer indexBuffer = nullptr; - vk::raii::DeviceMemory indexBufferMemory = nullptr; - - std::vector uniformBuffers; - std::vector uniformBuffersMemory; - std::vector uniformBuffersMapped; - - vk::raii::DescriptorPool descriptorPool = nullptr; - std::vector descriptorSets; - - vk::raii::CommandPool commandPool = nullptr; - std::vector commandBuffers; - - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; - std::vector inFlightFences; - uint32_t semaphoreIndex = 0; - uint32_t currentFrame = 0; - - bool framebufferResized = false; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); - } - - static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { - auto app = static_cast(glfwGetWindowUserPointer(window)); - app->framebufferResized = true; - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - createSurface(); - pickPhysicalDevice(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createDescriptorSetLayout(); - createGraphicsPipeline(); - createCommandPool(); - createTextureImage(); - createVertexBuffer(); - createIndexBuffer(); - createUniformBuffers(); - createDescriptorPool(); - createDescriptorSets(); - createCommandBuffers(); - createSyncObjects(); - } - - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - drawFrame(); - } - - device.waitIdle(); - } - - void cleanupSwapChain() { - swapChainImageViews.clear(); - swapChain = nullptr; - } - - void cleanup() { - glfwDestroyWindow(window); - - glfwTerminate(); - } - - void recreateSwapChain() { - int width = 0, height = 0; - glfwGetFramebufferSize(window, &width, &height); - while (width == 0 || height == 0) { - glfwGetFramebufferSize(window, &width, &height); - glfwWaitEvents(); - } - - device.waitIdle(); - - cleanupSwapChain(); - createSwapChain(); - createImageViews(); - } - - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - } ); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error( "failed to find a suitable GPU!" ); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain featureChain = { - {}, // vk::PhysicalDeviceFeatures2 - {.synchronization2 = true, .dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( *surface ); - swapChainExtent = chooseSwapExtent( surfaceCapabilities ); - swapChainSurfaceFormat = chooseSwapSurfaceFormat( physicalDevice.getSurfaceFormatsKHR( *surface ) ); - vk::SwapchainCreateInfoKHR swapChainCreateInfo{ .surface = *surface, - .minImageCount = chooseSwapMinImageCount( surfaceCapabilities ), - .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 }; - - swapChain = vk::raii::SwapchainKHR( device, swapChainCreateInfo ); - swapChainImages = swapChain.getImages(); - } - - void createImageViews() { - vk::ImageViewCreateInfo imageViewCreateInfo{ .viewType = vk::ImageViewType::e2D, .format = swapChainSurfaceFormat.format, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } }; - for ( auto image : swapChainImages ) - { - imageViewCreateInfo.image = image; - swapChainImageViews.emplace_back( device, imageViewCreateInfo ); - } - } - - void createDescriptorSetLayout() { - vk::DescriptorSetLayoutBinding uboLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr); - vk::DescriptorSetLayoutCreateInfo layoutInfo{ .bindingCount = 1, .pBindings = &uboLayoutBinding }; - descriptorSetLayout = vk::raii::DescriptorSetLayout( device, layoutInfo ); - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; - - auto bindingDescription = Vertex::getBindingDescription(); - auto attributeDescriptions = Vertex::getAttributeDescriptions(); - vk::PipelineVertexInputStateCreateInfo vertexInputInfo { .vertexBindingDescriptionCount =1, .pVertexBindingDescriptions = &bindingDescription, - .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), .pVertexAttributeDescriptions = attributeDescriptions.data() }; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ .topology = vk::PrimitiveTopology::eTriangleList }; - vk::PipelineViewportStateCreateInfo viewportState{ .viewportCount = 1, .scissorCount = 1 }; - - vk::PipelineRasterizationStateCreateInfo rasterizer{ .depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eCounterClockwise, .depthBiasEnable = vk::False, - .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f }; - - vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; - - vk::PipelineColorBlendAttachmentState colorBlendAttachment{ .blendEnable = vk::False, - .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA - }; - - vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0 }; - - pipelineLayout = vk::raii::PipelineLayout( device, pipelineLayoutInfo ); - - vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ .colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format }; - vk::GraphicsPipelineCreateInfo pipelineInfo{ .pNext = &pipelineRenderingCreateInfo, - .stageCount = 2, .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, .layout = pipelineLayout, .renderPass = nullptr }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{ .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex }; - commandPool = vk::raii::CommandPool(device, poolInfo); - } - - void createTextureImage() { - int texWidth, texHeight, texChannels; - stbi_uc* pixels = stbi_load("textures/texture.jpg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); - vk::DeviceSize imageSize = texWidth * texHeight * 4; - - if (!pixels) { - throw std::runtime_error("failed to load texture image!"); - } - - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, imageSize); - memcpy(data, pixels, imageSize); - stagingBufferMemory.unmapMemory(); - - stbi_image_free(pixels); - - createImage(texWidth, texHeight, vk::Format::eR8G8B8A8Srgb, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory); - - transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal); - copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); - transitionImageLayout(textureImage, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal); - } - - void createImage(uint32_t width, uint32_t height, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image& image, vk::raii::DeviceMemory& imageMemory) { - vk::ImageCreateInfo imageInfo{ .imageType = vk::ImageType::e2D, .format = format, - .extent = {width, height, 1}, .mipLevels = 1, .arrayLayers = 1, - .samples = vk::SampleCountFlagBits::e1, .tiling = tiling, - .usage = usage, .sharingMode = vk::SharingMode::eExclusive }; - - image = vk::raii::Image( device, imageInfo ); - - vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) }; - imageMemory = vk::raii::DeviceMemory( device, allocInfo ); - image.bindMemory(imageMemory, 0); - } - - void transitionImageLayout(const vk::raii::Image& image, vk::ImageLayout oldLayout, vk::ImageLayout newLayout) { - auto commandBuffer = beginSingleTimeCommands(); - - vk::ImageMemoryBarrier barrier{ .oldLayout = oldLayout, .newLayout = newLayout, - .image = image, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } }; - - vk::PipelineStageFlags sourceStage; - vk::PipelineStageFlags destinationStage; - - if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) { - barrier.srcAccessMask = {}; - barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; - - sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; - destinationStage = vk::PipelineStageFlagBits::eTransfer; - } else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) { - barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; - barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - - sourceStage = vk::PipelineStageFlagBits::eTransfer; - destinationStage = vk::PipelineStageFlagBits::eFragmentShader; - } else { - throw std::invalid_argument("unsupported layout transition!"); - } - commandBuffer->pipelineBarrier(sourceStage, destinationStage, {}, {}, nullptr, barrier); - endSingleTimeCommands(*commandBuffer); - } - - void copyBufferToImage(const vk::raii::Buffer& buffer, vk::raii::Image& image, uint32_t width, uint32_t height) { - std::unique_ptr commandBuffer = beginSingleTimeCommands(); - vk::BufferImageCopy region{ .bufferOffset = 0, .bufferRowLength = 0, .bufferImageHeight = 0, - .imageSubresource = { vk::ImageAspectFlagBits::eColor, 0, 0, 1 }, - .imageOffset = {0, 0, 0}, .imageExtent = {width, height, 1} }; - commandBuffer->copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, {region}); - endSingleTimeCommands(*commandBuffer); - } - - void createVertexBuffer() { - vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(dataStaging, vertices.data(), bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); - - copyBuffer(stagingBuffer, vertexBuffer, bufferSize); - } - - void createIndexBuffer() { - vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); - - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(data, indices.data(), (size_t) bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); - - copyBuffer(stagingBuffer, indexBuffer, bufferSize); - } - - void createUniformBuffers() { - uniformBuffers.clear(); - uniformBuffersMemory.clear(); - uniformBuffersMapped.clear(); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DeviceSize bufferSize = sizeof(UniformBufferObject); - vk::raii::Buffer buffer({}); - vk::raii::DeviceMemory bufferMem({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); - uniformBuffers.emplace_back(std::move(buffer)); - uniformBuffersMemory.emplace_back(std::move(bufferMem)); - uniformBuffersMapped.emplace_back( uniformBuffersMemory[i].mapMemory(0, bufferSize)); - } - } - - void createDescriptorPool() { - vk::DescriptorPoolSize poolSize(vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT); - vk::DescriptorPoolCreateInfo poolInfo{ .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, .maxSets = MAX_FRAMES_IN_FLIGHT, .poolSizeCount = 1, .pPoolSizes = &poolSize }; - descriptorPool = vk::raii::DescriptorPool(device, poolInfo); - } - - void createDescriptorSets() { - std::vector layouts(MAX_FRAMES_IN_FLIGHT, *descriptorSetLayout); - vk::DescriptorSetAllocateInfo allocInfo{ .descriptorPool = descriptorPool, .descriptorSetCount = static_cast(layouts.size()), .pSetLayouts = layouts.data() }; - - descriptorSets = device.allocateDescriptorSets(allocInfo); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DescriptorBufferInfo bufferInfo{ .buffer = uniformBuffers[i], .offset = 0, .range = sizeof(UniformBufferObject) }; - vk::WriteDescriptorSet descriptorWrite{ .dstSet = descriptorSets[i], .dstBinding = 0, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eUniformBuffer, .pBufferInfo = &bufferInfo }; - device.updateDescriptorSets(descriptorWrite, {}); - } - } - - void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer& buffer, vk::raii::DeviceMemory& bufferMemory) { - vk::BufferCreateInfo bufferInfo{ .size = size, .usage = usage, .sharingMode = vk::SharingMode::eExclusive }; - buffer = vk::raii::Buffer(device, bufferInfo); - vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ .allocationSize =memRequirements.size, .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) }; - bufferMemory = vk::raii::DeviceMemory(device, allocInfo); - buffer.bindMemory(bufferMemory, 0); - } - - std::unique_ptr beginSingleTimeCommands() { - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1 }; - std::unique_ptr commandBuffer = std::make_unique(std::move(vk::raii::CommandBuffers(device, allocInfo).front())); - - vk::CommandBufferBeginInfo beginInfo{ .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit }; - commandBuffer->begin(beginInfo); - - return commandBuffer; - } - - void endSingleTimeCommands(vk::raii::CommandBuffer& commandBuffer) { - commandBuffer.end(); - - vk::SubmitInfo submitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandBuffer }; - queue.submit(submitInfo, nullptr); - queue.waitIdle(); - } - - void copyBuffer(vk::raii::Buffer & srcBuffer, vk::raii::Buffer & dstBuffer, vk::DeviceSize size) { - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1 }; - vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); - commandCopyBuffer.begin(vk::CommandBufferBeginInfo{ .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit }); - commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy{ .size = size }); - commandCopyBuffer.end(); - queue.submit(vk::SubmitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer }, nullptr); - queue.waitIdle(); - } - - uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) { - vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); - - for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { - if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { - return i; - } - } - - throw std::runtime_error("failed to find suitable memory type!"); - } - - void createCommandBuffers() { - commandBuffers.clear(); - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = MAX_FRAMES_IN_FLIGHT }; - commandBuffers = vk::raii::CommandBuffers( device, allocInfo ); - } - - void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin( {} ); - // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL - transition_image_layout( - imageIndex, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage - ); - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::RenderingAttachmentInfo attachmentInfo = { - .imageView = swapChainImageViews[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor - }; - vk::RenderingInfo renderingInfo = { - .renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &attachmentInfo - }; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].bindIndexBuffer( *indexBuffer, 0, vk::IndexType::eUint16 ); - commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); - commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); - commandBuffers[currentFrame].endRendering(); - // After rendering, transition the swapchain image to PRESENT_SRC - transition_image_layout( - imageIndex, - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage - ); - commandBuffers[currentFrame].end(); - } - - void transition_image_layout( - uint32_t imageIndex, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::AccessFlags2 src_access_mask, - vk::AccessFlags2 dst_access_mask, - vk::PipelineStageFlags2 src_stage_mask, - vk::PipelineStageFlags2 dst_stage_mask - ) { - vk::ImageMemoryBarrier2 barrier = { - .srcStageMask = src_stage_mask, - .srcAccessMask = src_access_mask, - .dstStageMask = dst_stage_mask, - .dstAccessMask = dst_access_mask, - .oldLayout = old_layout, - .newLayout = new_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); - } - - void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); - - for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - } - - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - inFlightFences.emplace_back(device, vk::FenceCreateInfo { .flags = vk::FenceCreateFlagBits::eSignaled }); - } - } - - void updateUniformBuffer(uint32_t currentImage) { - static auto startTime = std::chrono::high_resolution_clock::now(); - - auto currentTime = std::chrono::high_resolution_clock::now(); - float time = std::chrono::duration(currentTime - startTime).count(); - - UniformBufferObject ubo{}; - ubo.model = rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.view = lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); - ubo.proj[1][1] *= -1; - - memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); - } - - void drawFrame() { - while ( vk::Result::eTimeout == device.waitForFences( *inFlightFences[currentFrame], vk::True, UINT64_MAX ) ) - ; - auto [result, imageIndex] = swapChain.acquireNextImage( UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr ); - - if (result == vk::Result::eErrorOutOfDateKHR) { - recreateSwapChain(); - return; - } - if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) { - throw std::runtime_error("failed to acquire swap chain image!"); - } - updateUniformBuffer(currentFrame); - - device.resetFences( *inFlightFences[currentFrame] ); - commandBuffers[currentFrame].reset(); - recordCommandBuffer(imageIndex); - - vk::PipelineStageFlags waitDestinationStageMask( vk::PipelineStageFlagBits::eColorAttachmentOutput ); - const vk::SubmitInfo submitInfo{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], - .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], - .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex] }; - queue.submit(submitInfo, *inFlightFences[currentFrame]); - - - const vk::PresentInfoKHR presentInfoKHR{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], - .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex }; - result = queue.presentKHR( presentInfoKHR ); - if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { - framebufferResized = false; - recreateSwapChain(); - } else if (result != vk::Result::eSuccess) { - throw std::runtime_error("failed to present swap chain image!"); - } - semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; - } - - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size(), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const & surfaceCapabilities) { - auto minImageCount = std::max( 3u, surfaceCapabilities.minImageCount ); - if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) { - minImageCount = surfaceCapabilities.maxImageCount; - } - return minImageCount; - } - - static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { - assert(!availableFormats.empty()); - const auto formatIt = std::ranges::find_if( - availableFormats, - []( const auto & format ) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; } ); - return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - assert(std::ranges::any_of(availablePresentModes, [](auto presentMode){ return presentMode == vk::PresentModeKHR::eFifo; })); - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eFifo; - } - - vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) { - if (capabilities.currentExtent.width != 0xFFFFFFFF) { - 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) - }; - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - 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; - } -}; - -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/25_sampler.cpp b/attachments/25_sampler.cpp index af435325..ca3f2558 100644 --- a/attachments/25_sampler.cpp +++ b/attachments/25_sampler.cpp @@ -12,14 +12,14 @@ #include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include #define GLM_FORCE_RADIANS @@ -29,13 +29,12 @@ import vulkan_hpp; #define STB_IMAGE_IMPLEMENTATION #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -constexpr int MAX_FRAMES_IN_FLIGHT = 2; +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; +constexpr int MAX_FRAMES_IN_FLIGHT = 2; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -43,884 +42,940 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -struct Vertex { - glm::vec2 pos; - glm::vec3 color; - - static vk::VertexInputBindingDescription getBindingDescription() { - return { 0, sizeof(Vertex), vk::VertexInputRate::eVertex }; - } - - static std::array getAttributeDescriptions() { - return { - vk::VertexInputAttributeDescription( 0, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, pos) ), - vk::VertexInputAttributeDescription( 1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color) ) - }; - } +struct Vertex +{ + glm::vec2 pos; + glm::vec3 color; + + static vk::VertexInputBindingDescription getBindingDescription() + { + return {0, sizeof(Vertex), vk::VertexInputRate::eVertex}; + } + + static std::array getAttributeDescriptions() + { + return { + vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, pos)), + vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color))}; + } }; -struct UniformBufferObject { - alignas(16) glm::mat4 model; - alignas(16) glm::mat4 view; - alignas(16) glm::mat4 proj; +struct UniformBufferObject +{ + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; }; const std::vector vertices = { {{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}}, {{0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}}, {{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}, - {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}} -}; + {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}}}; const std::vector indices = { - 0, 1, 2, 2, 3, 0 + 0, 1, 2, 2, 3, 0}; + +class HelloTriangleApplication +{ + public: + void run() + { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + + private: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::SurfaceFormatKHR swapChainSurfaceFormat; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + + vk::raii::Image textureImage = nullptr; + vk::raii::DeviceMemory textureImageMemory = nullptr; + vk::raii::ImageView textureImageView = nullptr; + vk::raii::Sampler textureSampler = nullptr; + + vk::raii::Buffer vertexBuffer = nullptr; + vk::raii::DeviceMemory vertexBufferMemory = nullptr; + vk::raii::Buffer indexBuffer = nullptr; + vk::raii::DeviceMemory indexBufferMemory = nullptr; + + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + std::vector uniformBuffersMapped; + + vk::raii::DescriptorPool descriptorPool = nullptr; + std::vector descriptorSets; + + vk::raii::CommandPool commandPool = nullptr; + std::vector commandBuffers; + + std::vector presentCompleteSemaphore; + std::vector renderFinishedSemaphore; + std::vector inFlightFences; + uint32_t semaphoreIndex = 0; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow *window, int width, int height) + { + auto app = static_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createDescriptorSetLayout(); + createGraphicsPipeline(); + createCommandPool(); + createTextureImage(); + createTextureImageView(); + createTextureSampler(); + createVertexBuffer(); + createIndexBuffer(); + createUniformBuffers(); + createDescriptorPool(); + createDescriptorSets(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() + { + while (!glfwWindowShouldClose(window)) + { + glfwPollEvents(); + drawFrame(); + } + + device.waitIdle(); + } + + void cleanupSwapChain() + { + swapChainImageViews.clear(); + swapChain = nullptr; + } + + void cleanup() + { + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() + { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) + { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + device.waitIdle(); + + cleanupSwapChain(); + createSwapChain(); + createImageViews(); + } + + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().features.samplerAnisotropy && + features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain featureChain = { + {.features = {.samplerAnisotropy = true}}, // vk::PhysicalDeviceFeatures2 + {.synchronization2 = true, .dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(*surface); + swapChainExtent = chooseSwapExtent(surfaceCapabilities); + swapChainSurfaceFormat = chooseSwapSurfaceFormat(physicalDevice.getSurfaceFormatsKHR(*surface)); + vk::SwapchainCreateInfoKHR swapChainCreateInfo{.surface = *surface, + .minImageCount = chooseSwapMinImageCount(surfaceCapabilities), + .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + void createImageViews() + { + vk::ImageViewCreateInfo imageViewCreateInfo{.viewType = vk::ImageViewType::e2D, .format = swapChainSurfaceFormat.format, .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + for (auto image : swapChainImages) + { + imageViewCreateInfo.image = image; + swapChainImageViews.emplace_back(device, imageViewCreateInfo); + } + } + + void createDescriptorSetLayout() + { + vk::DescriptorSetLayoutBinding uboLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr); + vk::DescriptorSetLayoutCreateInfo layoutInfo{.bindingCount = 1, .pBindings = &uboLayoutBinding}; + descriptorSetLayout = vk::raii::DescriptorSetLayout(device, layoutInfo); + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; + + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{.vertexBindingDescriptionCount = 1, .pVertexBindingDescriptions = &bindingDescription, .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), .pVertexAttributeDescriptions = attributeDescriptions.data()}; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{.topology = vk::PrimitiveTopology::eTriangleList}; + vk::PipelineViewportStateCreateInfo viewportState{.viewportCount = 1, .scissorCount = 1}; + + vk::PipelineRasterizationStateCreateInfo rasterizer{.depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, .frontFace = vk::FrontFace::eCounterClockwise, .depthBiasEnable = vk::False, .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f}; + + vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; + + vk::PipelineColorBlendAttachmentState colorBlendAttachment{.blendEnable = vk::False, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA}; + + vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0}; + + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format}; + vk::GraphicsPipelineCreateInfo pipelineInfo{.pNext = &pipelineRenderingCreateInfo, + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex}; + commandPool = vk::raii::CommandPool(device, poolInfo); + } + + void createTextureImage() + { + int texWidth, texHeight, texChannels; + stbi_uc *pixels = stbi_load("textures/texture.jpg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + vk::DeviceSize imageSize = texWidth * texHeight * 4; + + if (!pixels) + { + throw std::runtime_error("failed to load texture image!"); + } + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, imageSize); + memcpy(data, pixels, imageSize); + stagingBufferMemory.unmapMemory(); + + stbi_image_free(pixels); + + createImage(texWidth, texHeight, vk::Format::eR8G8B8A8Srgb, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory); + + transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); + transitionImageLayout(textureImage, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal); + } + + void createTextureImageView() + { + textureImageView = createImageView(textureImage, vk::Format::eR8G8B8A8Srgb); + } + + void createTextureSampler() + { + vk::PhysicalDeviceProperties properties = physicalDevice.getProperties(); + vk::SamplerCreateInfo samplerInfo{ + .magFilter = vk::Filter::eLinear, + .minFilter = vk::Filter::eLinear, + .mipmapMode = vk::SamplerMipmapMode::eLinear, + .addressModeU = vk::SamplerAddressMode::eRepeat, + .addressModeV = vk::SamplerAddressMode::eRepeat, + .addressModeW = vk::SamplerAddressMode::eRepeat, + .mipLodBias = 0.0f, + .anisotropyEnable = vk::True, + .maxAnisotropy = properties.limits.maxSamplerAnisotropy, + .compareEnable = vk::False, + .compareOp = vk::CompareOp::eAlways}; + textureSampler = vk::raii::Sampler(device, samplerInfo); + } + + vk::raii::ImageView createImageView(vk::raii::Image &image, vk::Format format) + { + vk::ImageViewCreateInfo viewInfo{ + .image = image, + .viewType = vk::ImageViewType::e2D, + .format = format, + .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + return vk::raii::ImageView(device, viewInfo); + } + + void createImage(uint32_t width, uint32_t height, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image &image, vk::raii::DeviceMemory &imageMemory) + { + vk::ImageCreateInfo imageInfo{.imageType = vk::ImageType::e2D, .format = format, .extent = {width, height, 1}, .mipLevels = 1, .arrayLayers = 1, .samples = vk::SampleCountFlagBits::e1, .tiling = tiling, .usage = usage, .sharingMode = vk::SharingMode::eExclusive}; + + image = vk::raii::Image(device, imageInfo); + + vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{.allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + imageMemory = vk::raii::DeviceMemory(device, allocInfo); + image.bindMemory(imageMemory, 0); + } + + void transitionImageLayout(const vk::raii::Image &image, vk::ImageLayout oldLayout, vk::ImageLayout newLayout) + { + auto commandBuffer = beginSingleTimeCommands(); + + vk::ImageMemoryBarrier barrier{.oldLayout = oldLayout, .newLayout = newLayout, .image = image, .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + + vk::PipelineStageFlags sourceStage; + vk::PipelineStageFlags destinationStage; + + if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) + { + barrier.srcAccessMask = {}; + barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; + + sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; + destinationStage = vk::PipelineStageFlagBits::eTransfer; + } + else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) + { + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + + sourceStage = vk::PipelineStageFlagBits::eTransfer; + destinationStage = vk::PipelineStageFlagBits::eFragmentShader; + } + else + { + throw std::invalid_argument("unsupported layout transition!"); + } + commandBuffer->pipelineBarrier(sourceStage, destinationStage, {}, {}, nullptr, barrier); + endSingleTimeCommands(*commandBuffer); + } + + void copyBufferToImage(const vk::raii::Buffer &buffer, vk::raii::Image &image, uint32_t width, uint32_t height) + { + std::unique_ptr commandBuffer = beginSingleTimeCommands(); + vk::BufferImageCopy region{.bufferOffset = 0, .bufferRowLength = 0, .bufferImageHeight = 0, .imageSubresource = {vk::ImageAspectFlagBits::eColor, 0, 0, 1}, .imageOffset = {0, 0, 0}, .imageExtent = {width, height, 1}}; + commandBuffer->copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, {region}); + endSingleTimeCommands(*commandBuffer); + } + + void createVertexBuffer() + { + vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(dataStaging, vertices.data(), bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + } + + void createIndexBuffer() + { + vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(data, indices.data(), bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); + + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + } + + void createUniformBuffers() + { + uniformBuffers.clear(); + uniformBuffersMemory.clear(); + uniformBuffersMapped.clear(); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DeviceSize bufferSize = sizeof(UniformBufferObject); + vk::raii::Buffer buffer({}); + vk::raii::DeviceMemory bufferMem({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); + uniformBuffers.emplace_back(std::move(buffer)); + uniformBuffersMemory.emplace_back(std::move(bufferMem)); + uniformBuffersMapped.emplace_back(uniformBuffersMemory[i].mapMemory(0, bufferSize)); + } + } + + void createDescriptorPool() + { + vk::DescriptorPoolSize poolSize(vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT); + vk::DescriptorPoolCreateInfo poolInfo{.flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, .maxSets = MAX_FRAMES_IN_FLIGHT, .poolSizeCount = 1, .pPoolSizes = &poolSize}; + descriptorPool = vk::raii::DescriptorPool(device, poolInfo); + } + + void createDescriptorSets() + { + std::vector layouts(MAX_FRAMES_IN_FLIGHT, *descriptorSetLayout); + vk::DescriptorSetAllocateInfo allocInfo{.descriptorPool = descriptorPool, .descriptorSetCount = static_cast(layouts.size()), .pSetLayouts = layouts.data()}; + + descriptorSets = device.allocateDescriptorSets(allocInfo); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DescriptorBufferInfo bufferInfo{.buffer = uniformBuffers[i], .offset = 0, .range = sizeof(UniformBufferObject)}; + vk::WriteDescriptorSet descriptorWrite{.dstSet = descriptorSets[i], .dstBinding = 0, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eUniformBuffer, .pBufferInfo = &bufferInfo}; + device.updateDescriptorSets(descriptorWrite, {}); + } + } + + void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer &buffer, vk::raii::DeviceMemory &bufferMemory) + { + vk::BufferCreateInfo bufferInfo{.size = size, .usage = usage, .sharingMode = vk::SharingMode::eExclusive}; + buffer = vk::raii::Buffer(device, bufferInfo); + vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{.allocationSize = memRequirements.size, .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + bufferMemory = vk::raii::DeviceMemory(device, allocInfo); + buffer.bindMemory(bufferMemory, 0); + } + + std::unique_ptr beginSingleTimeCommands() + { + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1}; + std::unique_ptr commandBuffer = std::make_unique(std::move(vk::raii::CommandBuffers(device, allocInfo).front())); + + vk::CommandBufferBeginInfo beginInfo{.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; + commandBuffer->begin(beginInfo); + + return commandBuffer; + } + + void endSingleTimeCommands(vk::raii::CommandBuffer &commandBuffer) + { + commandBuffer.end(); + + vk::SubmitInfo submitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandBuffer}; + queue.submit(submitInfo, nullptr); + queue.waitIdle(); + } + + void copyBuffer(vk::raii::Buffer &srcBuffer, vk::raii::Buffer &dstBuffer, vk::DeviceSize size) + { + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1}; + vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); + commandCopyBuffer.begin(vk::CommandBufferBeginInfo{.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); + commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy{.size = size}); + commandCopyBuffer.end(); + queue.submit(vk::SubmitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer}, nullptr); + queue.waitIdle(); + } + + uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) + { + vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) + { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) + { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + void createCommandBuffers() + { + commandBuffers.clear(); + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = MAX_FRAMES_IN_FLIGHT}; + commandBuffers = vk::raii::CommandBuffers(device, allocInfo); + } + + void recordCommandBuffer(uint32_t imageIndex) + { + commandBuffers[currentFrame].begin({}); + // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL + transition_image_layout( + imageIndex, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage + ); + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::RenderingAttachmentInfo attachmentInfo = { + .imageView = swapChainImageViews[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor}; + vk::RenderingInfo renderingInfo = { + .renderArea = {.offset = {0, 0}, .extent = swapChainExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &attachmentInfo}; + commandBuffers[currentFrame].beginRendering(renderingInfo); + commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint16); + commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); + commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); + commandBuffers[currentFrame].endRendering(); + // After rendering, transition the swapchain image to PRESENT_SRC + transition_image_layout( + imageIndex, + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage + ); + commandBuffers[currentFrame].end(); + } + + void transition_image_layout( + uint32_t imageIndex, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout, + vk::AccessFlags2 src_access_mask, + vk::AccessFlags2 dst_access_mask, + vk::PipelineStageFlags2 src_stage_mask, + vk::PipelineStageFlags2 dst_stage_mask) + { + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = src_stage_mask, + .srcAccessMask = src_access_mask, + .dstStageMask = dst_stage_mask, + .dstAccessMask = dst_access_mask, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo dependency_info = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + } + + void createSyncObjects() + { + presentCompleteSemaphore.clear(); + renderFinishedSemaphore.clear(); + inFlightFences.clear(); + + for (size_t i = 0; i < swapChainImages.size(); i++) + { + presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); + } + } + + void updateUniformBuffer(uint32_t currentImage) + { + static auto startTime = std::chrono::high_resolution_clock::now(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + UniformBufferObject ubo{}; + ubo.model = rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.view = lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); + ubo.proj[1][1] *= -1; + + memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); + } + + void drawFrame() + { + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + ; + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr); + + if (result == vk::Result::eErrorOutOfDateKHR) + { + recreateSwapChain(); + return; + } + if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) + { + throw std::runtime_error("failed to acquire swap chain image!"); + } + updateUniformBuffer(currentFrame); + + device.resetFences(*inFlightFences[currentFrame]); + commandBuffers[currentFrame].reset(); + recordCommandBuffer(imageIndex); + + vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[currentFrame]); + + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + result = queue.presentKHR(presentInfoKHR); + if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) + { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != vk::Result::eSuccess) + { + throw std::runtime_error("failed to present swap chain image!"); + } + semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size(), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const &surfaceCapabilities) + { + auto minImageCount = std::max(3u, surfaceCapabilities.minImageCount); + if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) + { + minImageCount = surfaceCapabilities.maxImageCount; + } + return minImageCount; + } + + static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + assert(!availableFormats.empty()); + const auto formatIt = std::ranges::find_if( + availableFormats, + [](const auto &format) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; }); + return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + assert(std::ranges::any_of(availablePresentModes, [](auto presentMode) { return presentMode == vk::PresentModeKHR::eFifo; })); + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + vk::PresentModeKHR::eFifo; + } + + vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR &capabilities) + { + if (capabilities.currentExtent.width != 0xFFFFFFFF) + { + 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)}; + } + + std::vector getRequiredExtensions() + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + 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: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::SurfaceFormatKHR swapChainSurfaceFormat; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - - vk::raii::Image textureImage = nullptr; - vk::raii::DeviceMemory textureImageMemory = nullptr; - vk::raii::ImageView textureImageView = nullptr; - vk::raii::Sampler textureSampler = nullptr; - - vk::raii::Buffer vertexBuffer = nullptr; - vk::raii::DeviceMemory vertexBufferMemory = nullptr; - vk::raii::Buffer indexBuffer = nullptr; - vk::raii::DeviceMemory indexBufferMemory = nullptr; - - std::vector uniformBuffers; - std::vector uniformBuffersMemory; - std::vector uniformBuffersMapped; - - vk::raii::DescriptorPool descriptorPool = nullptr; - std::vector descriptorSets; - - vk::raii::CommandPool commandPool = nullptr; - std::vector commandBuffers; - - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; - std::vector inFlightFences; - uint32_t semaphoreIndex = 0; - uint32_t currentFrame = 0; - - bool framebufferResized = false; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); - } - - static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { - auto app = static_cast(glfwGetWindowUserPointer(window)); - app->framebufferResized = true; - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - createSurface(); - pickPhysicalDevice(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createDescriptorSetLayout(); - createGraphicsPipeline(); - createCommandPool(); - createTextureImage(); - createTextureImageView(); - createTextureSampler(); - createVertexBuffer(); - createIndexBuffer(); - createUniformBuffers(); - createDescriptorPool(); - createDescriptorSets(); - createCommandBuffers(); - createSyncObjects(); - } - - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - drawFrame(); - } - - device.waitIdle(); - } - - void cleanupSwapChain() { - swapChainImageViews.clear(); - swapChain = nullptr; - } - - void cleanup() { - glfwDestroyWindow(window); - - glfwTerminate(); - } - - void recreateSwapChain() { - int width = 0, height = 0; - glfwGetFramebufferSize(window, &width, &height); - while (width == 0 || height == 0) { - glfwGetFramebufferSize(window, &width, &height); - glfwWaitEvents(); - } - - device.waitIdle(); - - cleanupSwapChain(); - createSwapChain(); - createImageViews(); - } - - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().features.samplerAnisotropy && - features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - } ); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error( "failed to find a suitable GPU!" ); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain featureChain = { - {.features = {.samplerAnisotropy = true } }, // vk::PhysicalDeviceFeatures2 - {.synchronization2 = true, .dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( *surface ); - swapChainExtent = chooseSwapExtent( surfaceCapabilities ); - swapChainSurfaceFormat = chooseSwapSurfaceFormat( physicalDevice.getSurfaceFormatsKHR( *surface ) ); - vk::SwapchainCreateInfoKHR swapChainCreateInfo{ .surface = *surface, - .minImageCount = chooseSwapMinImageCount( surfaceCapabilities ), - .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 }; - - swapChain = vk::raii::SwapchainKHR( device, swapChainCreateInfo ); - swapChainImages = swapChain.getImages(); - } - - void createImageViews() { - vk::ImageViewCreateInfo imageViewCreateInfo{ .viewType = vk::ImageViewType::e2D, .format = swapChainSurfaceFormat.format, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } }; - for ( auto image : swapChainImages ) - { - imageViewCreateInfo.image = image; - swapChainImageViews.emplace_back( device, imageViewCreateInfo ); - } - } - - void createDescriptorSetLayout() { - vk::DescriptorSetLayoutBinding uboLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr); - vk::DescriptorSetLayoutCreateInfo layoutInfo{ .bindingCount = 1, .pBindings = &uboLayoutBinding }; - descriptorSetLayout = vk::raii::DescriptorSetLayout( device, layoutInfo ); - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; - - auto bindingDescription = Vertex::getBindingDescription(); - auto attributeDescriptions = Vertex::getAttributeDescriptions(); - vk::PipelineVertexInputStateCreateInfo vertexInputInfo { .vertexBindingDescriptionCount =1, .pVertexBindingDescriptions = &bindingDescription, - .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), .pVertexAttributeDescriptions = attributeDescriptions.data() }; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ .topology = vk::PrimitiveTopology::eTriangleList }; - vk::PipelineViewportStateCreateInfo viewportState{ .viewportCount = 1, .scissorCount = 1 }; - - vk::PipelineRasterizationStateCreateInfo rasterizer{ .depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eCounterClockwise, .depthBiasEnable = vk::False, - .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f }; - - vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; - - vk::PipelineColorBlendAttachmentState colorBlendAttachment{ .blendEnable = vk::False, - .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA - }; - - vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0 }; - - pipelineLayout = vk::raii::PipelineLayout( device, pipelineLayoutInfo ); - - vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ .colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format }; - vk::GraphicsPipelineCreateInfo pipelineInfo{ .pNext = &pipelineRenderingCreateInfo, - .stageCount = 2, .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, .layout = pipelineLayout, .renderPass = nullptr }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{ .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex }; - commandPool = vk::raii::CommandPool(device, poolInfo); - } - - void createTextureImage() { - int texWidth, texHeight, texChannels; - stbi_uc* pixels = stbi_load("textures/texture.jpg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); - vk::DeviceSize imageSize = texWidth * texHeight * 4; - - if (!pixels) { - throw std::runtime_error("failed to load texture image!"); - } - - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, imageSize); - memcpy(data, pixels, imageSize); - stagingBufferMemory.unmapMemory(); - - stbi_image_free(pixels); - - createImage(texWidth, texHeight, vk::Format::eR8G8B8A8Srgb, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory); - - transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal); - copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); - transitionImageLayout(textureImage, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal); - } - - void createTextureImageView() { - textureImageView = createImageView(textureImage, vk::Format::eR8G8B8A8Srgb); - } - - void createTextureSampler() { - vk::PhysicalDeviceProperties properties = physicalDevice.getProperties(); - vk::SamplerCreateInfo samplerInfo{ - .magFilter = vk::Filter::eLinear, - .minFilter = vk::Filter::eLinear, - .mipmapMode = vk::SamplerMipmapMode::eLinear, - .addressModeU = vk::SamplerAddressMode::eRepeat, - .addressModeV = vk::SamplerAddressMode::eRepeat, - .addressModeW = vk::SamplerAddressMode::eRepeat, - .mipLodBias = 0.0f, - .anisotropyEnable = vk::True, - .maxAnisotropy = properties.limits.maxSamplerAnisotropy, - .compareEnable = vk::False, - .compareOp = vk::CompareOp::eAlways }; - textureSampler = vk::raii::Sampler(device, samplerInfo); - } - - vk::raii::ImageView createImageView(vk::raii::Image& image, vk::Format format) { - vk::ImageViewCreateInfo viewInfo{ - .image = image, - .viewType = vk::ImageViewType::e2D, - .format = format, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } }; - return vk::raii::ImageView(device, viewInfo); - } - - void createImage(uint32_t width, uint32_t height, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image& image, vk::raii::DeviceMemory& imageMemory) { - vk::ImageCreateInfo imageInfo{ .imageType = vk::ImageType::e2D, .format = format, - .extent = {width, height, 1}, .mipLevels = 1, .arrayLayers = 1, - .samples = vk::SampleCountFlagBits::e1, .tiling = tiling, - .usage = usage, .sharingMode = vk::SharingMode::eExclusive }; - - image = vk::raii::Image( device, imageInfo ); - - vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) }; - imageMemory = vk::raii::DeviceMemory( device, allocInfo ); - image.bindMemory(imageMemory, 0); - } - - void transitionImageLayout(const vk::raii::Image& image, vk::ImageLayout oldLayout, vk::ImageLayout newLayout) { - auto commandBuffer = beginSingleTimeCommands(); - - vk::ImageMemoryBarrier barrier{ .oldLayout = oldLayout, .newLayout = newLayout, - .image = image, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } }; - - vk::PipelineStageFlags sourceStage; - vk::PipelineStageFlags destinationStage; - - if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) { - barrier.srcAccessMask = {}; - barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; - - sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; - destinationStage = vk::PipelineStageFlagBits::eTransfer; - } else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) { - barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; - barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - - sourceStage = vk::PipelineStageFlagBits::eTransfer; - destinationStage = vk::PipelineStageFlagBits::eFragmentShader; - } else { - throw std::invalid_argument("unsupported layout transition!"); - } - commandBuffer->pipelineBarrier(sourceStage, destinationStage, {}, {}, nullptr, barrier); - endSingleTimeCommands(*commandBuffer); - } - - void copyBufferToImage(const vk::raii::Buffer& buffer, vk::raii::Image& image, uint32_t width, uint32_t height) { - std::unique_ptr commandBuffer = beginSingleTimeCommands(); - vk::BufferImageCopy region{ .bufferOffset = 0, .bufferRowLength = 0, .bufferImageHeight = 0, - .imageSubresource = { vk::ImageAspectFlagBits::eColor, 0, 0, 1 }, - .imageOffset = {0, 0, 0}, .imageExtent = {width, height, 1} }; - commandBuffer->copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, {region}); - endSingleTimeCommands(*commandBuffer); - } - - void createVertexBuffer() { - vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(dataStaging, vertices.data(), bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); - - copyBuffer(stagingBuffer, vertexBuffer, bufferSize); - } - - void createIndexBuffer() { - vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); - - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(data, indices.data(), bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); - - copyBuffer(stagingBuffer, indexBuffer, bufferSize); - } - - void createUniformBuffers() { - uniformBuffers.clear(); - uniformBuffersMemory.clear(); - uniformBuffersMapped.clear(); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DeviceSize bufferSize = sizeof(UniformBufferObject); - vk::raii::Buffer buffer({}); - vk::raii::DeviceMemory bufferMem({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); - uniformBuffers.emplace_back(std::move(buffer)); - uniformBuffersMemory.emplace_back(std::move(bufferMem)); - uniformBuffersMapped.emplace_back( uniformBuffersMemory[i].mapMemory(0, bufferSize)); - } - } - - void createDescriptorPool() { - vk::DescriptorPoolSize poolSize(vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT); - vk::DescriptorPoolCreateInfo poolInfo{ .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, .maxSets = MAX_FRAMES_IN_FLIGHT, .poolSizeCount = 1, .pPoolSizes = &poolSize }; - descriptorPool = vk::raii::DescriptorPool(device, poolInfo); - } - - void createDescriptorSets() { - std::vector layouts(MAX_FRAMES_IN_FLIGHT, *descriptorSetLayout); - vk::DescriptorSetAllocateInfo allocInfo{ .descriptorPool = descriptorPool, .descriptorSetCount = static_cast(layouts.size()), .pSetLayouts = layouts.data() }; - - descriptorSets = device.allocateDescriptorSets(allocInfo); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DescriptorBufferInfo bufferInfo{ .buffer = uniformBuffers[i], .offset = 0, .range = sizeof(UniformBufferObject) }; - vk::WriteDescriptorSet descriptorWrite{ .dstSet = descriptorSets[i], .dstBinding = 0, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eUniformBuffer, .pBufferInfo = &bufferInfo }; - device.updateDescriptorSets(descriptorWrite, {}); - } - } - - void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer& buffer, vk::raii::DeviceMemory& bufferMemory) { - vk::BufferCreateInfo bufferInfo{ .size = size, .usage = usage, .sharingMode = vk::SharingMode::eExclusive }; - buffer = vk::raii::Buffer(device, bufferInfo); - vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ .allocationSize =memRequirements.size, .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) }; - bufferMemory = vk::raii::DeviceMemory(device, allocInfo); - buffer.bindMemory(bufferMemory, 0); - } - - std::unique_ptr beginSingleTimeCommands() { - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1 }; - std::unique_ptr commandBuffer = std::make_unique(std::move(vk::raii::CommandBuffers(device, allocInfo).front())); - - vk::CommandBufferBeginInfo beginInfo{ .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit }; - commandBuffer->begin(beginInfo); - - return commandBuffer; - } - - void endSingleTimeCommands(vk::raii::CommandBuffer& commandBuffer) { - commandBuffer.end(); - - vk::SubmitInfo submitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandBuffer }; - queue.submit(submitInfo, nullptr); - queue.waitIdle(); - } - - void copyBuffer(vk::raii::Buffer & srcBuffer, vk::raii::Buffer & dstBuffer, vk::DeviceSize size) { - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1 }; - vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); - commandCopyBuffer.begin(vk::CommandBufferBeginInfo{ .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit }); - commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy{ .size = size }); - commandCopyBuffer.end(); - queue.submit(vk::SubmitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer }, nullptr); - queue.waitIdle(); - } - - uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) { - vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); - - for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { - if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { - return i; - } - } - - throw std::runtime_error("failed to find suitable memory type!"); - } - - void createCommandBuffers() { - commandBuffers.clear(); - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = MAX_FRAMES_IN_FLIGHT }; - commandBuffers = vk::raii::CommandBuffers( device, allocInfo ); - } - - void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin( {} ); - // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL - transition_image_layout( - imageIndex, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage - ); - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::RenderingAttachmentInfo attachmentInfo = { - .imageView = swapChainImageViews[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor - }; - vk::RenderingInfo renderingInfo = { - .renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &attachmentInfo - }; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].bindIndexBuffer( *indexBuffer, 0, vk::IndexType::eUint16 ); - commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); - commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); - commandBuffers[currentFrame].endRendering(); - // After rendering, transition the swapchain image to PRESENT_SRC - transition_image_layout( - imageIndex, - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage - ); - commandBuffers[currentFrame].end(); - } - - void transition_image_layout( - uint32_t imageIndex, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::AccessFlags2 src_access_mask, - vk::AccessFlags2 dst_access_mask, - vk::PipelineStageFlags2 src_stage_mask, - vk::PipelineStageFlags2 dst_stage_mask - ) { - vk::ImageMemoryBarrier2 barrier = { - .srcStageMask = src_stage_mask, - .srcAccessMask = src_access_mask, - .dstStageMask = dst_stage_mask, - .dstAccessMask = dst_access_mask, - .oldLayout = old_layout, - .newLayout = new_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); - } - - void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); - - for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - } - - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - inFlightFences.emplace_back(device, vk::FenceCreateInfo { .flags = vk::FenceCreateFlagBits::eSignaled }); - } - } - - void updateUniformBuffer(uint32_t currentImage) { - static auto startTime = std::chrono::high_resolution_clock::now(); - - auto currentTime = std::chrono::high_resolution_clock::now(); - float time = std::chrono::duration(currentTime - startTime).count(); - - UniformBufferObject ubo{}; - ubo.model = rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.view = lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); - ubo.proj[1][1] *= -1; - - memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); - } - - void drawFrame() { - while ( vk::Result::eTimeout == device.waitForFences( *inFlightFences[currentFrame], vk::True, UINT64_MAX ) ) - ; - auto [result, imageIndex] = swapChain.acquireNextImage( UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr ); - - if (result == vk::Result::eErrorOutOfDateKHR) { - recreateSwapChain(); - return; - } - if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) { - throw std::runtime_error("failed to acquire swap chain image!"); - } - updateUniformBuffer(currentFrame); - - device.resetFences( *inFlightFences[currentFrame] ); - commandBuffers[currentFrame].reset(); - recordCommandBuffer(imageIndex); - - vk::PipelineStageFlags waitDestinationStageMask( vk::PipelineStageFlagBits::eColorAttachmentOutput ); - const vk::SubmitInfo submitInfo{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], - .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], - .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex] }; - queue.submit(submitInfo, *inFlightFences[currentFrame]); - - - const vk::PresentInfoKHR presentInfoKHR{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], - .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex }; - result = queue.presentKHR( presentInfoKHR ); - if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { - framebufferResized = false; - recreateSwapChain(); - } else if (result != vk::Result::eSuccess) { - throw std::runtime_error("failed to present swap chain image!"); - } - semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; - } - - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size(), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const & surfaceCapabilities) { - auto minImageCount = std::max( 3u, surfaceCapabilities.minImageCount ); - if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) { - minImageCount = surfaceCapabilities.maxImageCount; - } - return minImageCount; - } - - static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { - assert(!availableFormats.empty()); - const auto formatIt = std::ranges::find_if( - availableFormats, - []( const auto & format ) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; } ); - return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - assert(std::ranges::any_of(availablePresentModes, [](auto presentMode){ return presentMode == vk::PresentModeKHR::eFifo; })); - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eFifo; - } - - vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) { - if (capabilities.currentExtent.width != 0xFFFFFFFF) { - 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) - }; - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - 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; - } -}; - -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/26_texture_mapping.cpp b/attachments/26_texture_mapping.cpp index 16fc5cc2..600a7a6a 100644 --- a/attachments/26_texture_mapping.cpp +++ b/attachments/26_texture_mapping.cpp @@ -12,14 +12,14 @@ #include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include #define GLM_FORCE_RADIANS @@ -29,13 +29,12 @@ import vulkan_hpp; #define STB_IMAGE_IMPLEMENTATION #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -constexpr int MAX_FRAMES_IN_FLIGHT = 2; +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; +constexpr int MAX_FRAMES_IN_FLIGHT = 2; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -43,961 +42,1005 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -struct Vertex { - glm::vec2 pos; - glm::vec3 color; - glm::vec2 texCoord; - - static vk::VertexInputBindingDescription getBindingDescription() { - return { 0, sizeof(Vertex), vk::VertexInputRate::eVertex }; - } - - static std::array getAttributeDescriptions() { - return { - vk::VertexInputAttributeDescription( 0, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, pos) ), - vk::VertexInputAttributeDescription( 1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color) ), - vk::VertexInputAttributeDescription( 2, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, texCoord) ) - }; - } +struct Vertex +{ + glm::vec2 pos; + glm::vec3 color; + glm::vec2 texCoord; + + static vk::VertexInputBindingDescription getBindingDescription() + { + return {0, sizeof(Vertex), vk::VertexInputRate::eVertex}; + } + + static std::array getAttributeDescriptions() + { + return { + vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, pos)), + vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color)), + vk::VertexInputAttributeDescription(2, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, texCoord))}; + } }; -struct UniformBufferObject { - alignas(16) glm::mat4 model; - alignas(16) glm::mat4 view; - alignas(16) glm::mat4 proj; +struct UniformBufferObject +{ + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; }; const std::vector vertices = { {{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}, {1.0f, 0.0f}}, {{0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f}}, {{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}, {0.0f, 1.0f}}, - {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}, {1.0f, 1.0f}} -}; + {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}, {1.0f, 1.0f}}}; const std::vector indices = { - 0, 1, 2, 2, 3, 0 + 0, 1, 2, 2, 3, 0}; + +class HelloTriangleApplication +{ + public: + void run() + { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + + private: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::SurfaceFormatKHR swapChainSurfaceFormat; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + + vk::raii::Image textureImage = nullptr; + vk::raii::DeviceMemory textureImageMemory = nullptr; + vk::raii::ImageView textureImageView = nullptr; + vk::raii::Sampler textureSampler = nullptr; + + vk::raii::Buffer vertexBuffer = nullptr; + vk::raii::DeviceMemory vertexBufferMemory = nullptr; + vk::raii::Buffer indexBuffer = nullptr; + vk::raii::DeviceMemory indexBufferMemory = nullptr; + + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + std::vector uniformBuffersMapped; + + vk::raii::DescriptorPool descriptorPool = nullptr; + std::vector descriptorSets; + + vk::raii::CommandPool commandPool = nullptr; + std::vector commandBuffers; + + std::vector presentCompleteSemaphore; + std::vector renderFinishedSemaphore; + std::vector inFlightFences; + uint32_t semaphoreIndex = 0; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow *window, int width, int height) + { + auto app = static_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createDescriptorSetLayout(); + createGraphicsPipeline(); + createCommandPool(); + createTextureImage(); + createTextureImageView(); + createTextureSampler(); + createVertexBuffer(); + createIndexBuffer(); + createUniformBuffers(); + createDescriptorPool(); + createDescriptorSets(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() + { + while (!glfwWindowShouldClose(window)) + { + glfwPollEvents(); + drawFrame(); + } + + device.waitIdle(); + } + + void cleanupSwapChain() + { + swapChainImageViews.clear(); + swapChain = nullptr; + } + + void cleanup() + { + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() + { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) + { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + device.waitIdle(); + + cleanupSwapChain(); + createSwapChain(); + createImageViews(); + } + + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().features.samplerAnisotropy && + features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain featureChain = { + {.features = {.samplerAnisotropy = true}}, // vk::PhysicalDeviceFeatures2 + {.synchronization2 = true, .dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(*surface); + swapChainExtent = chooseSwapExtent(surfaceCapabilities); + swapChainSurfaceFormat = chooseSwapSurfaceFormat(physicalDevice.getSurfaceFormatsKHR(*surface)); + vk::SwapchainCreateInfoKHR swapChainCreateInfo{.surface = *surface, + .minImageCount = chooseSwapMinImageCount(surfaceCapabilities), + .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + void createImageViews() + { + vk::ImageViewCreateInfo imageViewCreateInfo{.viewType = vk::ImageViewType::e2D, .format = swapChainSurfaceFormat.format, .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + for (auto image : swapChainImages) + { + imageViewCreateInfo.image = image; + swapChainImageViews.emplace_back(device, imageViewCreateInfo); + } + } + + void createDescriptorSetLayout() + { + std::array bindings = { + vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr), + vk::DescriptorSetLayoutBinding(1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment, nullptr)}; + + vk::DescriptorSetLayoutCreateInfo layoutInfo{.bindingCount = static_cast(bindings.size()), .pBindings = bindings.data()}; + descriptorSetLayout = vk::raii::DescriptorSetLayout(device, layoutInfo); + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; + + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{.vertexBindingDescriptionCount = 1, .pVertexBindingDescriptions = &bindingDescription, .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), .pVertexAttributeDescriptions = attributeDescriptions.data()}; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{.topology = vk::PrimitiveTopology::eTriangleList}; + vk::PipelineViewportStateCreateInfo viewportState{.viewportCount = 1, .scissorCount = 1}; + + vk::PipelineRasterizationStateCreateInfo rasterizer{.depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, .frontFace = vk::FrontFace::eCounterClockwise, .depthBiasEnable = vk::False, .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f}; + + vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; + + vk::PipelineColorBlendAttachmentState colorBlendAttachment{.blendEnable = vk::False, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA}; + + vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0}; + + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format}; + vk::GraphicsPipelineCreateInfo pipelineInfo{.pNext = &pipelineRenderingCreateInfo, + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex}; + commandPool = vk::raii::CommandPool(device, poolInfo); + } + + void createTextureImage() + { + int texWidth, texHeight, texChannels; + stbi_uc *pixels = stbi_load("textures/texture.jpg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + vk::DeviceSize imageSize = texWidth * texHeight * 4; + + if (!pixels) + { + throw std::runtime_error("failed to load texture image!"); + } + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, imageSize); + memcpy(data, pixels, imageSize); + stagingBufferMemory.unmapMemory(); + + stbi_image_free(pixels); + + createImage(texWidth, texHeight, vk::Format::eR8G8B8A8Srgb, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory); + + transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); + transitionImageLayout(textureImage, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal); + } + + void createTextureImageView() + { + textureImageView = createImageView(textureImage, vk::Format::eR8G8B8A8Srgb, vk::ImageAspectFlagBits::eColor); + } + + void createTextureSampler() + { + vk::PhysicalDeviceProperties properties = physicalDevice.getProperties(); + vk::SamplerCreateInfo samplerInfo{ + .magFilter = vk::Filter::eLinear, + .minFilter = vk::Filter::eLinear, + .mipmapMode = vk::SamplerMipmapMode::eLinear, + .addressModeU = vk::SamplerAddressMode::eRepeat, + .addressModeV = vk::SamplerAddressMode::eRepeat, + .addressModeW = vk::SamplerAddressMode::eRepeat, + .mipLodBias = 0.0f, + .anisotropyEnable = vk::True, + .maxAnisotropy = properties.limits.maxSamplerAnisotropy, + .compareEnable = vk::False, + .compareOp = vk::CompareOp::eAlways}; + textureSampler = vk::raii::Sampler(device, samplerInfo); + } + + vk::raii::ImageView createImageView(vk::raii::Image &image, vk::Format format, vk::ImageAspectFlags aspectFlags) + { + vk::ImageViewCreateInfo viewInfo{ + .image = image, + .viewType = vk::ImageViewType::e2D, + .format = format, + .subresourceRange = {aspectFlags, 0, 1, 0, 1}}; + return vk::raii::ImageView(device, viewInfo); + } + + void createImage(uint32_t width, uint32_t height, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image &image, vk::raii::DeviceMemory &imageMemory) + { + vk::ImageCreateInfo imageInfo{ + .imageType = vk::ImageType::e2D, + .format = format, + .extent = {width, height, 1}, + .mipLevels = 1, + .arrayLayers = 1, + .samples = vk::SampleCountFlagBits::e1, + .tiling = tiling, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive}; + + image = vk::raii::Image(device, imageInfo); + + vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{ + .allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + imageMemory = vk::raii::DeviceMemory(device, allocInfo); + image.bindMemory(imageMemory, 0); + } + + void transitionImageLayout(const vk::raii::Image &image, vk::ImageLayout oldLayout, vk::ImageLayout newLayout) + { + auto commandBuffer = beginSingleTimeCommands(); + + vk::ImageMemoryBarrier barrier{ + .oldLayout = oldLayout, + .newLayout = newLayout, + .image = image, + .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + + vk::PipelineStageFlags sourceStage; + vk::PipelineStageFlags destinationStage; + + if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) + { + barrier.srcAccessMask = {}; + barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; + + sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; + destinationStage = vk::PipelineStageFlagBits::eTransfer; + } + else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) + { + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + + sourceStage = vk::PipelineStageFlagBits::eTransfer; + destinationStage = vk::PipelineStageFlagBits::eFragmentShader; + } + else + { + throw std::invalid_argument("unsupported layout transition!"); + } + commandBuffer->pipelineBarrier(sourceStage, destinationStage, {}, {}, nullptr, barrier); + endSingleTimeCommands(*commandBuffer); + } + + void copyBufferToImage(const vk::raii::Buffer &buffer, vk::raii::Image &image, uint32_t width, uint32_t height) + { + std::unique_ptr commandBuffer = beginSingleTimeCommands(); + vk::BufferImageCopy region{ + .bufferOffset = 0, + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource = {vk::ImageAspectFlagBits::eColor, 0, 0, 1}, + .imageOffset = {0, 0, 0}, + .imageExtent = {width, height, 1}}; + commandBuffer->copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, {region}); + endSingleTimeCommands(*commandBuffer); + } + + void createVertexBuffer() + { + vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(dataStaging, vertices.data(), bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + } + + void createIndexBuffer() + { + vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(data, indices.data(), bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); + + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + } + + void createUniformBuffers() + { + uniformBuffers.clear(); + uniformBuffersMemory.clear(); + uniformBuffersMapped.clear(); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DeviceSize bufferSize = sizeof(UniformBufferObject); + vk::raii::Buffer buffer({}); + vk::raii::DeviceMemory bufferMem({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); + uniformBuffers.emplace_back(std::move(buffer)); + uniformBuffersMemory.emplace_back(std::move(bufferMem)); + uniformBuffersMapped.emplace_back(uniformBuffersMemory[i].mapMemory(0, bufferSize)); + } + } + + void createDescriptorPool() + { + std::array poolSize{ + vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT), + vk::DescriptorPoolSize(vk::DescriptorType::eCombinedImageSampler, MAX_FRAMES_IN_FLIGHT)}; + vk::DescriptorPoolCreateInfo poolInfo{ + .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, + .maxSets = MAX_FRAMES_IN_FLIGHT, + .poolSizeCount = static_cast(poolSize.size()), + .pPoolSizes = poolSize.data()}; + descriptorPool = vk::raii::DescriptorPool(device, poolInfo); + } + + void createDescriptorSets() + { + std::vector layouts(MAX_FRAMES_IN_FLIGHT, descriptorSetLayout); + vk::DescriptorSetAllocateInfo allocInfo{ + .descriptorPool = descriptorPool, + .descriptorSetCount = static_cast(layouts.size()), + .pSetLayouts = layouts.data()}; + + descriptorSets.clear(); + descriptorSets = device.allocateDescriptorSets(allocInfo); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DescriptorBufferInfo bufferInfo{ + .buffer = uniformBuffers[i], + .offset = 0, + .range = sizeof(UniformBufferObject)}; + vk::DescriptorImageInfo imageInfo{ + .sampler = textureSampler, + .imageView = textureImageView, + .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal}; + std::array descriptorWrites{ + vk::WriteDescriptorSet{ + .dstSet = descriptorSets[i], + .dstBinding = 0, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eUniformBuffer, + .pBufferInfo = &bufferInfo}, + vk::WriteDescriptorSet{ + .dstSet = descriptorSets[i], + .dstBinding = 1, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .pImageInfo = &imageInfo}}; + device.updateDescriptorSets(descriptorWrites, {}); + } + } + + void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer &buffer, vk::raii::DeviceMemory &bufferMemory) + { + vk::BufferCreateInfo bufferInfo{ + .size = size, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive}; + buffer = vk::raii::Buffer(device, bufferInfo); + vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{ + .allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + bufferMemory = vk::raii::DeviceMemory(device, allocInfo); + buffer.bindMemory(bufferMemory, 0); + } + + std::unique_ptr beginSingleTimeCommands() + { + vk::CommandBufferAllocateInfo allocInfo{ + .commandPool = commandPool, + .level = vk::CommandBufferLevel::ePrimary, + .commandBufferCount = 1}; + std::unique_ptr commandBuffer = std::make_unique(std::move(vk::raii::CommandBuffers(device, allocInfo).front())); + + vk::CommandBufferBeginInfo beginInfo{ + .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; + commandBuffer->begin(beginInfo); + + return commandBuffer; + } + + void endSingleTimeCommands(vk::raii::CommandBuffer &commandBuffer) + { + commandBuffer.end(); + + vk::SubmitInfo submitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandBuffer}; + queue.submit(submitInfo, nullptr); + queue.waitIdle(); + } + + void copyBuffer(vk::raii::Buffer &srcBuffer, vk::raii::Buffer &dstBuffer, vk::DeviceSize size) + { + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1}; + vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); + commandCopyBuffer.begin(vk::CommandBufferBeginInfo{.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); + commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy{.size = size}); + commandCopyBuffer.end(); + queue.submit(vk::SubmitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer}, nullptr); + queue.waitIdle(); + } + + uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) + { + vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) + { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) + { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + void createCommandBuffers() + { + commandBuffers.clear(); + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = MAX_FRAMES_IN_FLIGHT}; + commandBuffers = vk::raii::CommandBuffers(device, allocInfo); + } + + void recordCommandBuffer(uint32_t imageIndex) + { + commandBuffers[currentFrame].begin({}); + // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL + transition_image_layout( + imageIndex, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage + ); + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::RenderingAttachmentInfo attachmentInfo = { + .imageView = swapChainImageViews[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor}; + vk::RenderingInfo renderingInfo = { + .renderArea = {.offset = {0, 0}, .extent = swapChainExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &attachmentInfo}; + commandBuffers[currentFrame].beginRendering(renderingInfo); + commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint16); + commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); + commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); + commandBuffers[currentFrame].endRendering(); + // After rendering, transition the swapchain image to PRESENT_SRC + transition_image_layout( + imageIndex, + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage + ); + commandBuffers[currentFrame].end(); + } + + void transition_image_layout( + uint32_t imageIndex, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout, + vk::AccessFlags2 src_access_mask, + vk::AccessFlags2 dst_access_mask, + vk::PipelineStageFlags2 src_stage_mask, + vk::PipelineStageFlags2 dst_stage_mask) + { + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = src_stage_mask, + .srcAccessMask = src_access_mask, + .dstStageMask = dst_stage_mask, + .dstAccessMask = dst_access_mask, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo dependency_info = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + } + + void createSyncObjects() + { + presentCompleteSemaphore.clear(); + renderFinishedSemaphore.clear(); + inFlightFences.clear(); + + for (size_t i = 0; i < swapChainImages.size(); i++) + { + presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); + } + } + + void updateUniformBuffer(uint32_t currentImage) + { + static auto startTime = std::chrono::high_resolution_clock::now(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + UniformBufferObject ubo{}; + ubo.model = rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.view = lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); + ubo.proj[1][1] *= -1; + + memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); + } + + void drawFrame() + { + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + ; + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr); + + if (result == vk::Result::eErrorOutOfDateKHR) + { + recreateSwapChain(); + return; + } + if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) + { + throw std::runtime_error("failed to acquire swap chain image!"); + } + updateUniformBuffer(currentFrame); + + device.resetFences(*inFlightFences[currentFrame]); + commandBuffers[currentFrame].reset(); + recordCommandBuffer(imageIndex); + + vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[currentFrame]); + + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + result = queue.presentKHR(presentInfoKHR); + if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) + { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != vk::Result::eSuccess) + { + throw std::runtime_error("failed to present swap chain image!"); + } + semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size(), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const &surfaceCapabilities) + { + auto minImageCount = std::max(3u, surfaceCapabilities.minImageCount); + if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) + { + minImageCount = surfaceCapabilities.maxImageCount; + } + return minImageCount; + } + + static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + assert(!availableFormats.empty()); + const auto formatIt = std::ranges::find_if( + availableFormats, + [](const auto &format) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; }); + return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + assert(std::ranges::any_of(availablePresentModes, [](auto presentMode) { return presentMode == vk::PresentModeKHR::eFifo; })); + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + vk::PresentModeKHR::eFifo; + } + + vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR &capabilities) + { + if (capabilities.currentExtent.width != 0xFFFFFFFF) + { + 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)}; + } + + std::vector getRequiredExtensions() + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + 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: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::SurfaceFormatKHR swapChainSurfaceFormat; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - - vk::raii::Image textureImage = nullptr; - vk::raii::DeviceMemory textureImageMemory = nullptr; - vk::raii::ImageView textureImageView = nullptr; - vk::raii::Sampler textureSampler = nullptr; - - vk::raii::Buffer vertexBuffer = nullptr; - vk::raii::DeviceMemory vertexBufferMemory = nullptr; - vk::raii::Buffer indexBuffer = nullptr; - vk::raii::DeviceMemory indexBufferMemory = nullptr; - - std::vector uniformBuffers; - std::vector uniformBuffersMemory; - std::vector uniformBuffersMapped; - - vk::raii::DescriptorPool descriptorPool = nullptr; - std::vector descriptorSets; - - vk::raii::CommandPool commandPool = nullptr; - std::vector commandBuffers; - - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; - std::vector inFlightFences; - uint32_t semaphoreIndex = 0; - uint32_t currentFrame = 0; - - bool framebufferResized = false; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); - } - - static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { - auto app = static_cast(glfwGetWindowUserPointer(window)); - app->framebufferResized = true; - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - createSurface(); - pickPhysicalDevice(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createDescriptorSetLayout(); - createGraphicsPipeline(); - createCommandPool(); - createTextureImage(); - createTextureImageView(); - createTextureSampler(); - createVertexBuffer(); - createIndexBuffer(); - createUniformBuffers(); - createDescriptorPool(); - createDescriptorSets(); - createCommandBuffers(); - createSyncObjects(); - } - - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - drawFrame(); - } - - device.waitIdle(); - } - - void cleanupSwapChain() { - swapChainImageViews.clear(); - swapChain = nullptr; - } - - void cleanup() { - glfwDestroyWindow(window); - - glfwTerminate(); - } - - void recreateSwapChain() { - int width = 0, height = 0; - glfwGetFramebufferSize(window, &width, &height); - while (width == 0 || height == 0) { - glfwGetFramebufferSize(window, &width, &height); - glfwWaitEvents(); - } - - device.waitIdle(); - - cleanupSwapChain(); - createSwapChain(); - createImageViews(); - } - - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().features.samplerAnisotropy && - features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - } ); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error( "failed to find a suitable GPU!" ); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain featureChain = { - {.features = {.samplerAnisotropy = true } }, // vk::PhysicalDeviceFeatures2 - {.synchronization2 = true, .dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( *surface ); - swapChainExtent = chooseSwapExtent( surfaceCapabilities ); - swapChainSurfaceFormat = chooseSwapSurfaceFormat( physicalDevice.getSurfaceFormatsKHR( *surface ) ); - vk::SwapchainCreateInfoKHR swapChainCreateInfo{ .surface = *surface, - .minImageCount = chooseSwapMinImageCount( surfaceCapabilities ), - .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 }; - - swapChain = vk::raii::SwapchainKHR( device, swapChainCreateInfo ); - swapChainImages = swapChain.getImages(); - } - - void createImageViews() { - vk::ImageViewCreateInfo imageViewCreateInfo{ .viewType = vk::ImageViewType::e2D, .format = swapChainSurfaceFormat.format, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } }; - for ( auto image : swapChainImages ) - { - imageViewCreateInfo.image = image; - swapChainImageViews.emplace_back( device, imageViewCreateInfo ); - } - } - - void createDescriptorSetLayout() { - std::array bindings = { - vk::DescriptorSetLayoutBinding( 0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr), - vk::DescriptorSetLayoutBinding( 1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment, nullptr) - }; - - vk::DescriptorSetLayoutCreateInfo layoutInfo{ .bindingCount = static_cast(bindings.size()), .pBindings = bindings.data() }; - descriptorSetLayout = vk::raii::DescriptorSetLayout( device, layoutInfo ); - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; - - auto bindingDescription = Vertex::getBindingDescription(); - auto attributeDescriptions = Vertex::getAttributeDescriptions(); - vk::PipelineVertexInputStateCreateInfo vertexInputInfo { .vertexBindingDescriptionCount =1, .pVertexBindingDescriptions = &bindingDescription, - .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), .pVertexAttributeDescriptions = attributeDescriptions.data() }; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ .topology = vk::PrimitiveTopology::eTriangleList }; - vk::PipelineViewportStateCreateInfo viewportState{ .viewportCount = 1, .scissorCount = 1 }; - - vk::PipelineRasterizationStateCreateInfo rasterizer{ .depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eCounterClockwise, .depthBiasEnable = vk::False, - .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f }; - - vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; - - vk::PipelineColorBlendAttachmentState colorBlendAttachment{ .blendEnable = vk::False, - .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA - }; - - vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0 }; - - pipelineLayout = vk::raii::PipelineLayout( device, pipelineLayoutInfo ); - - vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ .colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format }; - vk::GraphicsPipelineCreateInfo pipelineInfo{ .pNext = &pipelineRenderingCreateInfo, - .stageCount = 2, .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, .layout = pipelineLayout, .renderPass = nullptr }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{ .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex }; - commandPool = vk::raii::CommandPool(device, poolInfo); - } - - void createTextureImage() { - int texWidth, texHeight, texChannels; - stbi_uc* pixels = stbi_load("textures/texture.jpg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); - vk::DeviceSize imageSize = texWidth * texHeight * 4; - - if (!pixels) { - throw std::runtime_error("failed to load texture image!"); - } - - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, imageSize); - memcpy(data, pixels, imageSize); - stagingBufferMemory.unmapMemory(); - - stbi_image_free(pixels); - - createImage(texWidth, texHeight, vk::Format::eR8G8B8A8Srgb, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory); - - transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal); - copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); - transitionImageLayout(textureImage, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal); - } - - void createTextureImageView() { - textureImageView = createImageView(textureImage, vk::Format::eR8G8B8A8Srgb, vk::ImageAspectFlagBits::eColor); - } - - void createTextureSampler() { - vk::PhysicalDeviceProperties properties = physicalDevice.getProperties(); - vk::SamplerCreateInfo samplerInfo{ - .magFilter = vk::Filter::eLinear, - .minFilter = vk::Filter::eLinear, - .mipmapMode = vk::SamplerMipmapMode::eLinear, - .addressModeU = vk::SamplerAddressMode::eRepeat, - .addressModeV = vk::SamplerAddressMode::eRepeat, - .addressModeW = vk::SamplerAddressMode::eRepeat, - .mipLodBias = 0.0f, - .anisotropyEnable = vk::True, - .maxAnisotropy = properties.limits.maxSamplerAnisotropy, - .compareEnable = vk::False, - .compareOp = vk::CompareOp::eAlways - }; - textureSampler = vk::raii::Sampler(device, samplerInfo); - } - - vk::raii::ImageView createImageView(vk::raii::Image& image, vk::Format format, vk::ImageAspectFlags aspectFlags) { - vk::ImageViewCreateInfo viewInfo{ - .image = image, - .viewType = vk::ImageViewType::e2D, - .format = format, - .subresourceRange = { aspectFlags, 0, 1, 0, 1 } - }; - return vk::raii::ImageView(device, viewInfo); - } - - void createImage(uint32_t width, uint32_t height, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image& image, vk::raii::DeviceMemory& imageMemory) { - vk::ImageCreateInfo imageInfo{ - .imageType = vk::ImageType::e2D, - .format = format, - .extent = {width, height, 1}, - .mipLevels = 1, - .arrayLayers = 1, - .samples = vk::SampleCountFlagBits::e1, - .tiling = tiling, - .usage = usage, - .sharingMode = vk::SharingMode::eExclusive - }; - - image = vk::raii::Image(device, imageInfo); - - vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ - .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) - }; - imageMemory = vk::raii::DeviceMemory(device, allocInfo); - image.bindMemory(imageMemory, 0); - } - - void transitionImageLayout(const vk::raii::Image& image, vk::ImageLayout oldLayout, vk::ImageLayout newLayout) { - auto commandBuffer = beginSingleTimeCommands(); - - vk::ImageMemoryBarrier barrier{ - .oldLayout = oldLayout, - .newLayout = newLayout, - .image = image, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } - }; - - vk::PipelineStageFlags sourceStage; - vk::PipelineStageFlags destinationStage; - - if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) { - barrier.srcAccessMask = {}; - barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; - - sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; - destinationStage = vk::PipelineStageFlagBits::eTransfer; - } else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) { - barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; - barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - - sourceStage = vk::PipelineStageFlagBits::eTransfer; - destinationStage = vk::PipelineStageFlagBits::eFragmentShader; - } else { - throw std::invalid_argument("unsupported layout transition!"); - } - commandBuffer->pipelineBarrier(sourceStage, destinationStage, {}, {}, nullptr, barrier); - endSingleTimeCommands(*commandBuffer); - } - - void copyBufferToImage(const vk::raii::Buffer& buffer, vk::raii::Image& image, uint32_t width, uint32_t height) { - std::unique_ptr commandBuffer = beginSingleTimeCommands(); - vk::BufferImageCopy region{ - .bufferOffset = 0, - .bufferRowLength = 0, - .bufferImageHeight = 0, - .imageSubresource = { vk::ImageAspectFlagBits::eColor, 0, 0, 1 }, - .imageOffset = {0, 0, 0}, - .imageExtent = {width, height, 1} - }; - commandBuffer->copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, {region}); - endSingleTimeCommands(*commandBuffer); - } - - void createVertexBuffer() { - vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(dataStaging, vertices.data(), bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); - - copyBuffer(stagingBuffer, vertexBuffer, bufferSize); - } - - void createIndexBuffer() { - vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); - - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(data, indices.data(), bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); - - copyBuffer(stagingBuffer, indexBuffer, bufferSize); - } - - void createUniformBuffers() { - uniformBuffers.clear(); - uniformBuffersMemory.clear(); - uniformBuffersMapped.clear(); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DeviceSize bufferSize = sizeof(UniformBufferObject); - vk::raii::Buffer buffer({}); - vk::raii::DeviceMemory bufferMem({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); - uniformBuffers.emplace_back(std::move(buffer)); - uniformBuffersMemory.emplace_back(std::move(bufferMem)); - uniformBuffersMapped.emplace_back( uniformBuffersMemory[i].mapMemory(0, bufferSize)); - } - } - - void createDescriptorPool() { - std::array poolSize { - vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT), - vk::DescriptorPoolSize(vk::DescriptorType::eCombinedImageSampler, MAX_FRAMES_IN_FLIGHT) - }; - vk::DescriptorPoolCreateInfo poolInfo{ - .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, - .maxSets = MAX_FRAMES_IN_FLIGHT, - .poolSizeCount = static_cast(poolSize.size()), - .pPoolSizes = poolSize.data() - }; - descriptorPool = vk::raii::DescriptorPool(device, poolInfo); - } - - void createDescriptorSets() { - std::vector layouts(MAX_FRAMES_IN_FLIGHT, descriptorSetLayout); - vk::DescriptorSetAllocateInfo allocInfo{ - .descriptorPool = descriptorPool, - .descriptorSetCount = static_cast(layouts.size()), - .pSetLayouts = layouts.data() - }; - - descriptorSets.clear(); - descriptorSets = device.allocateDescriptorSets(allocInfo); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DescriptorBufferInfo bufferInfo{ - .buffer = uniformBuffers[i], - .offset = 0, - .range = sizeof(UniformBufferObject) - }; - vk::DescriptorImageInfo imageInfo{ - .sampler = textureSampler, - .imageView = textureImageView, - .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal - }; - std::array descriptorWrites{ - vk::WriteDescriptorSet{ - .dstSet = descriptorSets[i], - .dstBinding = 0, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eUniformBuffer, - .pBufferInfo = &bufferInfo - }, - vk::WriteDescriptorSet{ - .dstSet = descriptorSets[i], - .dstBinding = 1, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eCombinedImageSampler, - .pImageInfo = &imageInfo - } - }; - device.updateDescriptorSets(descriptorWrites, {}); - } - } - - void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer& buffer, vk::raii::DeviceMemory& bufferMemory) { - vk::BufferCreateInfo bufferInfo{ - .size = size, - .usage = usage, - .sharingMode = vk::SharingMode::eExclusive - }; - buffer = vk::raii::Buffer(device, bufferInfo); - vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ - .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) - }; - bufferMemory = vk::raii::DeviceMemory(device, allocInfo); - buffer.bindMemory(bufferMemory, 0); - } - - std::unique_ptr beginSingleTimeCommands() { - vk::CommandBufferAllocateInfo allocInfo{ - .commandPool = commandPool, - .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = 1 - }; - std::unique_ptr commandBuffer = std::make_unique(std::move(vk::raii::CommandBuffers(device, allocInfo).front())); - - vk::CommandBufferBeginInfo beginInfo{ - .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit - }; - commandBuffer->begin(beginInfo); - - return commandBuffer; - } - - void endSingleTimeCommands(vk::raii::CommandBuffer& commandBuffer) { - commandBuffer.end(); - - vk::SubmitInfo submitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandBuffer }; - queue.submit(submitInfo, nullptr); - queue.waitIdle(); - } - - void copyBuffer(vk::raii::Buffer & srcBuffer, vk::raii::Buffer & dstBuffer, vk::DeviceSize size) { - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1 }; - vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); - commandCopyBuffer.begin(vk::CommandBufferBeginInfo{ .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit }); - commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy{ .size = size }); - commandCopyBuffer.end(); - queue.submit(vk::SubmitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer }, nullptr); - queue.waitIdle(); - } - - uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) { - vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); - - for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { - if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { - return i; - } - } - - throw std::runtime_error("failed to find suitable memory type!"); - } - - void createCommandBuffers() { - commandBuffers.clear(); - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = MAX_FRAMES_IN_FLIGHT }; - commandBuffers = vk::raii::CommandBuffers(device, allocInfo); - } - - void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin({}); - // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL - transition_image_layout( - imageIndex, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage - ); - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::RenderingAttachmentInfo attachmentInfo = { - .imageView = swapChainImageViews[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor - }; - vk::RenderingInfo renderingInfo = { - .renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &attachmentInfo - }; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].bindIndexBuffer( *indexBuffer, 0, vk::IndexType::eUint16 ); - commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); - commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); - commandBuffers[currentFrame].endRendering(); - // After rendering, transition the swapchain image to PRESENT_SRC - transition_image_layout( - imageIndex, - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage - ); - commandBuffers[currentFrame].end(); - } - - void transition_image_layout( - uint32_t imageIndex, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::AccessFlags2 src_access_mask, - vk::AccessFlags2 dst_access_mask, - vk::PipelineStageFlags2 src_stage_mask, - vk::PipelineStageFlags2 dst_stage_mask - ) { - vk::ImageMemoryBarrier2 barrier = { - .srcStageMask = src_stage_mask, - .srcAccessMask = src_access_mask, - .dstStageMask = dst_stage_mask, - .dstAccessMask = dst_access_mask, - .oldLayout = old_layout, - .newLayout = new_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); - } - - void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); - - for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - } - - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - inFlightFences.emplace_back(device, vk::FenceCreateInfo{ .flags = vk::FenceCreateFlagBits::eSignaled }); - } - } - - void updateUniformBuffer(uint32_t currentImage) { - static auto startTime = std::chrono::high_resolution_clock::now(); - - auto currentTime = std::chrono::high_resolution_clock::now(); - float time = std::chrono::duration(currentTime - startTime).count(); - - UniformBufferObject ubo{}; - ubo.model = rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.view = lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); - ubo.proj[1][1] *= -1; - - memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); - } - - void drawFrame() { - while ( vk::Result::eTimeout == device.waitForFences( *inFlightFences[currentFrame], vk::True, UINT64_MAX ) ) - ; - auto [result, imageIndex] = swapChain.acquireNextImage( UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr ); - - if (result == vk::Result::eErrorOutOfDateKHR) { - recreateSwapChain(); - return; - } - if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) { - throw std::runtime_error("failed to acquire swap chain image!"); - } - updateUniformBuffer(currentFrame); - - device.resetFences( *inFlightFences[currentFrame] ); - commandBuffers[currentFrame].reset(); - recordCommandBuffer(imageIndex); - - vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); - const vk::SubmitInfo submitInfo{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], - .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], - .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex] }; - queue.submit(submitInfo, *inFlightFences[currentFrame]); - - - const vk::PresentInfoKHR presentInfoKHR{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], - .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex }; - result = queue.presentKHR(presentInfoKHR); - if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { - framebufferResized = false; - recreateSwapChain(); - } else if (result != vk::Result::eSuccess) { - throw std::runtime_error("failed to present swap chain image!"); - } - semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; - } - - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size(), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const & surfaceCapabilities) { - auto minImageCount = std::max( 3u, surfaceCapabilities.minImageCount ); - if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) { - minImageCount = surfaceCapabilities.maxImageCount; - } - return minImageCount; - } - - static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { - assert(!availableFormats.empty()); - const auto formatIt = std::ranges::find_if( - availableFormats, - []( const auto & format ) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; } ); - return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - assert(std::ranges::any_of(availablePresentModes, [](auto presentMode){ return presentMode == vk::PresentModeKHR::eFifo; })); - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eFifo; - } - - vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) { - if (capabilities.currentExtent.width != 0xFFFFFFFF) { - 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) - }; - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - 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; - } -}; - -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/27_depth_buffering.cpp b/attachments/27_depth_buffering.cpp index 248345df..7850f114 100644 --- a/attachments/27_depth_buffering.cpp +++ b/attachments/27_depth_buffering.cpp @@ -12,14 +12,14 @@ #include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include #define GLM_FORCE_RADIANS @@ -30,13 +30,12 @@ import vulkan_hpp; #define STB_IMAGE_IMPLEMENTATION #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -constexpr int MAX_FRAMES_IN_FLIGHT = 2; +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; +constexpr int MAX_FRAMES_IN_FLIGHT = 2; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -44,28 +43,31 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -struct Vertex { - glm::vec3 pos; - glm::vec3 color; - glm::vec2 texCoord; - - static vk::VertexInputBindingDescription getBindingDescription() { - return { 0, sizeof(Vertex), vk::VertexInputRate::eVertex }; - } - - static std::array getAttributeDescriptions() { - return { - vk::VertexInputAttributeDescription( 0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, pos) ), - vk::VertexInputAttributeDescription( 1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color) ), - vk::VertexInputAttributeDescription( 2, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, texCoord) ) - }; - } +struct Vertex +{ + glm::vec3 pos; + glm::vec3 color; + glm::vec2 texCoord; + + static vk::VertexInputBindingDescription getBindingDescription() + { + return {0, sizeof(Vertex), vk::VertexInputRate::eVertex}; + } + + static std::array getAttributeDescriptions() + { + return { + vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, pos)), + vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color)), + vk::VertexInputAttributeDescription(2, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, texCoord))}; + } }; -struct UniformBufferObject { - alignas(16) glm::mat4 model; - alignas(16) glm::mat4 view; - alignas(16) glm::mat4 proj; +struct UniformBufferObject +{ + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; }; const std::vector vertices = { @@ -77,1055 +79,1084 @@ const std::vector vertices = { {{-0.5f, -0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}, {1.0f, 0.0f}}, {{0.5f, -0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f}}, {{0.5f, 0.5f, -0.5f}, {0.0f, 0.0f, 1.0f}, {0.0f, 1.0f}}, - {{-0.5f, 0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}, {1.0f, 1.0f}} -}; + {{-0.5f, 0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}, {1.0f, 1.0f}}}; const std::vector indices = { 0, 1, 2, 2, 3, 0, - 4, 5, 6, 6, 7, 4 + 4, 5, 6, 6, 7, 4}; + +class HelloTriangleApplication +{ + public: + void run() + { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + + private: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::SurfaceFormatKHR swapChainSurfaceFormat; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + + vk::raii::Image depthImage = nullptr; + vk::raii::DeviceMemory depthImageMemory = nullptr; + vk::raii::ImageView depthImageView = nullptr; + + vk::raii::Image textureImage = nullptr; + vk::raii::DeviceMemory textureImageMemory = nullptr; + vk::raii::ImageView textureImageView = nullptr; + vk::raii::Sampler textureSampler = nullptr; + + vk::raii::Buffer vertexBuffer = nullptr; + vk::raii::DeviceMemory vertexBufferMemory = nullptr; + vk::raii::Buffer indexBuffer = nullptr; + vk::raii::DeviceMemory indexBufferMemory = nullptr; + + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + std::vector uniformBuffersMapped; + + vk::raii::DescriptorPool descriptorPool = nullptr; + std::vector descriptorSets; + + vk::raii::CommandPool commandPool = nullptr; + std::vector commandBuffers; + + std::vector presentCompleteSemaphore; + std::vector renderFinishedSemaphore; + std::vector inFlightFences; + uint32_t semaphoreIndex = 0; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow *window, int width, int height) + { + auto app = static_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createDescriptorSetLayout(); + createGraphicsPipeline(); + createCommandPool(); + createDepthResources(); + createTextureImage(); + createTextureImageView(); + createTextureSampler(); + createVertexBuffer(); + createIndexBuffer(); + createUniformBuffers(); + createDescriptorPool(); + createDescriptorSets(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() + { + while (!glfwWindowShouldClose(window)) + { + glfwPollEvents(); + drawFrame(); + } + + device.waitIdle(); + } + + void cleanupSwapChain() + { + swapChainImageViews.clear(); + swapChain = nullptr; + } + + void cleanup() const + { + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() + { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) + { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + device.waitIdle(); + + cleanupSwapChain(); + createSwapChain(); + createImageViews(); + createDepthResources(); + } + + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().features.samplerAnisotropy && + features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain featureChain = { + {.features = {.samplerAnisotropy = true}}, // vk::PhysicalDeviceFeatures2 + {.synchronization2 = true, .dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(*surface); + swapChainExtent = chooseSwapExtent(surfaceCapabilities); + swapChainSurfaceFormat = chooseSwapSurfaceFormat(physicalDevice.getSurfaceFormatsKHR(*surface)); + vk::SwapchainCreateInfoKHR swapChainCreateInfo{.surface = *surface, + .minImageCount = chooseSwapMinImageCount(surfaceCapabilities), + .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + void createImageViews() + { + vk::ImageViewCreateInfo imageViewCreateInfo{ + .viewType = vk::ImageViewType::e2D, + .format = swapChainSurfaceFormat.format, + .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + for (auto image : swapChainImages) + { + imageViewCreateInfo.image = image; + swapChainImageViews.emplace_back(device, imageViewCreateInfo); + } + } + + void createDescriptorSetLayout() + { + std::array bindings = { + vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr), + vk::DescriptorSetLayoutBinding(1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment, nullptr)}; + + vk::DescriptorSetLayoutCreateInfo layoutInfo{.bindingCount = static_cast(bindings.size()), .pBindings = bindings.data()}; + descriptorSetLayout = vk::raii::DescriptorSetLayout(device, layoutInfo); + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; + + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{ + .vertexBindingDescriptionCount = 1, + .pVertexBindingDescriptions = &bindingDescription, + .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), + .pVertexAttributeDescriptions = attributeDescriptions.data()}; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ + .topology = vk::PrimitiveTopology::eTriangleList, + .primitiveRestartEnable = vk::False}; + vk::PipelineViewportStateCreateInfo viewportState{ + .viewportCount = 1, + .scissorCount = 1}; + vk::PipelineRasterizationStateCreateInfo rasterizer{ + .depthClampEnable = vk::False, + .rasterizerDiscardEnable = vk::False, + .polygonMode = vk::PolygonMode::eFill, + .cullMode = vk::CullModeFlagBits::eBack, + .frontFace = vk::FrontFace::eCounterClockwise, + .depthBiasEnable = vk::False}; + rasterizer.lineWidth = 1.0f; + vk::PipelineMultisampleStateCreateInfo multisampling{ + .rasterizationSamples = vk::SampleCountFlagBits::e1, + .sampleShadingEnable = vk::False}; + vk::PipelineDepthStencilStateCreateInfo depthStencil{ + .depthTestEnable = vk::True, + .depthWriteEnable = vk::True, + .depthCompareOp = vk::CompareOp::eLess, + .depthBoundsTestEnable = vk::False, + .stencilTestEnable = vk::False}; + vk::PipelineColorBlendAttachmentState colorBlendAttachment; + colorBlendAttachment.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA; + colorBlendAttachment.blendEnable = vk::False; + + vk::PipelineColorBlendStateCreateInfo colorBlending{ + .logicOpEnable = vk::False, + .logicOp = vk::LogicOp::eCopy, + .attachmentCount = 1, + .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0}; + + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::Format depthFormat = findDepthFormat(); + vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ + .colorAttachmentCount = 1, + .pColorAttachmentFormats = &swapChainSurfaceFormat.format, + .depthAttachmentFormat = depthFormat}; + vk::GraphicsPipelineCreateInfo pipelineInfo{.pNext = &pipelineRenderingCreateInfo, + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pDepthStencilState = &depthStencil, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{ + .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex}; + commandPool = vk::raii::CommandPool(device, poolInfo); + } + + void createDepthResources() + { + vk::Format depthFormat = findDepthFormat(); + + createImage(swapChainExtent.width, swapChainExtent.height, depthFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eDepthStencilAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, depthImage, depthImageMemory); + depthImageView = createImageView(depthImage, depthFormat, vk::ImageAspectFlagBits::eDepth); + } + + vk::Format findSupportedFormat(const std::vector &candidates, vk::ImageTiling tiling, vk::FormatFeatureFlags features) + { + auto formatIt = std::ranges::find_if(candidates, [&](auto const format) { + vk::FormatProperties props = physicalDevice.getFormatProperties(format); + return (((tiling == vk::ImageTiling::eLinear) && ((props.linearTilingFeatures & features) == features)) || + ((tiling == vk::ImageTiling::eOptimal) && ((props.optimalTilingFeatures & features) == features))); + }); + if (formatIt == candidates.end()) + { + throw std::runtime_error("failed to find supported format!"); + } + return *formatIt; + } + + vk::Format findDepthFormat() + { + return findSupportedFormat( + {vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, vk::Format::eD24UnormS8Uint}, + vk::ImageTiling::eOptimal, + vk::FormatFeatureFlagBits::eDepthStencilAttachment); + } + + bool hasStencilComponent(vk::Format format) + { + return format == vk::Format::eD32SfloatS8Uint || format == vk::Format::eD24UnormS8Uint; + } + + void createTextureImage() + { + int texWidth, texHeight, texChannels; + stbi_uc *pixels = stbi_load("textures/texture.jpg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + vk::DeviceSize imageSize = texWidth * texHeight * 4; + + if (!pixels) + { + throw std::runtime_error("failed to load texture image!"); + } + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, imageSize); + memcpy(data, pixels, imageSize); + stagingBufferMemory.unmapMemory(); + + stbi_image_free(pixels); + + createImage(texWidth, texHeight, vk::Format::eR8G8B8A8Srgb, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory); + + transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); + transitionImageLayout(textureImage, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal); + } + + void createTextureImageView() + { + textureImageView = createImageView(textureImage, vk::Format::eR8G8B8A8Srgb, vk::ImageAspectFlagBits::eColor); + } + + void createTextureSampler() + { + vk::PhysicalDeviceProperties properties = physicalDevice.getProperties(); + vk::SamplerCreateInfo samplerInfo{ + .magFilter = vk::Filter::eLinear, + .minFilter = vk::Filter::eLinear, + .mipmapMode = vk::SamplerMipmapMode::eLinear, + .addressModeU = vk::SamplerAddressMode::eRepeat, + .addressModeV = vk::SamplerAddressMode::eRepeat, + .addressModeW = vk::SamplerAddressMode::eRepeat, + .mipLodBias = 0.0f, + .anisotropyEnable = vk::True, + .maxAnisotropy = properties.limits.maxSamplerAnisotropy, + .compareEnable = vk::False, + .compareOp = vk::CompareOp::eAlways}; + textureSampler = vk::raii::Sampler(device, samplerInfo); + } + + vk::raii::ImageView createImageView(vk::raii::Image &image, vk::Format format, vk::ImageAspectFlags aspectFlags) + { + vk::ImageViewCreateInfo viewInfo{ + .image = image, + .viewType = vk::ImageViewType::e2D, + .format = format, + .subresourceRange = {aspectFlags, 0, 1, 0, 1}}; + return vk::raii::ImageView(device, viewInfo); + } + + void createImage(uint32_t width, uint32_t height, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image &image, vk::raii::DeviceMemory &imageMemory) + { + vk::ImageCreateInfo imageInfo{ + .imageType = vk::ImageType::e2D, + .format = format, + .extent = {width, height, 1}, + .mipLevels = 1, + .arrayLayers = 1, + .samples = vk::SampleCountFlagBits::e1, + .tiling = tiling, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive, + .initialLayout = vk::ImageLayout::eUndefined}; + image = vk::raii::Image(device, imageInfo); + + vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{ + .allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + imageMemory = vk::raii::DeviceMemory(device, allocInfo); + image.bindMemory(imageMemory, 0); + } + + void transitionImageLayout(const vk::raii::Image &image, vk::ImageLayout oldLayout, vk::ImageLayout newLayout) + { + auto commandBuffer = beginSingleTimeCommands(); + + vk::ImageMemoryBarrier barrier{ + .oldLayout = oldLayout, + .newLayout = newLayout, + .image = image, + .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + + vk::PipelineStageFlags sourceStage; + vk::PipelineStageFlags destinationStage; + + if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) + { + barrier.srcAccessMask = {}; + barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; + + sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; + destinationStage = vk::PipelineStageFlagBits::eTransfer; + } + else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) + { + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + + sourceStage = vk::PipelineStageFlagBits::eTransfer; + destinationStage = vk::PipelineStageFlagBits::eFragmentShader; + } + else + { + throw std::invalid_argument("unsupported layout transition!"); + } + commandBuffer->pipelineBarrier(sourceStage, destinationStage, {}, {}, nullptr, barrier); + endSingleTimeCommands(*commandBuffer); + } + + void copyBufferToImage(const vk::raii::Buffer &buffer, vk::raii::Image &image, uint32_t width, uint32_t height) + { + std::unique_ptr commandBuffer = beginSingleTimeCommands(); + vk::BufferImageCopy region{ + .bufferOffset = 0, + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource = {vk::ImageAspectFlagBits::eColor, 0, 0, 1}, + .imageOffset = {0, 0, 0}, + .imageExtent = {width, height, 1}}; + commandBuffer->copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, {region}); + endSingleTimeCommands(*commandBuffer); + } + + void createVertexBuffer() + { + vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(dataStaging, vertices.data(), bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + } + + void createIndexBuffer() + { + vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(data, indices.data(), bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); + + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + } + + void createUniformBuffers() + { + uniformBuffers.clear(); + uniformBuffersMemory.clear(); + uniformBuffersMapped.clear(); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DeviceSize bufferSize = sizeof(UniformBufferObject); + vk::raii::Buffer buffer({}); + vk::raii::DeviceMemory bufferMem({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); + uniformBuffers.emplace_back(std::move(buffer)); + uniformBuffersMemory.emplace_back(std::move(bufferMem)); + uniformBuffersMapped.emplace_back(uniformBuffersMemory[i].mapMemory(0, bufferSize)); + } + } + + void createDescriptorPool() + { + std::array poolSize{ + vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT), + vk::DescriptorPoolSize(vk::DescriptorType::eCombinedImageSampler, MAX_FRAMES_IN_FLIGHT)}; + vk::DescriptorPoolCreateInfo poolInfo{ + .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, + .maxSets = MAX_FRAMES_IN_FLIGHT, + .poolSizeCount = static_cast(poolSize.size()), + .pPoolSizes = poolSize.data()}; + descriptorPool = vk::raii::DescriptorPool(device, poolInfo); + } + + void createDescriptorSets() + { + std::vector layouts(MAX_FRAMES_IN_FLIGHT, descriptorSetLayout); + vk::DescriptorSetAllocateInfo allocInfo{ + .descriptorPool = descriptorPool, + .descriptorSetCount = static_cast(layouts.size()), + .pSetLayouts = layouts.data()}; + + descriptorSets.clear(); + descriptorSets = device.allocateDescriptorSets(allocInfo); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DescriptorBufferInfo bufferInfo{ + .buffer = uniformBuffers[i], + .offset = 0, + .range = sizeof(UniformBufferObject)}; + vk::DescriptorImageInfo imageInfo{ + .sampler = textureSampler, + .imageView = textureImageView, + .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal}; + std::array descriptorWrites{ + vk::WriteDescriptorSet{ + .dstSet = descriptorSets[i], + .dstBinding = 0, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eUniformBuffer, + .pBufferInfo = &bufferInfo}, + vk::WriteDescriptorSet{ + .dstSet = descriptorSets[i], + .dstBinding = 1, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .pImageInfo = &imageInfo}}; + device.updateDescriptorSets(descriptorWrites, {}); + } + } + + void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer &buffer, vk::raii::DeviceMemory &bufferMemory) + { + vk::BufferCreateInfo bufferInfo{ + .size = size, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive}; + buffer = vk::raii::Buffer(device, bufferInfo); + vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{ + .allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + bufferMemory = vk::raii::DeviceMemory(device, allocInfo); + buffer.bindMemory(bufferMemory, 0); + } + + std::unique_ptr beginSingleTimeCommands() + { + vk::CommandBufferAllocateInfo allocInfo{ + .commandPool = commandPool, + .level = vk::CommandBufferLevel::ePrimary, + .commandBufferCount = 1}; + std::unique_ptr commandBuffer = std::make_unique(std::move(vk::raii::CommandBuffers(device, allocInfo).front())); + + vk::CommandBufferBeginInfo beginInfo{ + .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; + commandBuffer->begin(beginInfo); + + return commandBuffer; + } + + void endSingleTimeCommands(vk::raii::CommandBuffer &commandBuffer) + { + commandBuffer.end(); + + vk::SubmitInfo submitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandBuffer}; + queue.submit(submitInfo, nullptr); + queue.waitIdle(); + } + + void copyBuffer(vk::raii::Buffer &srcBuffer, vk::raii::Buffer &dstBuffer, vk::DeviceSize size) + { + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1}; + vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); + commandCopyBuffer.begin(vk::CommandBufferBeginInfo{.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); + commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy{.size = size}); + commandCopyBuffer.end(); + queue.submit(vk::SubmitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer}, nullptr); + queue.waitIdle(); + } + + uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) + { + vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) + { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) + { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + void createCommandBuffers() + { + commandBuffers.clear(); + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = MAX_FRAMES_IN_FLIGHT}; + commandBuffers = vk::raii::CommandBuffers(device, allocInfo); + } + + void recordCommandBuffer(uint32_t imageIndex) + { + commandBuffers[currentFrame].begin({}); + // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL + transition_image_layout( + imageIndex, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage + ); + // Transition depth image to depth attachment optimal layout + vk::ImageMemoryBarrier2 depthBarrier = { + .srcStageMask = vk::PipelineStageFlagBits2::eTopOfPipe, + .srcAccessMask = {}, + .dstStageMask = vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, + .dstAccessMask = vk::AccessFlagBits2::eDepthStencilAttachmentRead | vk::AccessFlagBits2::eDepthStencilAttachmentWrite, + .oldLayout = vk::ImageLayout::eUndefined, + .newLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = depthImage, + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eDepth, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo depthDependencyInfo = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &depthBarrier}; + commandBuffers[currentFrame].pipelineBarrier2(depthDependencyInfo); + + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::ClearValue clearDepth = vk::ClearDepthStencilValue(1.0f, 0); + + vk::RenderingAttachmentInfo colorAttachmentInfo = { + .imageView = swapChainImageViews[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor}; + + vk::RenderingAttachmentInfo depthAttachmentInfo = { + .imageView = depthImageView, + .imageLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eDontCare, + .clearValue = clearDepth}; + + vk::RenderingInfo renderingInfo = { + .renderArea = {.offset = {0, 0}, .extent = swapChainExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &colorAttachmentInfo, + .pDepthAttachment = &depthAttachmentInfo}; + commandBuffers[currentFrame].beginRendering(renderingInfo); + commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint16); + commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); + commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); + commandBuffers[currentFrame].endRendering(); + // After rendering, transition the swapchain image to PRESENT_SRC + transition_image_layout( + imageIndex, + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage + ); + commandBuffers[currentFrame].end(); + } + + void transition_image_layout( + uint32_t imageIndex, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout, + vk::AccessFlags2 src_access_mask, + vk::AccessFlags2 dst_access_mask, + vk::PipelineStageFlags2 src_stage_mask, + vk::PipelineStageFlags2 dst_stage_mask) + { + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = src_stage_mask, + .srcAccessMask = src_access_mask, + .dstStageMask = dst_stage_mask, + .dstAccessMask = dst_access_mask, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo dependency_info = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + } + + void createSyncObjects() + { + presentCompleteSemaphore.clear(); + renderFinishedSemaphore.clear(); + inFlightFences.clear(); + + for (size_t i = 0; i < swapChainImages.size(); i++) + { + presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); + } + } + + void updateUniformBuffer(uint32_t currentImage) + { + static auto startTime = std::chrono::high_resolution_clock::now(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + UniformBufferObject ubo{}; + ubo.model = rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.view = lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); + ubo.proj[1][1] *= -1; + + memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); + } + + void drawFrame() + { + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + ; + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr); + + if (result == vk::Result::eErrorOutOfDateKHR) + { + recreateSwapChain(); + return; + } + if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) + { + throw std::runtime_error("failed to acquire swap chain image!"); + } + updateUniformBuffer(currentFrame); + + device.resetFences(*inFlightFences[currentFrame]); + commandBuffers[currentFrame].reset(); + recordCommandBuffer(imageIndex); + + vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[currentFrame]); + + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + result = queue.presentKHR(presentInfoKHR); + if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) + { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != vk::Result::eSuccess) + { + throw std::runtime_error("failed to present swap chain image!"); + } + semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size(), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const &surfaceCapabilities) + { + auto minImageCount = std::max(3u, surfaceCapabilities.minImageCount); + if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) + { + minImageCount = surfaceCapabilities.maxImageCount; + } + return minImageCount; + } + + static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + assert(!availableFormats.empty()); + const auto formatIt = std::ranges::find_if( + availableFormats, + [](const auto &format) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; }); + return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + assert(std::ranges::any_of(availablePresentModes, [](auto presentMode) { return presentMode == vk::PresentModeKHR::eFifo; })); + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + vk::PresentModeKHR::eFifo; + } + + vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR &capabilities) + { + if (capabilities.currentExtent.width != 0xFFFFFFFF) + { + 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)}; + } + + std::vector getRequiredExtensions() + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + 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: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::SurfaceFormatKHR swapChainSurfaceFormat; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - - vk::raii::Image depthImage = nullptr; - vk::raii::DeviceMemory depthImageMemory = nullptr; - vk::raii::ImageView depthImageView = nullptr; - - vk::raii::Image textureImage = nullptr; - vk::raii::DeviceMemory textureImageMemory = nullptr; - vk::raii::ImageView textureImageView = nullptr; - vk::raii::Sampler textureSampler = nullptr; - - vk::raii::Buffer vertexBuffer = nullptr; - vk::raii::DeviceMemory vertexBufferMemory = nullptr; - vk::raii::Buffer indexBuffer = nullptr; - vk::raii::DeviceMemory indexBufferMemory = nullptr; - - std::vector uniformBuffers; - std::vector uniformBuffersMemory; - std::vector uniformBuffersMapped; - - vk::raii::DescriptorPool descriptorPool = nullptr; - std::vector descriptorSets; - - vk::raii::CommandPool commandPool = nullptr; - std::vector commandBuffers; - - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; - std::vector inFlightFences; - uint32_t semaphoreIndex = 0; - uint32_t currentFrame = 0; - - bool framebufferResized = false; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); - } - - static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { - auto app = static_cast(glfwGetWindowUserPointer(window)); - app->framebufferResized = true; - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - createSurface(); - pickPhysicalDevice(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createDescriptorSetLayout(); - createGraphicsPipeline(); - createCommandPool(); - createDepthResources(); - createTextureImage(); - createTextureImageView(); - createTextureSampler(); - createVertexBuffer(); - createIndexBuffer(); - createUniformBuffers(); - createDescriptorPool(); - createDescriptorSets(); - createCommandBuffers(); - createSyncObjects(); - } - - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - drawFrame(); - } - - device.waitIdle(); - } - - void cleanupSwapChain() { - swapChainImageViews.clear(); - swapChain = nullptr; - } - - void cleanup() const { - glfwDestroyWindow(window); - - glfwTerminate(); - } - - void recreateSwapChain() { - int width = 0, height = 0; - glfwGetFramebufferSize(window, &width, &height); - while (width == 0 || height == 0) { - glfwGetFramebufferSize(window, &width, &height); - glfwWaitEvents(); - } - - device.waitIdle(); - - cleanupSwapChain(); - createSwapChain(); - createImageViews(); - createDepthResources(); - } - - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().features.samplerAnisotropy && - features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - } ); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error( "failed to find a suitable GPU!" ); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain featureChain = { - {.features = {.samplerAnisotropy = true } }, // vk::PhysicalDeviceFeatures2 - {.synchronization2 = true, .dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( *surface ); - swapChainExtent = chooseSwapExtent( surfaceCapabilities ); - swapChainSurfaceFormat = chooseSwapSurfaceFormat( physicalDevice.getSurfaceFormatsKHR( *surface ) ); - vk::SwapchainCreateInfoKHR swapChainCreateInfo{ .surface = *surface, - .minImageCount = chooseSwapMinImageCount( surfaceCapabilities ), - .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 }; - - swapChain = vk::raii::SwapchainKHR( device, swapChainCreateInfo ); - swapChainImages = swapChain.getImages(); - } - - void createImageViews() { - vk::ImageViewCreateInfo imageViewCreateInfo{ - .viewType = vk::ImageViewType::e2D, - .format = swapChainSurfaceFormat.format, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } - }; - for ( auto image : swapChainImages ) - { - imageViewCreateInfo.image = image; - swapChainImageViews.emplace_back( device, imageViewCreateInfo ); - } - } - - void createDescriptorSetLayout() { - std::array bindings = { - vk::DescriptorSetLayoutBinding( 0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr), - vk::DescriptorSetLayoutBinding( 1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment, nullptr) - }; - - vk::DescriptorSetLayoutCreateInfo layoutInfo{ .bindingCount = static_cast(bindings.size()), .pBindings = bindings.data() }; - descriptorSetLayout = vk::raii::DescriptorSetLayout( device, layoutInfo ); - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; - - auto bindingDescription = Vertex::getBindingDescription(); - auto attributeDescriptions = Vertex::getAttributeDescriptions(); - vk::PipelineVertexInputStateCreateInfo vertexInputInfo{ - .vertexBindingDescriptionCount = 1, - .pVertexBindingDescriptions = &bindingDescription, - .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), - .pVertexAttributeDescriptions = attributeDescriptions.data() - }; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ - .topology = vk::PrimitiveTopology::eTriangleList, - .primitiveRestartEnable = vk::False - }; - vk::PipelineViewportStateCreateInfo viewportState{ - .viewportCount = 1, - .scissorCount = 1 - }; - vk::PipelineRasterizationStateCreateInfo rasterizer{ - .depthClampEnable = vk::False, - .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, - .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eCounterClockwise, - .depthBiasEnable = vk::False - }; - rasterizer.lineWidth = 1.0f; - vk::PipelineMultisampleStateCreateInfo multisampling{ - .rasterizationSamples = vk::SampleCountFlagBits::e1, - .sampleShadingEnable = vk::False - }; - vk::PipelineDepthStencilStateCreateInfo depthStencil{ - .depthTestEnable = vk::True, - .depthWriteEnable = vk::True, - .depthCompareOp = vk::CompareOp::eLess, - .depthBoundsTestEnable = vk::False, - .stencilTestEnable = vk::False - }; - vk::PipelineColorBlendAttachmentState colorBlendAttachment; - colorBlendAttachment.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA; - colorBlendAttachment.blendEnable = vk::False; - - vk::PipelineColorBlendStateCreateInfo colorBlending{ - .logicOpEnable = vk::False, - .logicOp = vk::LogicOp::eCopy, - .attachmentCount = 1, - .pAttachments = &colorBlendAttachment - }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0 }; - - pipelineLayout = vk::raii::PipelineLayout( device, pipelineLayoutInfo ); - - vk::Format depthFormat = findDepthFormat(); - vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ - .colorAttachmentCount = 1, - .pColorAttachmentFormats = &swapChainSurfaceFormat.format, - .depthAttachmentFormat = depthFormat - }; - vk::GraphicsPipelineCreateInfo pipelineInfo{ .pNext = &pipelineRenderingCreateInfo, - .stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pDepthStencilState = &depthStencil, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = pipelineLayout, - .renderPass = nullptr - }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{ - .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex - }; - commandPool = vk::raii::CommandPool(device, poolInfo); - } - - void createDepthResources() { - vk::Format depthFormat = findDepthFormat(); - - createImage(swapChainExtent.width, swapChainExtent.height, depthFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eDepthStencilAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, depthImage, depthImageMemory); - depthImageView = createImageView(depthImage, depthFormat, vk::ImageAspectFlagBits::eDepth); - } - - vk::Format findSupportedFormat(const std::vector& candidates, vk::ImageTiling tiling, vk::FormatFeatureFlags features) { - auto formatIt = std::ranges::find_if(candidates, [&](auto const format){ - vk::FormatProperties props = physicalDevice.getFormatProperties(format); - return (((tiling == vk::ImageTiling::eLinear) && ((props.linearTilingFeatures & features) == features)) || - ((tiling == vk::ImageTiling::eOptimal) && ((props.optimalTilingFeatures & features) == features))); - }); - if ( formatIt == candidates.end()) - { - throw std::runtime_error("failed to find supported format!"); - } - return *formatIt; - } - - vk::Format findDepthFormat() { - return findSupportedFormat( - {vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, vk::Format::eD24UnormS8Uint}, - vk::ImageTiling::eOptimal, - vk::FormatFeatureFlagBits::eDepthStencilAttachment - ); - } - - bool hasStencilComponent(vk::Format format) { - return format == vk::Format::eD32SfloatS8Uint || format == vk::Format::eD24UnormS8Uint; - } - - void createTextureImage() { - int texWidth, texHeight, texChannels; - stbi_uc* pixels = stbi_load("textures/texture.jpg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); - vk::DeviceSize imageSize = texWidth * texHeight * 4; - - if (!pixels) { - throw std::runtime_error("failed to load texture image!"); - } - - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, imageSize); - memcpy(data, pixels, imageSize); - stagingBufferMemory.unmapMemory(); - - stbi_image_free(pixels); - - createImage(texWidth, texHeight, vk::Format::eR8G8B8A8Srgb, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory); - - transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal); - copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); - transitionImageLayout(textureImage, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal); - } - - void createTextureImageView() { - textureImageView = createImageView(textureImage, vk::Format::eR8G8B8A8Srgb, vk::ImageAspectFlagBits::eColor); - } - - void createTextureSampler() { - vk::PhysicalDeviceProperties properties = physicalDevice.getProperties(); - vk::SamplerCreateInfo samplerInfo{ - .magFilter = vk::Filter::eLinear, - .minFilter = vk::Filter::eLinear, - .mipmapMode = vk::SamplerMipmapMode::eLinear, - .addressModeU = vk::SamplerAddressMode::eRepeat, - .addressModeV = vk::SamplerAddressMode::eRepeat, - .addressModeW = vk::SamplerAddressMode::eRepeat, - .mipLodBias = 0.0f, - .anisotropyEnable = vk::True, - .maxAnisotropy = properties.limits.maxSamplerAnisotropy, - .compareEnable = vk::False, - .compareOp = vk::CompareOp::eAlways - }; - textureSampler = vk::raii::Sampler(device, samplerInfo); - } - - vk::raii::ImageView createImageView(vk::raii::Image& image, vk::Format format, vk::ImageAspectFlags aspectFlags) { - vk::ImageViewCreateInfo viewInfo{ - .image = image, - .viewType = vk::ImageViewType::e2D, - .format = format, - .subresourceRange = { aspectFlags, 0, 1, 0, 1 } - }; - return vk::raii::ImageView(device, viewInfo); - } - - void createImage(uint32_t width, uint32_t height, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image& image, vk::raii::DeviceMemory& imageMemory) { - vk::ImageCreateInfo imageInfo{ - .imageType = vk::ImageType::e2D, - .format = format, - .extent = {width, height, 1}, - .mipLevels = 1, - .arrayLayers = 1, - .samples = vk::SampleCountFlagBits::e1, - .tiling = tiling, - .usage = usage, - .sharingMode = vk::SharingMode::eExclusive, - .initialLayout = vk::ImageLayout::eUndefined - }; - image = vk::raii::Image(device, imageInfo); - - vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ - .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) - }; - imageMemory = vk::raii::DeviceMemory(device, allocInfo); - image.bindMemory(imageMemory, 0); - } - - void transitionImageLayout(const vk::raii::Image& image, vk::ImageLayout oldLayout, vk::ImageLayout newLayout) { - auto commandBuffer = beginSingleTimeCommands(); - - vk::ImageMemoryBarrier barrier{ - .oldLayout = oldLayout, - .newLayout = newLayout, - .image = image, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } - }; - - vk::PipelineStageFlags sourceStage; - vk::PipelineStageFlags destinationStage; - - if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) { - barrier.srcAccessMask = {}; - barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; - - sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; - destinationStage = vk::PipelineStageFlagBits::eTransfer; - } else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) { - barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; - barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - - sourceStage = vk::PipelineStageFlagBits::eTransfer; - destinationStage = vk::PipelineStageFlagBits::eFragmentShader; - } else { - throw std::invalid_argument("unsupported layout transition!"); - } - commandBuffer->pipelineBarrier( sourceStage, destinationStage, {}, {}, nullptr, barrier ); - endSingleTimeCommands(*commandBuffer); - } - - void copyBufferToImage(const vk::raii::Buffer& buffer, vk::raii::Image& image, uint32_t width, uint32_t height) { - std::unique_ptr commandBuffer = beginSingleTimeCommands(); - vk::BufferImageCopy region{ - .bufferOffset = 0, - .bufferRowLength = 0, - .bufferImageHeight = 0, - .imageSubresource = { vk::ImageAspectFlagBits::eColor, 0, 0, 1 }, - .imageOffset = {0, 0, 0}, - .imageExtent = {width, height, 1} - }; - commandBuffer->copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, {region}); - endSingleTimeCommands(*commandBuffer); - } - - void createVertexBuffer() { - vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(dataStaging, vertices.data(), bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); - - copyBuffer(stagingBuffer, vertexBuffer, bufferSize); - } - - void createIndexBuffer() { - vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); - - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(data, indices.data(), bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); - - copyBuffer(stagingBuffer, indexBuffer, bufferSize); - } - - void createUniformBuffers() { - uniformBuffers.clear(); - uniformBuffersMemory.clear(); - uniformBuffersMapped.clear(); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DeviceSize bufferSize = sizeof(UniformBufferObject); - vk::raii::Buffer buffer({}); - vk::raii::DeviceMemory bufferMem({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); - uniformBuffers.emplace_back(std::move(buffer)); - uniformBuffersMemory.emplace_back(std::move(bufferMem)); - uniformBuffersMapped.emplace_back( uniformBuffersMemory[i].mapMemory(0, bufferSize)); - } - } - - void createDescriptorPool() { - std::array poolSize { - vk::DescriptorPoolSize( vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT), - vk::DescriptorPoolSize( vk::DescriptorType::eCombinedImageSampler, MAX_FRAMES_IN_FLIGHT) - }; - vk::DescriptorPoolCreateInfo poolInfo{ - .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, - .maxSets = MAX_FRAMES_IN_FLIGHT, - .poolSizeCount = static_cast(poolSize.size()), - .pPoolSizes = poolSize.data() - }; - descriptorPool = vk::raii::DescriptorPool(device, poolInfo); - } - - void createDescriptorSets() { - std::vector layouts(MAX_FRAMES_IN_FLIGHT, descriptorSetLayout); - vk::DescriptorSetAllocateInfo allocInfo{ - .descriptorPool = descriptorPool, - .descriptorSetCount = static_cast(layouts.size()), - .pSetLayouts = layouts.data() - }; - - descriptorSets.clear(); - descriptorSets = device.allocateDescriptorSets(allocInfo); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DescriptorBufferInfo bufferInfo{ - .buffer = uniformBuffers[i], - .offset = 0, - .range = sizeof(UniformBufferObject) - }; - vk::DescriptorImageInfo imageInfo{ - .sampler = textureSampler, - .imageView = textureImageView, - .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal - }; - std::array descriptorWrites{ - vk::WriteDescriptorSet{ - .dstSet = descriptorSets[i], - .dstBinding = 0, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eUniformBuffer, - .pBufferInfo = &bufferInfo - }, - vk::WriteDescriptorSet{ - .dstSet = descriptorSets[i], - .dstBinding = 1, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eCombinedImageSampler, - .pImageInfo = &imageInfo - } - }; - device.updateDescriptorSets(descriptorWrites, {}); - } - } - - void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer& buffer, vk::raii::DeviceMemory& bufferMemory) { - vk::BufferCreateInfo bufferInfo{ - .size = size, - .usage = usage, - .sharingMode = vk::SharingMode::eExclusive - }; - buffer = vk::raii::Buffer(device, bufferInfo); - vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ - .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) - }; - bufferMemory = vk::raii::DeviceMemory(device, allocInfo); - buffer.bindMemory(bufferMemory, 0); - } - - std::unique_ptr beginSingleTimeCommands() { - vk::CommandBufferAllocateInfo allocInfo{ - .commandPool = commandPool, - .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = 1 - }; - std::unique_ptr commandBuffer = std::make_unique(std::move(vk::raii::CommandBuffers(device, allocInfo).front())); - - vk::CommandBufferBeginInfo beginInfo{ - .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit - }; - commandBuffer->begin(beginInfo); - - return commandBuffer; - } - - void endSingleTimeCommands(vk::raii::CommandBuffer& commandBuffer) { - commandBuffer.end(); - - vk::SubmitInfo submitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandBuffer }; - queue.submit(submitInfo, nullptr); - queue.waitIdle(); - } - - void copyBuffer(vk::raii::Buffer & srcBuffer, vk::raii::Buffer & dstBuffer, vk::DeviceSize size) { - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1 }; - vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); - commandCopyBuffer.begin(vk::CommandBufferBeginInfo{ .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit }); - commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy{ .size = size }); - commandCopyBuffer.end(); - queue.submit(vk::SubmitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer }, nullptr); - queue.waitIdle(); - } - - uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) { - vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); - - for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { - if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { - return i; - } - } - - throw std::runtime_error("failed to find suitable memory type!"); - } - - void createCommandBuffers() { - commandBuffers.clear(); - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = MAX_FRAMES_IN_FLIGHT }; - commandBuffers = vk::raii::CommandBuffers(device, allocInfo); - } - - void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin({}); - // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL - transition_image_layout( - imageIndex, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage - ); - // Transition depth image to depth attachment optimal layout - vk::ImageMemoryBarrier2 depthBarrier = { - .srcStageMask = vk::PipelineStageFlagBits2::eTopOfPipe, - .srcAccessMask = {}, - .dstStageMask = vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, - .dstAccessMask = vk::AccessFlagBits2::eDepthStencilAttachmentRead | vk::AccessFlagBits2::eDepthStencilAttachmentWrite, - .oldLayout = vk::ImageLayout::eUndefined, - .newLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = depthImage, - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eDepth, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo depthDependencyInfo = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &depthBarrier - }; - commandBuffers[currentFrame].pipelineBarrier2(depthDependencyInfo); - - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::ClearValue clearDepth = vk::ClearDepthStencilValue(1.0f, 0); - - vk::RenderingAttachmentInfo colorAttachmentInfo = { - .imageView = swapChainImageViews[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor - }; - - vk::RenderingAttachmentInfo depthAttachmentInfo = { - .imageView = depthImageView, - .imageLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eDontCare, - .clearValue = clearDepth - }; - - vk::RenderingInfo renderingInfo = { - .renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &colorAttachmentInfo, - .pDepthAttachment = &depthAttachmentInfo - }; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].bindIndexBuffer( *indexBuffer, 0, vk::IndexType::eUint16 ); - commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); - commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); - commandBuffers[currentFrame].endRendering(); - // After rendering, transition the swapchain image to PRESENT_SRC - transition_image_layout( - imageIndex, - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage - ); - commandBuffers[currentFrame].end(); - } - - void transition_image_layout( - uint32_t imageIndex, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::AccessFlags2 src_access_mask, - vk::AccessFlags2 dst_access_mask, - vk::PipelineStageFlags2 src_stage_mask, - vk::PipelineStageFlags2 dst_stage_mask - ) { - vk::ImageMemoryBarrier2 barrier = { - .srcStageMask = src_stage_mask, - .srcAccessMask = src_access_mask, - .dstStageMask = dst_stage_mask, - .dstAccessMask = dst_access_mask, - .oldLayout = old_layout, - .newLayout = new_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); - } - - void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); - - for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - } - - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - inFlightFences.emplace_back(device, vk::FenceCreateInfo{ .flags = vk::FenceCreateFlagBits::eSignaled }); - } - } - - void updateUniformBuffer(uint32_t currentImage) { - static auto startTime = std::chrono::high_resolution_clock::now(); - - auto currentTime = std::chrono::high_resolution_clock::now(); - float time = std::chrono::duration(currentTime - startTime).count(); - - UniformBufferObject ubo{}; - ubo.model = rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.view = lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); - ubo.proj[1][1] *= -1; - - memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); - } - - void drawFrame() { - while ( vk::Result::eTimeout == device.waitForFences( *inFlightFences[currentFrame], vk::True, UINT64_MAX ) ) - ; - auto [result, imageIndex] = swapChain.acquireNextImage( UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr ); - - if (result == vk::Result::eErrorOutOfDateKHR) { - recreateSwapChain(); - return; - } - if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) { - throw std::runtime_error("failed to acquire swap chain image!"); - } - updateUniformBuffer(currentFrame); - - device.resetFences( *inFlightFences[currentFrame] ); - commandBuffers[currentFrame].reset(); - recordCommandBuffer(imageIndex); - - vk::PipelineStageFlags waitDestinationStageMask( vk::PipelineStageFlagBits::eColorAttachmentOutput ); - const vk::SubmitInfo submitInfo{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], - .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], - .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex] }; - queue.submit(submitInfo, *inFlightFences[currentFrame]); - - - const vk::PresentInfoKHR presentInfoKHR{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], - .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex }; - result = queue.presentKHR(presentInfoKHR); - if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { - framebufferResized = false; - recreateSwapChain(); - } else if (result != vk::Result::eSuccess) { - throw std::runtime_error("failed to present swap chain image!"); - } - semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; - } - - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size(), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const & surfaceCapabilities) { - auto minImageCount = std::max( 3u, surfaceCapabilities.minImageCount ); - if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) { - minImageCount = surfaceCapabilities.maxImageCount; - } - return minImageCount; - } - - static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { - assert(!availableFormats.empty()); - const auto formatIt = std::ranges::find_if( - availableFormats, - []( const auto & format ) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; } ); - return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - assert(std::ranges::any_of(availablePresentModes, [](auto presentMode){ return presentMode == vk::PresentModeKHR::eFifo; })); - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eFifo; - } - - vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) { - if (capabilities.currentExtent.width != 0xFFFFFFFF) { - 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) - }; - } - - std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - 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; - } -}; - -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/28_model_loading.cpp b/attachments/28_model_loading.cpp index 791a6539..af4d5211 100644 --- a/attachments/28_model_loading.cpp +++ b/attachments/28_model_loading.cpp @@ -12,14 +12,14 @@ #include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include #define GLM_FORCE_RADIANS @@ -35,16 +35,15 @@ import vulkan_hpp; #define TINYOBJLOADER_IMPLEMENTATION #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -constexpr uint64_t FenceTimeout = 100000000; -const std::string MODEL_PATH = "models/viking_room.obj"; -const std::string TEXTURE_PATH = "textures/viking_room.png"; -constexpr int MAX_FRAMES_IN_FLIGHT = 2; +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; +constexpr uint64_t FenceTimeout = 100000000; +const std::string MODEL_PATH = "models/viking_room.obj"; +const std::string TEXTURE_PATH = "textures/viking_room.png"; +constexpr int MAX_FRAMES_IN_FLIGHT = 2; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -52,1125 +51,1169 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -struct Vertex { - glm::vec3 pos; - glm::vec3 color; - glm::vec2 texCoord; - - static vk::VertexInputBindingDescription getBindingDescription() { - return { 0, sizeof(Vertex), vk::VertexInputRate::eVertex }; - } - - static std::array getAttributeDescriptions() { - return { - vk::VertexInputAttributeDescription( 0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, pos) ), - vk::VertexInputAttributeDescription( 1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color) ), - vk::VertexInputAttributeDescription( 2, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, texCoord) ) - }; - } - - bool operator==(const Vertex& other) const { - return pos == other.pos && color == other.color && texCoord == other.texCoord; - } +struct Vertex +{ + glm::vec3 pos; + glm::vec3 color; + glm::vec2 texCoord; + + static vk::VertexInputBindingDescription getBindingDescription() + { + return {0, sizeof(Vertex), vk::VertexInputRate::eVertex}; + } + + static std::array getAttributeDescriptions() + { + return { + vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, pos)), + vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color)), + vk::VertexInputAttributeDescription(2, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, texCoord))}; + } + + bool operator==(const Vertex &other) const + { + return pos == other.pos && color == other.color && texCoord == other.texCoord; + } }; -template<> struct std::hash { - size_t operator()(Vertex const& vertex) const noexcept { - return ((hash()(vertex.pos) ^ (hash()(vertex.color) << 1)) >> 1) ^ (hash()(vertex.texCoord) << 1); - } +template <> +struct std::hash +{ + size_t operator()(Vertex const &vertex) const noexcept + { + return ((hash()(vertex.pos) ^ (hash()(vertex.color) << 1)) >> 1) ^ (hash()(vertex.texCoord) << 1); + } }; -struct UniformBufferObject { - alignas(16) glm::mat4 model; - alignas(16) glm::mat4 view; - alignas(16) glm::mat4 proj; +struct UniformBufferObject +{ + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; }; -class HelloTriangleApplication { -public: - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } - -private: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::SurfaceFormatKHR swapChainSurfaceFormat; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - - vk::raii::Image depthImage = nullptr; - vk::raii::DeviceMemory depthImageMemory = nullptr; - vk::raii::ImageView depthImageView = nullptr; - - vk::raii::Image textureImage = nullptr; - vk::raii::DeviceMemory textureImageMemory = nullptr; - vk::raii::ImageView textureImageView = nullptr; - vk::raii::Sampler textureSampler = nullptr; - - std::vector vertices; - std::vector indices; - vk::raii::Buffer vertexBuffer = nullptr; - vk::raii::DeviceMemory vertexBufferMemory = nullptr; - vk::raii::Buffer indexBuffer = nullptr; - vk::raii::DeviceMemory indexBufferMemory = nullptr; - - std::vector uniformBuffers; - std::vector uniformBuffersMemory; - std::vector uniformBuffersMapped; - - vk::raii::DescriptorPool descriptorPool = nullptr; - std::vector descriptorSets; - - vk::raii::CommandPool commandPool = nullptr; - std::vector commandBuffers; - - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; - std::vector inFlightFences; - uint32_t semaphoreIndex = 0; - uint32_t currentFrame = 0; - - bool framebufferResized = false; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); - } - - static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { - auto app = static_cast(glfwGetWindowUserPointer(window)); - app->framebufferResized = true; - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - createSurface(); - pickPhysicalDevice(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createDescriptorSetLayout(); - createGraphicsPipeline(); - createCommandPool(); - createDepthResources(); - createTextureImage(); - createTextureImageView(); - createTextureSampler(); - loadModel(); - createVertexBuffer(); - createIndexBuffer(); - createUniformBuffers(); - createDescriptorPool(); - createDescriptorSets(); - createCommandBuffers(); - createSyncObjects(); - } - - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - drawFrame(); - } - - device.waitIdle(); - } - - void cleanupSwapChain() { - swapChainImageViews.clear(); - swapChain = nullptr; - } - - void cleanup() const { - glfwDestroyWindow(window); - - glfwTerminate(); - } - - void recreateSwapChain() { - int width = 0, height = 0; - glfwGetFramebufferSize(window, &width, &height); - while (width == 0 || height == 0) { - glfwGetFramebufferSize(window, &width, &height); - glfwWaitEvents(); - } - - device.waitIdle(); - - cleanupSwapChain(); - createSwapChain(); - createImageViews(); - createDepthResources(); - } - - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().features.samplerAnisotropy && - features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - } ); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error( "failed to find a suitable GPU!" ); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain featureChain = { - {.features = {.samplerAnisotropy = true } }, // vk::PhysicalDeviceFeatures2 - {.synchronization2 = true, .dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( *surface ); - swapChainExtent = chooseSwapExtent( surfaceCapabilities ); - swapChainSurfaceFormat = chooseSwapSurfaceFormat( physicalDevice.getSurfaceFormatsKHR( *surface ) ); - vk::SwapchainCreateInfoKHR swapChainCreateInfo{ .surface = *surface, - .minImageCount = chooseSwapMinImageCount( surfaceCapabilities ), - .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 }; - - swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); - swapChainImages = swapChain.getImages(); - } - - void createImageViews() { - vk::ImageViewCreateInfo imageViewCreateInfo{ - .viewType = vk::ImageViewType::e2D, - .format = swapChainSurfaceFormat.format, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } - }; - for ( auto image : swapChainImages ) - { - imageViewCreateInfo.image = image; - swapChainImageViews.emplace_back( device, imageViewCreateInfo ); - } - } - - void createDescriptorSetLayout() { - std::array bindings = { - vk::DescriptorSetLayoutBinding( 0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr), - vk::DescriptorSetLayoutBinding( 1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment, nullptr) - }; - - vk::DescriptorSetLayoutCreateInfo layoutInfo{ .bindingCount = static_cast(bindings.size()), .pBindings = bindings.data() }; - descriptorSetLayout = vk::raii::DescriptorSetLayout(device, layoutInfo); - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; - - auto bindingDescription = Vertex::getBindingDescription(); - auto attributeDescriptions = Vertex::getAttributeDescriptions(); - vk::PipelineVertexInputStateCreateInfo vertexInputInfo{ - .vertexBindingDescriptionCount = 1, - .pVertexBindingDescriptions = &bindingDescription, - .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), - .pVertexAttributeDescriptions = attributeDescriptions.data() - }; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ - .topology = vk::PrimitiveTopology::eTriangleList, - .primitiveRestartEnable = vk::False - }; - vk::PipelineViewportStateCreateInfo viewportState{ - .viewportCount = 1, - .scissorCount = 1 - }; - vk::PipelineRasterizationStateCreateInfo rasterizer{ - .depthClampEnable = vk::False, - .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, - .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eCounterClockwise, - .depthBiasEnable = vk::False - }; - rasterizer.lineWidth = 1.0f; - vk::PipelineMultisampleStateCreateInfo multisampling{ - .rasterizationSamples = vk::SampleCountFlagBits::e1, - .sampleShadingEnable = vk::False - }; - vk::PipelineDepthStencilStateCreateInfo depthStencil{ - .depthTestEnable = vk::True, - .depthWriteEnable = vk::True, - .depthCompareOp = vk::CompareOp::eLess, - .depthBoundsTestEnable = vk::False, - .stencilTestEnable = vk::False - }; - vk::PipelineColorBlendAttachmentState colorBlendAttachment; - colorBlendAttachment.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA; - colorBlendAttachment.blendEnable = vk::False; - - vk::PipelineColorBlendStateCreateInfo colorBlending{ - .logicOpEnable = vk::False, - .logicOp = vk::LogicOp::eCopy, - .attachmentCount = 1, - .pAttachments = &colorBlendAttachment - }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0 }; - - pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); - - vk::Format depthFormat = findDepthFormat(); - vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ - .colorAttachmentCount = 1, - .pColorAttachmentFormats = &swapChainSurfaceFormat.format, - .depthAttachmentFormat = depthFormat - }; - vk::GraphicsPipelineCreateInfo pipelineInfo{ .pNext = &pipelineRenderingCreateInfo, - .stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pDepthStencilState = &depthStencil, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = pipelineLayout, - .renderPass = nullptr - }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{ - .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex - }; - commandPool = vk::raii::CommandPool(device, poolInfo); - } - - void createDepthResources() { - vk::Format depthFormat = findDepthFormat(); - - createImage(swapChainExtent.width, swapChainExtent.height, depthFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eDepthStencilAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, depthImage, depthImageMemory); - depthImageView = createImageView(depthImage, depthFormat, vk::ImageAspectFlagBits::eDepth); - } - - vk::Format findSupportedFormat(const std::vector& candidates, vk::ImageTiling tiling, vk::FormatFeatureFlags features) const { - for (const auto format : candidates) { - vk::FormatProperties props = physicalDevice.getFormatProperties(format); - - if (tiling == vk::ImageTiling::eLinear && (props.linearTilingFeatures & features) == features) { - return format; - } - if (tiling == vk::ImageTiling::eOptimal && (props.optimalTilingFeatures & features) == features) { - return format; - } - } - - throw std::runtime_error("failed to find supported format!"); - } - - [[nodiscard]] vk::Format findDepthFormat() const { - return findSupportedFormat( - {vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, vk::Format::eD24UnormS8Uint}, - vk::ImageTiling::eOptimal, - vk::FormatFeatureFlagBits::eDepthStencilAttachment - ); - } - - static bool hasStencilComponent(vk::Format format) { - return format == vk::Format::eD32SfloatS8Uint || format == vk::Format::eD24UnormS8Uint; - } - - void createTextureImage() { - int texWidth, texHeight, texChannels; - stbi_uc* pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); - vk::DeviceSize imageSize = texWidth * texHeight * 4; - - if (!pixels) { - throw std::runtime_error("failed to load texture image!"); - } - - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, imageSize); - memcpy(data, pixels, imageSize); - stagingBufferMemory.unmapMemory(); - - stbi_image_free(pixels); - - createImage(texWidth, texHeight, vk::Format::eR8G8B8A8Srgb, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory); - - transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal); - copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); - transitionImageLayout(textureImage, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal); - } - - void createTextureImageView() { - textureImageView = createImageView(textureImage, vk::Format::eR8G8B8A8Srgb, vk::ImageAspectFlagBits::eColor); - } - - void createTextureSampler() { - vk::PhysicalDeviceProperties properties = physicalDevice.getProperties(); - vk::SamplerCreateInfo samplerInfo{ - .magFilter = vk::Filter::eLinear, - .minFilter = vk::Filter::eLinear, - .mipmapMode = vk::SamplerMipmapMode::eLinear, - .addressModeU = vk::SamplerAddressMode::eRepeat, - .addressModeV = vk::SamplerAddressMode::eRepeat, - .addressModeW = vk::SamplerAddressMode::eRepeat, - .mipLodBias = 0.0f, - .anisotropyEnable = vk::True, - .maxAnisotropy = properties.limits.maxSamplerAnisotropy, - .compareEnable = vk::False, - .compareOp = vk::CompareOp::eAlways - }; - textureSampler = vk::raii::Sampler(device, samplerInfo); - } - - vk::raii::ImageView createImageView(vk::raii::Image& image, vk::Format format, vk::ImageAspectFlags aspectFlags) { - vk::ImageViewCreateInfo viewInfo{ - .image = image, - .viewType = vk::ImageViewType::e2D, - .format = format, - .subresourceRange = { aspectFlags, 0, 1, 0, 1 } - }; - return vk::raii::ImageView(device, viewInfo); - } - - void createImage(uint32_t width, uint32_t height, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image& image, vk::raii::DeviceMemory& imageMemory) { - vk::ImageCreateInfo imageInfo{ - .imageType = vk::ImageType::e2D, - .format = format, - .extent = {width, height, 1}, - .mipLevels = 1, - .arrayLayers = 1, - .samples = vk::SampleCountFlagBits::e1, - .tiling = tiling, - .usage = usage, - .sharingMode = vk::SharingMode::eExclusive, - .initialLayout = vk::ImageLayout::eUndefined - }; - image = vk::raii::Image(device, imageInfo); - - vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ - .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) - }; - imageMemory = vk::raii::DeviceMemory(device, allocInfo); - image.bindMemory(imageMemory, 0); - } - - void transitionImageLayout(const vk::raii::Image& image, vk::ImageLayout oldLayout, vk::ImageLayout newLayout) { - auto commandBuffer = beginSingleTimeCommands(); - - vk::ImageMemoryBarrier barrier{ - .oldLayout = oldLayout, - .newLayout = newLayout, - .image = image, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } - }; - - vk::PipelineStageFlags sourceStage; - vk::PipelineStageFlags destinationStage; - - if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) { - barrier.srcAccessMask = {}; - barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; - - sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; - destinationStage = vk::PipelineStageFlagBits::eTransfer; - } else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) { - barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; - barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - - sourceStage = vk::PipelineStageFlagBits::eTransfer; - destinationStage = vk::PipelineStageFlagBits::eFragmentShader; - } else { - throw std::invalid_argument("unsupported layout transition!"); - } - commandBuffer->pipelineBarrier( sourceStage, destinationStage, {}, {}, nullptr, barrier ); - endSingleTimeCommands(*commandBuffer); - } - - void copyBufferToImage(const vk::raii::Buffer& buffer, vk::raii::Image& image, uint32_t width, uint32_t height) { - std::unique_ptr commandBuffer = beginSingleTimeCommands(); - vk::BufferImageCopy region{ - .bufferOffset = 0, - .bufferRowLength = 0, - .bufferImageHeight = 0, - .imageSubresource = { vk::ImageAspectFlagBits::eColor, 0, 0, 1 }, - .imageOffset = {0, 0, 0}, - .imageExtent = {width, height, 1} - }; - commandBuffer->copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, {region}); - endSingleTimeCommands(*commandBuffer); - } - - void loadModel() { - tinyobj::attrib_t attrib; - std::vector shapes; - std::vector materials; - std::string warn, err; - - if (!LoadObj(&attrib, &shapes, &materials, &warn, &err, MODEL_PATH.c_str())) { - throw std::runtime_error(warn + err); - } - - std::unordered_map uniqueVertices{}; - - for (const auto& shape : shapes) { - for (const auto& index : shape.mesh.indices) { - Vertex vertex{}; - - vertex.pos = { - attrib.vertices[3 * index.vertex_index + 0], - attrib.vertices[3 * index.vertex_index + 1], - attrib.vertices[3 * index.vertex_index + 2] - }; - - vertex.texCoord = { - attrib.texcoords[2 * index.texcoord_index + 0], - 1.0f - attrib.texcoords[2 * index.texcoord_index + 1] - }; - - vertex.color = {1.0f, 1.0f, 1.0f}; - - if (!uniqueVertices.contains(vertex)) { - uniqueVertices[vertex] = static_cast(vertices.size()); - vertices.push_back(vertex); - } - - indices.push_back(uniqueVertices[vertex]); - } - } - } - - void createVertexBuffer() { - vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(dataStaging, vertices.data(), bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); - - copyBuffer(stagingBuffer, vertexBuffer, bufferSize); - } - - void createIndexBuffer() { - vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); - - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(data, indices.data(), bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); - - copyBuffer(stagingBuffer, indexBuffer, bufferSize); - } - - void createUniformBuffers() { - uniformBuffers.clear(); - uniformBuffersMemory.clear(); - uniformBuffersMapped.clear(); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DeviceSize bufferSize = sizeof(UniformBufferObject); - vk::raii::Buffer buffer({}); - vk::raii::DeviceMemory bufferMem({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); - uniformBuffers.emplace_back(std::move(buffer)); - uniformBuffersMemory.emplace_back(std::move(bufferMem)); - uniformBuffersMapped.emplace_back( uniformBuffersMemory[i].mapMemory(0, bufferSize)); - } - } - - void createDescriptorPool() { - std::array poolSize { - vk::DescriptorPoolSize( vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT), - vk::DescriptorPoolSize( vk::DescriptorType::eCombinedImageSampler, MAX_FRAMES_IN_FLIGHT) - }; - vk::DescriptorPoolCreateInfo poolInfo{ - .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, - .maxSets = MAX_FRAMES_IN_FLIGHT, - .poolSizeCount = static_cast(poolSize.size()), - .pPoolSizes = poolSize.data() - }; - descriptorPool = vk::raii::DescriptorPool(device, poolInfo); - } - - void createDescriptorSets() { - std::vector layouts(MAX_FRAMES_IN_FLIGHT, descriptorSetLayout); - vk::DescriptorSetAllocateInfo allocInfo{ - .descriptorPool = descriptorPool, - .descriptorSetCount = static_cast(layouts.size()), - .pSetLayouts = layouts.data() - }; - - descriptorSets.clear(); - descriptorSets = device.allocateDescriptorSets(allocInfo); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DescriptorBufferInfo bufferInfo{ - .buffer = uniformBuffers[i], - .offset = 0, - .range = sizeof(UniformBufferObject) - }; - vk::DescriptorImageInfo imageInfo{ - .sampler = textureSampler, - .imageView = textureImageView, - .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal - }; - std::array descriptorWrites{ - vk::WriteDescriptorSet{ - .dstSet = descriptorSets[i], - .dstBinding = 0, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eUniformBuffer, - .pBufferInfo = &bufferInfo - }, - vk::WriteDescriptorSet{ - .dstSet = descriptorSets[i], - .dstBinding = 1, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eCombinedImageSampler, - .pImageInfo = &imageInfo - } - }; - device.updateDescriptorSets(descriptorWrites, {}); - } - } - - void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer& buffer, vk::raii::DeviceMemory& bufferMemory) { - vk::BufferCreateInfo bufferInfo{ - .size = size, - .usage = usage, - .sharingMode = vk::SharingMode::eExclusive - }; - buffer = vk::raii::Buffer(device, bufferInfo); - vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ - .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) - }; - bufferMemory = vk::raii::DeviceMemory(device, allocInfo); - buffer.bindMemory(bufferMemory, 0); - } - - std::unique_ptr beginSingleTimeCommands() { - vk::CommandBufferAllocateInfo allocInfo{ - .commandPool = commandPool, - .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = 1 - }; - std::unique_ptr commandBuffer = std::make_unique(std::move(vk::raii::CommandBuffers(device, allocInfo).front())); - - vk::CommandBufferBeginInfo beginInfo{ - .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit - }; - commandBuffer->begin(beginInfo); - - return commandBuffer; - } - - void endSingleTimeCommands(const vk::raii::CommandBuffer& commandBuffer) const { - commandBuffer.end(); - - vk::SubmitInfo submitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandBuffer }; - queue.submit(submitInfo, nullptr); - queue.waitIdle(); - } - - void copyBuffer(vk::raii::Buffer & srcBuffer, vk::raii::Buffer & dstBuffer, vk::DeviceSize size) { - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1 }; - vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); - commandCopyBuffer.begin(vk::CommandBufferBeginInfo{ .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit }); - commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy{ .size = size }); - commandCopyBuffer.end(); - queue.submit(vk::SubmitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer }, nullptr); - queue.waitIdle(); - } - - uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) { - vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); - - for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { - if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { - return i; - } - } - - throw std::runtime_error("failed to find suitable memory type!"); - } - - void createCommandBuffers() { - commandBuffers.clear(); - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = MAX_FRAMES_IN_FLIGHT }; - commandBuffers = vk::raii::CommandBuffers(device, allocInfo); - } - - void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin({}); - // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL - transition_image_layout( - imageIndex, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage - ); - // Transition depth image to depth attachment optimal layout - vk::ImageMemoryBarrier2 depthBarrier = { - .srcStageMask = vk::PipelineStageFlagBits2::eTopOfPipe, - .srcAccessMask = {}, - .dstStageMask = vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, - .dstAccessMask = vk::AccessFlagBits2::eDepthStencilAttachmentRead | vk::AccessFlagBits2::eDepthStencilAttachmentWrite, - .oldLayout = vk::ImageLayout::eUndefined, - .newLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = depthImage, - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eDepth, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo depthDependencyInfo = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &depthBarrier - }; - commandBuffers[currentFrame].pipelineBarrier2(depthDependencyInfo); - - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::ClearValue clearDepth = vk::ClearDepthStencilValue(1.0f, 0); - - vk::RenderingAttachmentInfo colorAttachmentInfo = { - .imageView = swapChainImageViews[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor - }; - - vk::RenderingAttachmentInfo depthAttachmentInfo = { - .imageView = depthImageView, - .imageLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eDontCare, - .clearValue = clearDepth - }; - - vk::RenderingInfo renderingInfo = { - .renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &colorAttachmentInfo, - .pDepthAttachment = &depthAttachmentInfo - }; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].bindIndexBuffer( *indexBuffer, 0, vk::IndexType::eUint32 ); - commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); - commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); - commandBuffers[currentFrame].endRendering(); - // After rendering, transition the swapchain image to PRESENT_SRC - transition_image_layout( - imageIndex, - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage - ); - commandBuffers[currentFrame].end(); - } - - void transition_image_layout( - uint32_t imageIndex, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::AccessFlags2 src_access_mask, - vk::AccessFlags2 dst_access_mask, - vk::PipelineStageFlags2 src_stage_mask, - vk::PipelineStageFlags2 dst_stage_mask - ) { - vk::ImageMemoryBarrier2 barrier = { - .srcStageMask = src_stage_mask, - .srcAccessMask = src_access_mask, - .dstStageMask = dst_stage_mask, - .dstAccessMask = dst_access_mask, - .oldLayout = old_layout, - .newLayout = new_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); - } - - void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); - - for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - } - - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - inFlightFences.emplace_back(device, vk::FenceCreateInfo{ .flags = vk::FenceCreateFlagBits::eSignaled }); - } - } - - void updateUniformBuffer(uint32_t currentImage) const { - static auto startTime = std::chrono::high_resolution_clock::now(); - - auto currentTime = std::chrono::high_resolution_clock::now(); - float time = std::chrono::duration(currentTime - startTime).count(); - - UniformBufferObject ubo{}; - ubo.model = rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.view = lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); - ubo.proj[1][1] *= -1; - - memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); - } - - void drawFrame() { - while ( vk::Result::eTimeout == device.waitForFences( *inFlightFences[currentFrame], vk::True, UINT64_MAX ) ) - ; - auto [result, imageIndex] = swapChain.acquireNextImage( UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr ); - - if (result == vk::Result::eErrorOutOfDateKHR) { - recreateSwapChain(); - return; - } - if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) { - throw std::runtime_error("failed to acquire swap chain image!"); - } - updateUniformBuffer(currentFrame); - - device.resetFences( *inFlightFences[currentFrame] ); - commandBuffers[currentFrame].reset(); - recordCommandBuffer(imageIndex); - - vk::PipelineStageFlags waitDestinationStageMask( vk::PipelineStageFlagBits::eColorAttachmentOutput ); - const vk::SubmitInfo submitInfo{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], - .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], - .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex] }; - queue.submit(submitInfo, *inFlightFences[currentFrame]); - - - const vk::PresentInfoKHR presentInfoKHR{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], - .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex }; - result = queue.presentKHR(presentInfoKHR); - if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { - framebufferResized = false; - recreateSwapChain(); - } else if (result != vk::Result::eSuccess) { - throw std::runtime_error("failed to present swap chain image!"); - } - semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; - } - - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size(), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const & surfaceCapabilities) { - auto minImageCount = std::max( 3u, surfaceCapabilities.minImageCount ); - if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) { - minImageCount = surfaceCapabilities.maxImageCount; - } - return minImageCount; - } - - static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { - assert(!availableFormats.empty()); - const auto formatIt = std::ranges::find_if( - availableFormats, - []( const auto & format ) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; } ); - return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - assert(std::ranges::any_of(availablePresentModes, [](auto presentMode){ return presentMode == vk::PresentModeKHR::eFifo; })); - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eFifo; - } - - vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) { - if (capabilities.currentExtent.width != 0xFFFFFFFF) { - 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) - }; - } - - [[nodiscard]] std::vector getRequiredExtensions() const { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - 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: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::SurfaceFormatKHR swapChainSurfaceFormat; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + + vk::raii::Image depthImage = nullptr; + vk::raii::DeviceMemory depthImageMemory = nullptr; + vk::raii::ImageView depthImageView = nullptr; + + vk::raii::Image textureImage = nullptr; + vk::raii::DeviceMemory textureImageMemory = nullptr; + vk::raii::ImageView textureImageView = nullptr; + vk::raii::Sampler textureSampler = nullptr; + + std::vector vertices; + std::vector indices; + vk::raii::Buffer vertexBuffer = nullptr; + vk::raii::DeviceMemory vertexBufferMemory = nullptr; + vk::raii::Buffer indexBuffer = nullptr; + vk::raii::DeviceMemory indexBufferMemory = nullptr; + + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + std::vector uniformBuffersMapped; + + vk::raii::DescriptorPool descriptorPool = nullptr; + std::vector descriptorSets; + + vk::raii::CommandPool commandPool = nullptr; + std::vector commandBuffers; + + std::vector presentCompleteSemaphore; + std::vector renderFinishedSemaphore; + std::vector inFlightFences; + uint32_t semaphoreIndex = 0; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow *window, int width, int height) + { + auto app = static_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createDescriptorSetLayout(); + createGraphicsPipeline(); + createCommandPool(); + createDepthResources(); + createTextureImage(); + createTextureImageView(); + createTextureSampler(); + loadModel(); + createVertexBuffer(); + createIndexBuffer(); + createUniformBuffers(); + createDescriptorPool(); + createDescriptorSets(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() + { + while (!glfwWindowShouldClose(window)) + { + glfwPollEvents(); + drawFrame(); + } + + device.waitIdle(); + } + + void cleanupSwapChain() + { + swapChainImageViews.clear(); + swapChain = nullptr; + } + + void cleanup() const + { + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() + { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) + { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + device.waitIdle(); + + cleanupSwapChain(); + createSwapChain(); + createImageViews(); + createDepthResources(); + } + + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().features.samplerAnisotropy && + features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain featureChain = { + {.features = {.samplerAnisotropy = true}}, // vk::PhysicalDeviceFeatures2 + {.synchronization2 = true, .dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(*surface); + swapChainExtent = chooseSwapExtent(surfaceCapabilities); + swapChainSurfaceFormat = chooseSwapSurfaceFormat(physicalDevice.getSurfaceFormatsKHR(*surface)); + vk::SwapchainCreateInfoKHR swapChainCreateInfo{.surface = *surface, + .minImageCount = chooseSwapMinImageCount(surfaceCapabilities), + .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + void createImageViews() + { + vk::ImageViewCreateInfo imageViewCreateInfo{ + .viewType = vk::ImageViewType::e2D, + .format = swapChainSurfaceFormat.format, + .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + for (auto image : swapChainImages) + { + imageViewCreateInfo.image = image; + swapChainImageViews.emplace_back(device, imageViewCreateInfo); + } + } + + void createDescriptorSetLayout() + { + std::array bindings = { + vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr), + vk::DescriptorSetLayoutBinding(1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment, nullptr)}; + + vk::DescriptorSetLayoutCreateInfo layoutInfo{.bindingCount = static_cast(bindings.size()), .pBindings = bindings.data()}; + descriptorSetLayout = vk::raii::DescriptorSetLayout(device, layoutInfo); + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; + + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{ + .vertexBindingDescriptionCount = 1, + .pVertexBindingDescriptions = &bindingDescription, + .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), + .pVertexAttributeDescriptions = attributeDescriptions.data()}; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ + .topology = vk::PrimitiveTopology::eTriangleList, + .primitiveRestartEnable = vk::False}; + vk::PipelineViewportStateCreateInfo viewportState{ + .viewportCount = 1, + .scissorCount = 1}; + vk::PipelineRasterizationStateCreateInfo rasterizer{ + .depthClampEnable = vk::False, + .rasterizerDiscardEnable = vk::False, + .polygonMode = vk::PolygonMode::eFill, + .cullMode = vk::CullModeFlagBits::eBack, + .frontFace = vk::FrontFace::eCounterClockwise, + .depthBiasEnable = vk::False}; + rasterizer.lineWidth = 1.0f; + vk::PipelineMultisampleStateCreateInfo multisampling{ + .rasterizationSamples = vk::SampleCountFlagBits::e1, + .sampleShadingEnable = vk::False}; + vk::PipelineDepthStencilStateCreateInfo depthStencil{ + .depthTestEnable = vk::True, + .depthWriteEnable = vk::True, + .depthCompareOp = vk::CompareOp::eLess, + .depthBoundsTestEnable = vk::False, + .stencilTestEnable = vk::False}; + vk::PipelineColorBlendAttachmentState colorBlendAttachment; + colorBlendAttachment.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA; + colorBlendAttachment.blendEnable = vk::False; + + vk::PipelineColorBlendStateCreateInfo colorBlending{ + .logicOpEnable = vk::False, + .logicOp = vk::LogicOp::eCopy, + .attachmentCount = 1, + .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0}; + + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::Format depthFormat = findDepthFormat(); + vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ + .colorAttachmentCount = 1, + .pColorAttachmentFormats = &swapChainSurfaceFormat.format, + .depthAttachmentFormat = depthFormat}; + vk::GraphicsPipelineCreateInfo pipelineInfo{.pNext = &pipelineRenderingCreateInfo, + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pDepthStencilState = &depthStencil, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{ + .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex}; + commandPool = vk::raii::CommandPool(device, poolInfo); + } + + void createDepthResources() + { + vk::Format depthFormat = findDepthFormat(); + + createImage(swapChainExtent.width, swapChainExtent.height, depthFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eDepthStencilAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, depthImage, depthImageMemory); + depthImageView = createImageView(depthImage, depthFormat, vk::ImageAspectFlagBits::eDepth); + } + + vk::Format findSupportedFormat(const std::vector &candidates, vk::ImageTiling tiling, vk::FormatFeatureFlags features) const + { + for (const auto format : candidates) + { + vk::FormatProperties props = physicalDevice.getFormatProperties(format); + + if (tiling == vk::ImageTiling::eLinear && (props.linearTilingFeatures & features) == features) + { + return format; + } + if (tiling == vk::ImageTiling::eOptimal && (props.optimalTilingFeatures & features) == features) + { + return format; + } + } + + throw std::runtime_error("failed to find supported format!"); + } + + [[nodiscard]] vk::Format findDepthFormat() const + { + return findSupportedFormat( + {vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, vk::Format::eD24UnormS8Uint}, + vk::ImageTiling::eOptimal, + vk::FormatFeatureFlagBits::eDepthStencilAttachment); + } + + static bool hasStencilComponent(vk::Format format) + { + return format == vk::Format::eD32SfloatS8Uint || format == vk::Format::eD24UnormS8Uint; + } + + void createTextureImage() + { + int texWidth, texHeight, texChannels; + stbi_uc *pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + vk::DeviceSize imageSize = texWidth * texHeight * 4; + + if (!pixels) + { + throw std::runtime_error("failed to load texture image!"); + } + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, imageSize); + memcpy(data, pixels, imageSize); + stagingBufferMemory.unmapMemory(); + + stbi_image_free(pixels); + + createImage(texWidth, texHeight, vk::Format::eR8G8B8A8Srgb, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory); + + transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); + transitionImageLayout(textureImage, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal); + } + + void createTextureImageView() + { + textureImageView = createImageView(textureImage, vk::Format::eR8G8B8A8Srgb, vk::ImageAspectFlagBits::eColor); + } + + void createTextureSampler() + { + vk::PhysicalDeviceProperties properties = physicalDevice.getProperties(); + vk::SamplerCreateInfo samplerInfo{ + .magFilter = vk::Filter::eLinear, + .minFilter = vk::Filter::eLinear, + .mipmapMode = vk::SamplerMipmapMode::eLinear, + .addressModeU = vk::SamplerAddressMode::eRepeat, + .addressModeV = vk::SamplerAddressMode::eRepeat, + .addressModeW = vk::SamplerAddressMode::eRepeat, + .mipLodBias = 0.0f, + .anisotropyEnable = vk::True, + .maxAnisotropy = properties.limits.maxSamplerAnisotropy, + .compareEnable = vk::False, + .compareOp = vk::CompareOp::eAlways}; + textureSampler = vk::raii::Sampler(device, samplerInfo); + } + + vk::raii::ImageView createImageView(vk::raii::Image &image, vk::Format format, vk::ImageAspectFlags aspectFlags) + { + vk::ImageViewCreateInfo viewInfo{ + .image = image, + .viewType = vk::ImageViewType::e2D, + .format = format, + .subresourceRange = {aspectFlags, 0, 1, 0, 1}}; + return vk::raii::ImageView(device, viewInfo); + } + + void createImage(uint32_t width, uint32_t height, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image &image, vk::raii::DeviceMemory &imageMemory) + { + vk::ImageCreateInfo imageInfo{ + .imageType = vk::ImageType::e2D, + .format = format, + .extent = {width, height, 1}, + .mipLevels = 1, + .arrayLayers = 1, + .samples = vk::SampleCountFlagBits::e1, + .tiling = tiling, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive, + .initialLayout = vk::ImageLayout::eUndefined}; + image = vk::raii::Image(device, imageInfo); + + vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{ + .allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + imageMemory = vk::raii::DeviceMemory(device, allocInfo); + image.bindMemory(imageMemory, 0); + } + + void transitionImageLayout(const vk::raii::Image &image, vk::ImageLayout oldLayout, vk::ImageLayout newLayout) + { + auto commandBuffer = beginSingleTimeCommands(); + + vk::ImageMemoryBarrier barrier{ + .oldLayout = oldLayout, + .newLayout = newLayout, + .image = image, + .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + + vk::PipelineStageFlags sourceStage; + vk::PipelineStageFlags destinationStage; + + if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) + { + barrier.srcAccessMask = {}; + barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; + + sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; + destinationStage = vk::PipelineStageFlagBits::eTransfer; + } + else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) + { + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + + sourceStage = vk::PipelineStageFlagBits::eTransfer; + destinationStage = vk::PipelineStageFlagBits::eFragmentShader; + } + else + { + throw std::invalid_argument("unsupported layout transition!"); + } + commandBuffer->pipelineBarrier(sourceStage, destinationStage, {}, {}, nullptr, barrier); + endSingleTimeCommands(*commandBuffer); + } + + void copyBufferToImage(const vk::raii::Buffer &buffer, vk::raii::Image &image, uint32_t width, uint32_t height) + { + std::unique_ptr commandBuffer = beginSingleTimeCommands(); + vk::BufferImageCopy region{ + .bufferOffset = 0, + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource = {vk::ImageAspectFlagBits::eColor, 0, 0, 1}, + .imageOffset = {0, 0, 0}, + .imageExtent = {width, height, 1}}; + commandBuffer->copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, {region}); + endSingleTimeCommands(*commandBuffer); + } + + void loadModel() + { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn, err; + + if (!LoadObj(&attrib, &shapes, &materials, &warn, &err, MODEL_PATH.c_str())) + { + throw std::runtime_error(warn + err); + } + + std::unordered_map uniqueVertices{}; + + for (const auto &shape : shapes) + { + for (const auto &index : shape.mesh.indices) + { + Vertex vertex{}; + + vertex.pos = { + attrib.vertices[3 * index.vertex_index + 0], + attrib.vertices[3 * index.vertex_index + 1], + attrib.vertices[3 * index.vertex_index + 2]}; + + vertex.texCoord = { + attrib.texcoords[2 * index.texcoord_index + 0], + 1.0f - attrib.texcoords[2 * index.texcoord_index + 1]}; + + vertex.color = {1.0f, 1.0f, 1.0f}; + + if (!uniqueVertices.contains(vertex)) + { + uniqueVertices[vertex] = static_cast(vertices.size()); + vertices.push_back(vertex); + } + + indices.push_back(uniqueVertices[vertex]); + } + } + } + + void createVertexBuffer() + { + vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(dataStaging, vertices.data(), bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + } + + void createIndexBuffer() + { + vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(data, indices.data(), bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); + + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + } + + void createUniformBuffers() + { + uniformBuffers.clear(); + uniformBuffersMemory.clear(); + uniformBuffersMapped.clear(); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DeviceSize bufferSize = sizeof(UniformBufferObject); + vk::raii::Buffer buffer({}); + vk::raii::DeviceMemory bufferMem({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); + uniformBuffers.emplace_back(std::move(buffer)); + uniformBuffersMemory.emplace_back(std::move(bufferMem)); + uniformBuffersMapped.emplace_back(uniformBuffersMemory[i].mapMemory(0, bufferSize)); + } + } + + void createDescriptorPool() + { + std::array poolSize{ + vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT), + vk::DescriptorPoolSize(vk::DescriptorType::eCombinedImageSampler, MAX_FRAMES_IN_FLIGHT)}; + vk::DescriptorPoolCreateInfo poolInfo{ + .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, + .maxSets = MAX_FRAMES_IN_FLIGHT, + .poolSizeCount = static_cast(poolSize.size()), + .pPoolSizes = poolSize.data()}; + descriptorPool = vk::raii::DescriptorPool(device, poolInfo); + } + + void createDescriptorSets() + { + std::vector layouts(MAX_FRAMES_IN_FLIGHT, descriptorSetLayout); + vk::DescriptorSetAllocateInfo allocInfo{ + .descriptorPool = descriptorPool, + .descriptorSetCount = static_cast(layouts.size()), + .pSetLayouts = layouts.data()}; + + descriptorSets.clear(); + descriptorSets = device.allocateDescriptorSets(allocInfo); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DescriptorBufferInfo bufferInfo{ + .buffer = uniformBuffers[i], + .offset = 0, + .range = sizeof(UniformBufferObject)}; + vk::DescriptorImageInfo imageInfo{ + .sampler = textureSampler, + .imageView = textureImageView, + .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal}; + std::array descriptorWrites{ + vk::WriteDescriptorSet{ + .dstSet = descriptorSets[i], + .dstBinding = 0, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eUniformBuffer, + .pBufferInfo = &bufferInfo}, + vk::WriteDescriptorSet{ + .dstSet = descriptorSets[i], + .dstBinding = 1, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .pImageInfo = &imageInfo}}; + device.updateDescriptorSets(descriptorWrites, {}); + } + } + + void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer &buffer, vk::raii::DeviceMemory &bufferMemory) + { + vk::BufferCreateInfo bufferInfo{ + .size = size, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive}; + buffer = vk::raii::Buffer(device, bufferInfo); + vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{ + .allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + bufferMemory = vk::raii::DeviceMemory(device, allocInfo); + buffer.bindMemory(bufferMemory, 0); + } + + std::unique_ptr beginSingleTimeCommands() + { + vk::CommandBufferAllocateInfo allocInfo{ + .commandPool = commandPool, + .level = vk::CommandBufferLevel::ePrimary, + .commandBufferCount = 1}; + std::unique_ptr commandBuffer = std::make_unique(std::move(vk::raii::CommandBuffers(device, allocInfo).front())); + + vk::CommandBufferBeginInfo beginInfo{ + .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; + commandBuffer->begin(beginInfo); + + return commandBuffer; + } + + void endSingleTimeCommands(const vk::raii::CommandBuffer &commandBuffer) const + { + commandBuffer.end(); + + vk::SubmitInfo submitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandBuffer}; + queue.submit(submitInfo, nullptr); + queue.waitIdle(); + } + + void copyBuffer(vk::raii::Buffer &srcBuffer, vk::raii::Buffer &dstBuffer, vk::DeviceSize size) + { + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1}; + vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); + commandCopyBuffer.begin(vk::CommandBufferBeginInfo{.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); + commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy{.size = size}); + commandCopyBuffer.end(); + queue.submit(vk::SubmitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer}, nullptr); + queue.waitIdle(); + } + + uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) + { + vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) + { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) + { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + void createCommandBuffers() + { + commandBuffers.clear(); + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = MAX_FRAMES_IN_FLIGHT}; + commandBuffers = vk::raii::CommandBuffers(device, allocInfo); + } + + void recordCommandBuffer(uint32_t imageIndex) + { + commandBuffers[currentFrame].begin({}); + // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL + transition_image_layout( + imageIndex, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage + ); + // Transition depth image to depth attachment optimal layout + vk::ImageMemoryBarrier2 depthBarrier = { + .srcStageMask = vk::PipelineStageFlagBits2::eTopOfPipe, + .srcAccessMask = {}, + .dstStageMask = vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, + .dstAccessMask = vk::AccessFlagBits2::eDepthStencilAttachmentRead | vk::AccessFlagBits2::eDepthStencilAttachmentWrite, + .oldLayout = vk::ImageLayout::eUndefined, + .newLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = depthImage, + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eDepth, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo depthDependencyInfo = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &depthBarrier}; + commandBuffers[currentFrame].pipelineBarrier2(depthDependencyInfo); + + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::ClearValue clearDepth = vk::ClearDepthStencilValue(1.0f, 0); + + vk::RenderingAttachmentInfo colorAttachmentInfo = { + .imageView = swapChainImageViews[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor}; + + vk::RenderingAttachmentInfo depthAttachmentInfo = { + .imageView = depthImageView, + .imageLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eDontCare, + .clearValue = clearDepth}; + + vk::RenderingInfo renderingInfo = { + .renderArea = {.offset = {0, 0}, .extent = swapChainExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &colorAttachmentInfo, + .pDepthAttachment = &depthAttachmentInfo}; + commandBuffers[currentFrame].beginRendering(renderingInfo); + commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint32); + commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); + commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); + commandBuffers[currentFrame].endRendering(); + // After rendering, transition the swapchain image to PRESENT_SRC + transition_image_layout( + imageIndex, + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage + ); + commandBuffers[currentFrame].end(); + } + + void transition_image_layout( + uint32_t imageIndex, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout, + vk::AccessFlags2 src_access_mask, + vk::AccessFlags2 dst_access_mask, + vk::PipelineStageFlags2 src_stage_mask, + vk::PipelineStageFlags2 dst_stage_mask) + { + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = src_stage_mask, + .srcAccessMask = src_access_mask, + .dstStageMask = dst_stage_mask, + .dstAccessMask = dst_access_mask, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo dependency_info = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + } + + void createSyncObjects() + { + presentCompleteSemaphore.clear(); + renderFinishedSemaphore.clear(); + inFlightFences.clear(); + + for (size_t i = 0; i < swapChainImages.size(); i++) + { + presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); + } + } + + void updateUniformBuffer(uint32_t currentImage) const + { + static auto startTime = std::chrono::high_resolution_clock::now(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + UniformBufferObject ubo{}; + ubo.model = rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.view = lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); + ubo.proj[1][1] *= -1; + + memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); + } + + void drawFrame() + { + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + ; + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr); + + if (result == vk::Result::eErrorOutOfDateKHR) + { + recreateSwapChain(); + return; + } + if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) + { + throw std::runtime_error("failed to acquire swap chain image!"); + } + updateUniformBuffer(currentFrame); + + device.resetFences(*inFlightFences[currentFrame]); + commandBuffers[currentFrame].reset(); + recordCommandBuffer(imageIndex); + + vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[currentFrame]); + + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + result = queue.presentKHR(presentInfoKHR); + if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) + { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != vk::Result::eSuccess) + { + throw std::runtime_error("failed to present swap chain image!"); + } + semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size(), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const &surfaceCapabilities) + { + auto minImageCount = std::max(3u, surfaceCapabilities.minImageCount); + if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) + { + minImageCount = surfaceCapabilities.maxImageCount; + } + return minImageCount; + } + + static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + assert(!availableFormats.empty()); + const auto formatIt = std::ranges::find_if( + availableFormats, + [](const auto &format) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; }); + return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + assert(std::ranges::any_of(availablePresentModes, [](auto presentMode) { return presentMode == vk::PresentModeKHR::eFifo; })); + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + vk::PresentModeKHR::eFifo; + } + + vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR &capabilities) + { + if (capabilities.currentExtent.width != 0xFFFFFFFF) + { + 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)}; + } + + [[nodiscard]] std::vector getRequiredExtensions() const + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + 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; + } }; -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/29_mipmapping.cpp b/attachments/29_mipmapping.cpp index 6f941097..220da939 100644 --- a/attachments/29_mipmapping.cpp +++ b/attachments/29_mipmapping.cpp @@ -12,14 +12,14 @@ #include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include #define GLM_FORCE_RADIANS @@ -35,16 +35,15 @@ import vulkan_hpp; #define TINYOBJLOADER_IMPLEMENTATION #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -constexpr uint64_t FenceTimeout = 100000000; -const std::string MODEL_PATH = "models/viking_room.obj"; -const std::string TEXTURE_PATH = "textures/viking_room.png"; -constexpr int MAX_FRAMES_IN_FLIGHT = 2; +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; +constexpr uint64_t FenceTimeout = 100000000; +const std::string MODEL_PATH = "models/viking_room.obj"; +const std::string TEXTURE_PATH = "textures/viking_room.png"; +constexpr int MAX_FRAMES_IN_FLIGHT = 2; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -52,1191 +51,1237 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -struct Vertex { - glm::vec3 pos; - glm::vec3 color; - glm::vec2 texCoord; - - static vk::VertexInputBindingDescription getBindingDescription() { - return { 0, sizeof(Vertex), vk::VertexInputRate::eVertex }; - } - - static std::array getAttributeDescriptions() { - return { - vk::VertexInputAttributeDescription( 0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, pos) ), - vk::VertexInputAttributeDescription( 1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color) ), - vk::VertexInputAttributeDescription( 2, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, texCoord) ) - }; - } - - bool operator==(const Vertex& other) const { - return pos == other.pos && color == other.color && texCoord == other.texCoord; - } +struct Vertex +{ + glm::vec3 pos; + glm::vec3 color; + glm::vec2 texCoord; + + static vk::VertexInputBindingDescription getBindingDescription() + { + return {0, sizeof(Vertex), vk::VertexInputRate::eVertex}; + } + + static std::array getAttributeDescriptions() + { + return { + vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, pos)), + vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color)), + vk::VertexInputAttributeDescription(2, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, texCoord))}; + } + + bool operator==(const Vertex &other) const + { + return pos == other.pos && color == other.color && texCoord == other.texCoord; + } }; -template<> struct std::hash { - size_t operator()(Vertex const& vertex) const noexcept { - return ((hash()(vertex.pos) ^ (hash()(vertex.color) << 1)) >> 1) ^ (hash()(vertex.texCoord) << 1); - } +template <> +struct std::hash +{ + size_t operator()(Vertex const &vertex) const noexcept + { + return ((hash()(vertex.pos) ^ (hash()(vertex.color) << 1)) >> 1) ^ (hash()(vertex.texCoord) << 1); + } }; -struct UniformBufferObject { - alignas(16) glm::mat4 model; - alignas(16) glm::mat4 view; - alignas(16) glm::mat4 proj; +struct UniformBufferObject +{ + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; }; -class HelloTriangleApplication { -public: - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } - -private: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::SurfaceFormatKHR swapChainSurfaceFormat; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - - vk::raii::Image depthImage = nullptr; - vk::raii::DeviceMemory depthImageMemory = nullptr; - vk::raii::ImageView depthImageView = nullptr; - - uint32_t mipLevels = 0; - vk::raii::Image textureImage = nullptr; - vk::raii::DeviceMemory textureImageMemory = nullptr; - vk::raii::ImageView textureImageView = nullptr; - vk::raii::Sampler textureSampler = nullptr; - - std::vector vertices; - std::vector indices; - vk::raii::Buffer vertexBuffer = nullptr; - vk::raii::DeviceMemory vertexBufferMemory = nullptr; - vk::raii::Buffer indexBuffer = nullptr; - vk::raii::DeviceMemory indexBufferMemory = nullptr; - - std::vector uniformBuffers; - std::vector uniformBuffersMemory; - std::vector uniformBuffersMapped; - - vk::raii::DescriptorPool descriptorPool = nullptr; - std::vector descriptorSets; - - vk::raii::CommandPool commandPool = nullptr; - std::vector commandBuffers; - - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; - std::vector inFlightFences; - uint32_t semaphoreIndex = 0; - uint32_t currentFrame = 0; - - bool framebufferResized = false; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); - } - - static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { - auto app = static_cast(glfwGetWindowUserPointer(window)); - app->framebufferResized = true; - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - createSurface(); - pickPhysicalDevice(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createDescriptorSetLayout(); - createGraphicsPipeline(); - createCommandPool(); - createDepthResources(); - createTextureImage(); - createTextureImageView(); - createTextureSampler(); - loadModel(); - createVertexBuffer(); - createIndexBuffer(); - createUniformBuffers(); - createDescriptorPool(); - createDescriptorSets(); - createCommandBuffers(); - createSyncObjects(); - } - - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - drawFrame(); - } - - device.waitIdle(); - } - - void cleanupSwapChain() { - swapChainImageViews.clear(); - swapChain = nullptr; - } - - void cleanup() const { - glfwDestroyWindow(window); - - glfwTerminate(); - } - - void recreateSwapChain() { - int width = 0, height = 0; - glfwGetFramebufferSize(window, &width, &height); - while (width == 0 || height == 0) { - glfwGetFramebufferSize(window, &width, &height); - glfwWaitEvents(); - } - - device.waitIdle(); - - cleanupSwapChain(); - createSwapChain(); - createImageViews(); - createDepthResources(); - } - - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations +class HelloTriangleApplication +{ + public: + void run() + { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + + private: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::SurfaceFormatKHR swapChainSurfaceFormat; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + + vk::raii::Image depthImage = nullptr; + vk::raii::DeviceMemory depthImageMemory = nullptr; + vk::raii::ImageView depthImageView = nullptr; + + uint32_t mipLevels = 0; + vk::raii::Image textureImage = nullptr; + vk::raii::DeviceMemory textureImageMemory = nullptr; + vk::raii::ImageView textureImageView = nullptr; + vk::raii::Sampler textureSampler = nullptr; + + std::vector vertices; + std::vector indices; + vk::raii::Buffer vertexBuffer = nullptr; + vk::raii::DeviceMemory vertexBufferMemory = nullptr; + vk::raii::Buffer indexBuffer = nullptr; + vk::raii::DeviceMemory indexBufferMemory = nullptr; + + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + std::vector uniformBuffersMapped; + + vk::raii::DescriptorPool descriptorPool = nullptr; + std::vector descriptorSets; + + vk::raii::CommandPool commandPool = nullptr; + std::vector commandBuffers; + + std::vector presentCompleteSemaphore; + std::vector renderFinishedSemaphore; + std::vector inFlightFences; + uint32_t semaphoreIndex = 0; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow *window, int width, int height) + { + auto app = static_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createDescriptorSetLayout(); + createGraphicsPipeline(); + createCommandPool(); + createDepthResources(); + createTextureImage(); + createTextureImageView(); + createTextureSampler(); + loadModel(); + createVertexBuffer(); + createIndexBuffer(); + createUniformBuffers(); + createDescriptorPool(); + createDescriptorSets(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() + { + while (!glfwWindowShouldClose(window)) + { + glfwPollEvents(); + drawFrame(); + } + + device.waitIdle(); + } + + void cleanupSwapChain() + { + swapChainImageViews.clear(); + swapChain = nullptr; + } + + void cleanup() const + { + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() + { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) + { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + device.waitIdle(); + + cleanupSwapChain(); + createSwapChain(); + createImageViews(); + createDepthResources(); + } + + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().features.samplerAnisotropy && - features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - }); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error("failed to find a suitable GPU!"); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain featureChain = { - {.features = {.samplerAnisotropy = true } }, // vk::PhysicalDeviceFeatures2 - {.synchronization2 = true, .dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( *surface ); - swapChainExtent = chooseSwapExtent( surfaceCapabilities ); - swapChainSurfaceFormat = chooseSwapSurfaceFormat( physicalDevice.getSurfaceFormatsKHR( *surface ) ); - vk::SwapchainCreateInfoKHR swapChainCreateInfo{ .surface = *surface, - .minImageCount = chooseSwapMinImageCount( surfaceCapabilities ), - .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 }; - - swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); - swapChainImages = swapChain.getImages(); - } - - void createImageViews() { - vk::ImageViewCreateInfo imageViewCreateInfo{ - .viewType = vk::ImageViewType::e2D, - .format = swapChainSurfaceFormat.format, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } - }; - for ( auto image : swapChainImages ) - { - imageViewCreateInfo.image = image; - swapChainImageViews.emplace_back( device, imageViewCreateInfo ); - } - } - - void createDescriptorSetLayout() { - std::array bindings = { - vk::DescriptorSetLayoutBinding( 0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr), - vk::DescriptorSetLayoutBinding( 1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment, nullptr) - }; - - vk::DescriptorSetLayoutCreateInfo layoutInfo{ .bindingCount = static_cast(bindings.size()), .pBindings = bindings.data() }; - descriptorSetLayout = vk::raii::DescriptorSetLayout(device, layoutInfo); - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; - - auto bindingDescription = Vertex::getBindingDescription(); - auto attributeDescriptions = Vertex::getAttributeDescriptions(); - vk::PipelineVertexInputStateCreateInfo vertexInputInfo{ - .vertexBindingDescriptionCount = 1, - .pVertexBindingDescriptions = &bindingDescription, - .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), - .pVertexAttributeDescriptions = attributeDescriptions.data() - }; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ - .topology = vk::PrimitiveTopology::eTriangleList, - .primitiveRestartEnable = vk::False - }; - vk::PipelineViewportStateCreateInfo viewportState{ - .viewportCount = 1, - .scissorCount = 1 - }; - vk::PipelineRasterizationStateCreateInfo rasterizer{ - .depthClampEnable = vk::False, - .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, - .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eCounterClockwise, - .depthBiasEnable = vk::False - }; - rasterizer.lineWidth = 1.0f; - vk::PipelineMultisampleStateCreateInfo multisampling{ - .rasterizationSamples = vk::SampleCountFlagBits::e1, - .sampleShadingEnable = vk::False - }; - vk::PipelineDepthStencilStateCreateInfo depthStencil{ - .depthTestEnable = vk::True, - .depthWriteEnable = vk::True, - .depthCompareOp = vk::CompareOp::eLess, - .depthBoundsTestEnable = vk::False, - .stencilTestEnable = vk::False - }; - vk::PipelineColorBlendAttachmentState colorBlendAttachment; - colorBlendAttachment.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA; - colorBlendAttachment.blendEnable = vk::False; - - vk::PipelineColorBlendStateCreateInfo colorBlending{ - .logicOpEnable = vk::False, - .logicOp = vk::LogicOp::eCopy, - .attachmentCount = 1, - .pAttachments = &colorBlendAttachment - }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0 }; - - pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); - - vk::Format depthFormat = findDepthFormat(); - vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ - .colorAttachmentCount = 1, - .pColorAttachmentFormats = &swapChainSurfaceFormat.format, - .depthAttachmentFormat = depthFormat - }; - vk::GraphicsPipelineCreateInfo pipelineInfo{ .pNext = &pipelineRenderingCreateInfo, - .stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pDepthStencilState = &depthStencil, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = pipelineLayout, - .renderPass = nullptr - }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{ - .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex - }; - commandPool = vk::raii::CommandPool(device, poolInfo); - } - - void createDepthResources() { - vk::Format depthFormat = findDepthFormat(); - - createImage(swapChainExtent.width, swapChainExtent.height, 1, depthFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eDepthStencilAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, depthImage, depthImageMemory); - depthImageView = createImageView(depthImage, depthFormat, vk::ImageAspectFlagBits::eDepth, 1); - } - - vk::Format findSupportedFormat(const std::vector& candidates, vk::ImageTiling tiling, vk::FormatFeatureFlags features) const { - for (const auto format : candidates) { - vk::FormatProperties props = physicalDevice.getFormatProperties(format); - - if (tiling == vk::ImageTiling::eLinear && (props.linearTilingFeatures & features) == features) { - return format; - } - if (tiling == vk::ImageTiling::eOptimal && (props.optimalTilingFeatures & features) == features) { - return format; - } - } - - throw std::runtime_error("failed to find supported format!"); - } - - [[nodiscard]] vk::Format findDepthFormat() const { - return findSupportedFormat( - {vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, vk::Format::eD24UnormS8Uint}, - vk::ImageTiling::eOptimal, - vk::FormatFeatureFlagBits::eDepthStencilAttachment - ); - } - - static bool hasStencilComponent(vk::Format format) { - return format == vk::Format::eD32SfloatS8Uint || format == vk::Format::eD24UnormS8Uint; - } - - void createTextureImage() { - int texWidth, texHeight, texChannels; - stbi_uc* pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); - vk::DeviceSize imageSize = texWidth * texHeight * 4; - mipLevels = static_cast(std::floor(std::log2(std::max(texWidth, texHeight)))) + 1; - - if (!pixels) { - throw std::runtime_error("failed to load texture image!"); - } - - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, imageSize); - memcpy(data, pixels, imageSize); - stagingBufferMemory.unmapMemory(); - - stbi_image_free(pixels); - - createImage(texWidth, texHeight, mipLevels, vk::Format::eR8G8B8A8Srgb, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory); - - transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, mipLevels); - copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); - - generateMipmaps(textureImage, vk::Format::eR8G8B8A8Srgb, texWidth, texHeight, mipLevels); - } - - void generateMipmaps(vk::raii::Image& image, vk::Format imageFormat, int32_t texWidth, int32_t texHeight, uint32_t mipLevels) { - // Check if image format supports linear blit-ing - vk::FormatProperties formatProperties = physicalDevice.getFormatProperties(imageFormat); - - if (!(formatProperties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eSampledImageFilterLinear)) { - throw std::runtime_error("texture image format does not support linear blitting!"); - } - - std::unique_ptr commandBuffer = beginSingleTimeCommands(); - - vk::ImageMemoryBarrier barrier = { .srcAccessMask = vk::AccessFlagBits::eTransferWrite, .dstAccessMask =vk::AccessFlagBits::eTransferRead - , .oldLayout = vk::ImageLayout::eTransferDstOptimal, .newLayout = vk::ImageLayout::eTransferSrcOptimal - , .srcQueueFamilyIndex = vk::QueueFamilyIgnored, .dstQueueFamilyIndex = vk::QueueFamilyIgnored, .image = image }; - barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; - barrier.subresourceRange.baseArrayLayer = 0; - barrier.subresourceRange.layerCount = 1; - barrier.subresourceRange.levelCount = 1; - - int32_t mipWidth = texWidth; - int32_t mipHeight = texHeight; - - for (uint32_t i = 1; i < mipLevels; i++) { - barrier.subresourceRange.baseMipLevel = i - 1; - barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal; - barrier.newLayout = vk::ImageLayout::eTransferSrcOptimal; - barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; - barrier.dstAccessMask = vk::AccessFlagBits::eTransferRead; - - commandBuffer->pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer, {}, {}, {}, barrier); - - vk::ArrayWrapper1D offsets, dstOffsets; - offsets[0] = vk::Offset3D(0, 0, 0); - offsets[1] = vk::Offset3D(mipWidth, mipHeight, 1); - dstOffsets[0] = vk::Offset3D(0, 0, 0); - dstOffsets[1] = vk::Offset3D(mipWidth > 1 ? mipWidth / 2 : 1, mipHeight > 1 ? mipHeight / 2 : 1, 1); - vk::ImageBlit blit = { .srcSubresource = {}, .srcOffsets = offsets, - .dstSubresource = {}, .dstOffsets = dstOffsets }; - blit.srcSubresource = vk::ImageSubresourceLayers( vk::ImageAspectFlagBits::eColor, i - 1, 0, 1); - blit.dstSubresource = vk::ImageSubresourceLayers( vk::ImageAspectFlagBits::eColor, i, 0, 1); - - commandBuffer->blitImage(image, vk::ImageLayout::eTransferSrcOptimal, image, vk::ImageLayout::eTransferDstOptimal, { blit }, vk::Filter::eLinear); - - barrier.oldLayout = vk::ImageLayout::eTransferSrcOptimal; - barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal; - barrier.srcAccessMask = vk::AccessFlagBits::eTransferRead; - barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - - commandBuffer->pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {}, barrier); - - if (mipWidth > 1) mipWidth /= 2; - if (mipHeight > 1) mipHeight /= 2; - } - - barrier.subresourceRange.baseMipLevel = mipLevels - 1; - barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal; - barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal; - barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; - barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - - commandBuffer->pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {}, barrier); - - endSingleTimeCommands(*commandBuffer); - } - - void createTextureImageView() { - textureImageView = createImageView(textureImage, vk::Format::eR8G8B8A8Srgb, vk::ImageAspectFlagBits::eColor, mipLevels); - } - - void createTextureSampler() { - vk::PhysicalDeviceProperties properties = physicalDevice.getProperties(); - vk::SamplerCreateInfo samplerInfo { - .magFilter = vk::Filter::eLinear, - .minFilter = vk::Filter::eLinear, - .mipmapMode = vk::SamplerMipmapMode::eLinear, - .addressModeU = vk::SamplerAddressMode::eRepeat, - .addressModeV = vk::SamplerAddressMode::eRepeat, - .addressModeW = vk::SamplerAddressMode::eRepeat, - .mipLodBias = 0.0f, - .anisotropyEnable = vk::True, - .maxAnisotropy = properties.limits.maxSamplerAnisotropy, - .compareEnable = vk::False, - .compareOp = vk::CompareOp::eAlways - }; - textureSampler = vk::raii::Sampler(device, samplerInfo); - } - - [[nodiscard]] vk::raii::ImageView createImageView(const vk::raii::Image& image, vk::Format format, vk::ImageAspectFlags aspectFlags, uint32_t mipLevels) const { - vk::ImageViewCreateInfo viewInfo{ - .image = image, - .viewType = vk::ImageViewType::e2D, - .format = format, - .subresourceRange = { aspectFlags, 0, mipLevels, 0, 1 } - }; - return vk::raii::ImageView( device, viewInfo ); - } - - void createImage(uint32_t width, uint32_t height, uint32_t mipLevels, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image& image, vk::raii::DeviceMemory& imageMemory) { - vk::ImageCreateInfo imageInfo{ - .imageType = vk::ImageType::e2D, - .format = format, - .extent = {width, height, 1}, - .mipLevels = mipLevels, - .arrayLayers = 1, - .samples = vk::SampleCountFlagBits::e1, - .tiling = tiling, - .usage = usage, - .sharingMode = vk::SharingMode::eExclusive, - .initialLayout = vk::ImageLayout::eUndefined - }; - image = vk::raii::Image(device, imageInfo); - - vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ - .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) - }; - imageMemory = vk::raii::DeviceMemory(device, allocInfo); - image.bindMemory(imageMemory, 0); - } - - void transitionImageLayout(const vk::raii::Image& image, const vk::ImageLayout oldLayout, const vk::ImageLayout newLayout, uint32_t mipLevels) { - const auto commandBuffer = beginSingleTimeCommands(); - - vk::ImageMemoryBarrier barrier{ - .oldLayout = oldLayout, - .newLayout = newLayout, - .image = image, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, mipLevels, 0, 1 } - }; - - vk::PipelineStageFlags sourceStage; - vk::PipelineStageFlags destinationStage; - - if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) { - barrier.srcAccessMask = {}; - barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; - - sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; - destinationStage = vk::PipelineStageFlagBits::eTransfer; - } else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) { - barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; - barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - - sourceStage = vk::PipelineStageFlagBits::eTransfer; - destinationStage = vk::PipelineStageFlagBits::eFragmentShader; - } else { - throw std::invalid_argument("unsupported layout transition!"); - } - commandBuffer->pipelineBarrier( sourceStage, destinationStage, {}, {}, nullptr, barrier ); - endSingleTimeCommands(*commandBuffer); - } - - void copyBufferToImage(const vk::raii::Buffer& buffer, const vk::raii::Image& image, uint32_t width, uint32_t height) { - std::unique_ptr commandBuffer = beginSingleTimeCommands(); - vk::BufferImageCopy region{ - .bufferOffset = 0, - .bufferRowLength = 0, - .bufferImageHeight = 0, - .imageSubresource = { vk::ImageAspectFlagBits::eColor, 0, 0, 1 }, - .imageOffset = {0, 0, 0}, - .imageExtent = {width, height, 1} - }; - commandBuffer->copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, {region}); - endSingleTimeCommands(*commandBuffer); - } - - void loadModel() { - tinyobj::attrib_t attrib; - std::vector shapes; - std::vector materials; - std::string warn, err; - - if (!LoadObj(&attrib, &shapes, &materials, &warn, &err, MODEL_PATH.c_str())) { - throw std::runtime_error(warn + err); - } - - std::unordered_map uniqueVertices{}; - - for (const auto& shape : shapes) { - for (const auto& index : shape.mesh.indices) { - Vertex vertex{}; - - vertex.pos = { - attrib.vertices[3 * index.vertex_index + 0], - attrib.vertices[3 * index.vertex_index + 1], - attrib.vertices[3 * index.vertex_index + 2] - }; - - vertex.texCoord = { - attrib.texcoords[2 * index.texcoord_index + 0], - 1.0f - attrib.texcoords[2 * index.texcoord_index + 1] - }; - - vertex.color = {1.0f, 1.0f, 1.0f}; - - if (!uniqueVertices.contains(vertex)) { - uniqueVertices[vertex] = static_cast(vertices.size()); - vertices.push_back(vertex); - } - - indices.push_back(uniqueVertices[vertex]); - } - } - } - - void createVertexBuffer() { - vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(dataStaging, vertices.data(), bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); - - copyBuffer(stagingBuffer, vertexBuffer, bufferSize); - } - - void createIndexBuffer() { - vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); - - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(data, indices.data(), bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); - - copyBuffer(stagingBuffer, indexBuffer, bufferSize); - } - - void createUniformBuffers() { - uniformBuffers.clear(); - uniformBuffersMemory.clear(); - uniformBuffersMapped.clear(); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DeviceSize bufferSize = sizeof(UniformBufferObject); - vk::raii::Buffer buffer({}); - vk::raii::DeviceMemory bufferMem({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); - uniformBuffers.emplace_back(std::move(buffer)); - uniformBuffersMemory.emplace_back(std::move(bufferMem)); - uniformBuffersMapped.emplace_back( uniformBuffersMemory[i].mapMemory(0, bufferSize)); - } - } - - void createDescriptorPool() { - std::array poolSize { - vk::DescriptorPoolSize( vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT), - vk::DescriptorPoolSize( vk::DescriptorType::eCombinedImageSampler, MAX_FRAMES_IN_FLIGHT) - }; - vk::DescriptorPoolCreateInfo poolInfo{ - .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, - .maxSets = MAX_FRAMES_IN_FLIGHT, - .poolSizeCount = static_cast(poolSize.size()), - .pPoolSizes = poolSize.data() - }; - descriptorPool = vk::raii::DescriptorPool(device, poolInfo); - } - - void createDescriptorSets() { - std::vector layouts(MAX_FRAMES_IN_FLIGHT, descriptorSetLayout); - vk::DescriptorSetAllocateInfo allocInfo{ - .descriptorPool = descriptorPool, - .descriptorSetCount = static_cast(layouts.size()), - .pSetLayouts = layouts.data() - }; - - descriptorSets.clear(); - descriptorSets = device.allocateDescriptorSets(allocInfo); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DescriptorBufferInfo bufferInfo{ - .buffer = uniformBuffers[i], - .offset = 0, - .range = sizeof(UniformBufferObject) - }; - vk::DescriptorImageInfo imageInfo{ - .sampler = textureSampler, - .imageView = textureImageView, - .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal - }; - std::array descriptorWrites{ - vk::WriteDescriptorSet{ - .dstSet = descriptorSets[i], - .dstBinding = 0, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eUniformBuffer, - .pBufferInfo = &bufferInfo - }, - vk::WriteDescriptorSet{ - .dstSet = descriptorSets[i], - .dstBinding = 1, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eCombinedImageSampler, - .pImageInfo = &imageInfo - } - }; - device.updateDescriptorSets(descriptorWrites, {}); - } - } - - void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer& buffer, vk::raii::DeviceMemory& bufferMemory) { - vk::BufferCreateInfo bufferInfo{ - .size = size, - .usage = usage, - .sharingMode = vk::SharingMode::eExclusive - }; - buffer = vk::raii::Buffer(device, bufferInfo); - vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ - .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) - }; - bufferMemory = vk::raii::DeviceMemory(device, allocInfo); - buffer.bindMemory(bufferMemory, 0); - } - - std::unique_ptr beginSingleTimeCommands() { - vk::CommandBufferAllocateInfo allocInfo{ - .commandPool = commandPool, - .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = 1 - }; - std::unique_ptr commandBuffer = std::make_unique(std::move(vk::raii::CommandBuffers(device, allocInfo).front())); - - vk::CommandBufferBeginInfo beginInfo{ - .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit - }; - commandBuffer->begin(beginInfo); - - return commandBuffer; - } - - void endSingleTimeCommands(const vk::raii::CommandBuffer& commandBuffer) const { - commandBuffer.end(); - - vk::SubmitInfo submitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandBuffer }; - queue.submit(submitInfo, nullptr); - queue.waitIdle(); - } - - void copyBuffer(vk::raii::Buffer & srcBuffer, vk::raii::Buffer & dstBuffer, vk::DeviceSize size) { - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1 }; - vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); - commandCopyBuffer.begin(vk::CommandBufferBeginInfo{ .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit }); - commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy{ .size = size }); - commandCopyBuffer.end(); - queue.submit(vk::SubmitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer }, nullptr); - queue.waitIdle(); - } - - uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) { - vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); - - for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { - if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { - return i; - } - } - - throw std::runtime_error("failed to find suitable memory type!"); - } - - void createCommandBuffers() { - commandBuffers.clear(); - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = MAX_FRAMES_IN_FLIGHT }; - commandBuffers = vk::raii::CommandBuffers(device, allocInfo); - } - - void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin({}); - // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL - transition_image_layout( - imageIndex, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage - ); - // Transition depth image to depth attachment optimal layout - vk::ImageMemoryBarrier2 depthBarrier = { - .srcStageMask = vk::PipelineStageFlagBits2::eTopOfPipe, - .srcAccessMask = {}, - .dstStageMask = vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, - .dstAccessMask = vk::AccessFlagBits2::eDepthStencilAttachmentRead | vk::AccessFlagBits2::eDepthStencilAttachmentWrite, - .oldLayout = vk::ImageLayout::eUndefined, - .newLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = depthImage, - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eDepth, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo depthDependencyInfo = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &depthBarrier - }; - commandBuffers[currentFrame].pipelineBarrier2(depthDependencyInfo); - - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::ClearValue clearDepth = vk::ClearDepthStencilValue(1.0f, 0); - - vk::RenderingAttachmentInfo colorAttachmentInfo = { - .imageView = swapChainImageViews[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor - }; - - vk::RenderingAttachmentInfo depthAttachmentInfo = { - .imageView = depthImageView, - .imageLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eDontCare, - .clearValue = clearDepth - }; - - vk::RenderingInfo renderingInfo = { - .renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &colorAttachmentInfo, - .pDepthAttachment = &depthAttachmentInfo - }; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].bindIndexBuffer( *indexBuffer, 0, vk::IndexType::eUint32 ); - commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); - commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); - commandBuffers[currentFrame].endRendering(); - // After rendering, transition the swapchain image to PRESENT_SRC - transition_image_layout( - imageIndex, - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage - ); - commandBuffers[currentFrame].end(); - } - - void transition_image_layout( - uint32_t imageIndex, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::AccessFlags2 src_access_mask, - vk::AccessFlags2 dst_access_mask, - vk::PipelineStageFlags2 src_stage_mask, - vk::PipelineStageFlags2 dst_stage_mask - ) { - vk::ImageMemoryBarrier2 barrier = { - .srcStageMask = src_stage_mask, - .srcAccessMask = src_access_mask, - .dstStageMask = dst_stage_mask, - .dstAccessMask = dst_access_mask, - .oldLayout = old_layout, - .newLayout = new_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); - } - - void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); - - for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - } - - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - inFlightFences.emplace_back(device, vk::FenceCreateInfo{ .flags = vk::FenceCreateFlagBits::eSignaled }); - } - } - - void updateUniformBuffer(uint32_t currentImage) const { - static auto startTime = std::chrono::high_resolution_clock::now(); - - auto currentTime = std::chrono::high_resolution_clock::now(); - float time = std::chrono::duration(currentTime - startTime).count(); - - UniformBufferObject ubo{}; - ubo.model = rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.view = lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); - ubo.proj[1][1] *= -1; - - memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); - } - - void drawFrame() { - while ( vk::Result::eTimeout == device.waitForFences( *inFlightFences[currentFrame], vk::True, UINT64_MAX ) ) - ; - auto [result, imageIndex] = swapChain.acquireNextImage( UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr ); - - if (result == vk::Result::eErrorOutOfDateKHR) { - recreateSwapChain(); - return; - } - if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) { - throw std::runtime_error("failed to acquire swap chain image!"); - } - updateUniformBuffer(currentFrame); - - device.resetFences( *inFlightFences[currentFrame] ); - commandBuffers[currentFrame].reset(); - recordCommandBuffer(imageIndex); - - vk::PipelineStageFlags waitDestinationStageMask( vk::PipelineStageFlagBits::eColorAttachmentOutput ); - const vk::SubmitInfo submitInfo{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], - .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], - .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex] }; - queue.submit(submitInfo, *inFlightFences[currentFrame]); - - - const vk::PresentInfoKHR presentInfoKHR{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], - .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex }; - result = queue.presentKHR(presentInfoKHR); - if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { - framebufferResized = false; - recreateSwapChain(); - } else if (result != vk::Result::eSuccess) { - throw std::runtime_error("failed to present swap chain image!"); - } - semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; - } - - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size(), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const & surfaceCapabilities) { - auto minImageCount = std::max( 3u, surfaceCapabilities.minImageCount ); - if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) { - minImageCount = surfaceCapabilities.maxImageCount; - } - return minImageCount; - } - - static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { - assert(!availableFormats.empty()); - const auto formatIt = std::ranges::find_if( - availableFormats, - []( const auto & format ) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; } ); - return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - assert(std::ranges::any_of(availablePresentModes, [](auto presentMode){ return presentMode == vk::PresentModeKHR::eFifo; })); - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eFifo; - } - - [[nodiscard]] vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) const { - if (capabilities.currentExtent.width != 0xFFFFFFFF) { - 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) - }; - } - - [[nodiscard]] std::vector getRequiredExtensions() const { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - 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; - } + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().features.samplerAnisotropy && + features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain featureChain = { + {.features = {.samplerAnisotropy = true}}, // vk::PhysicalDeviceFeatures2 + {.synchronization2 = true, .dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(*surface); + swapChainExtent = chooseSwapExtent(surfaceCapabilities); + swapChainSurfaceFormat = chooseSwapSurfaceFormat(physicalDevice.getSurfaceFormatsKHR(*surface)); + vk::SwapchainCreateInfoKHR swapChainCreateInfo{.surface = *surface, + .minImageCount = chooseSwapMinImageCount(surfaceCapabilities), + .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + void createImageViews() + { + vk::ImageViewCreateInfo imageViewCreateInfo{ + .viewType = vk::ImageViewType::e2D, + .format = swapChainSurfaceFormat.format, + .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + for (auto image : swapChainImages) + { + imageViewCreateInfo.image = image; + swapChainImageViews.emplace_back(device, imageViewCreateInfo); + } + } + + void createDescriptorSetLayout() + { + std::array bindings = { + vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr), + vk::DescriptorSetLayoutBinding(1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment, nullptr)}; + + vk::DescriptorSetLayoutCreateInfo layoutInfo{.bindingCount = static_cast(bindings.size()), .pBindings = bindings.data()}; + descriptorSetLayout = vk::raii::DescriptorSetLayout(device, layoutInfo); + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; + + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{ + .vertexBindingDescriptionCount = 1, + .pVertexBindingDescriptions = &bindingDescription, + .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), + .pVertexAttributeDescriptions = attributeDescriptions.data()}; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ + .topology = vk::PrimitiveTopology::eTriangleList, + .primitiveRestartEnable = vk::False}; + vk::PipelineViewportStateCreateInfo viewportState{ + .viewportCount = 1, + .scissorCount = 1}; + vk::PipelineRasterizationStateCreateInfo rasterizer{ + .depthClampEnable = vk::False, + .rasterizerDiscardEnable = vk::False, + .polygonMode = vk::PolygonMode::eFill, + .cullMode = vk::CullModeFlagBits::eBack, + .frontFace = vk::FrontFace::eCounterClockwise, + .depthBiasEnable = vk::False}; + rasterizer.lineWidth = 1.0f; + vk::PipelineMultisampleStateCreateInfo multisampling{ + .rasterizationSamples = vk::SampleCountFlagBits::e1, + .sampleShadingEnable = vk::False}; + vk::PipelineDepthStencilStateCreateInfo depthStencil{ + .depthTestEnable = vk::True, + .depthWriteEnable = vk::True, + .depthCompareOp = vk::CompareOp::eLess, + .depthBoundsTestEnable = vk::False, + .stencilTestEnable = vk::False}; + vk::PipelineColorBlendAttachmentState colorBlendAttachment; + colorBlendAttachment.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA; + colorBlendAttachment.blendEnable = vk::False; + + vk::PipelineColorBlendStateCreateInfo colorBlending{ + .logicOpEnable = vk::False, + .logicOp = vk::LogicOp::eCopy, + .attachmentCount = 1, + .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0}; + + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::Format depthFormat = findDepthFormat(); + vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ + .colorAttachmentCount = 1, + .pColorAttachmentFormats = &swapChainSurfaceFormat.format, + .depthAttachmentFormat = depthFormat}; + vk::GraphicsPipelineCreateInfo pipelineInfo{.pNext = &pipelineRenderingCreateInfo, + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pDepthStencilState = &depthStencil, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{ + .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex}; + commandPool = vk::raii::CommandPool(device, poolInfo); + } + + void createDepthResources() + { + vk::Format depthFormat = findDepthFormat(); + + createImage(swapChainExtent.width, swapChainExtent.height, 1, depthFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eDepthStencilAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, depthImage, depthImageMemory); + depthImageView = createImageView(depthImage, depthFormat, vk::ImageAspectFlagBits::eDepth, 1); + } + + vk::Format findSupportedFormat(const std::vector &candidates, vk::ImageTiling tiling, vk::FormatFeatureFlags features) const + { + for (const auto format : candidates) + { + vk::FormatProperties props = physicalDevice.getFormatProperties(format); + + if (tiling == vk::ImageTiling::eLinear && (props.linearTilingFeatures & features) == features) + { + return format; + } + if (tiling == vk::ImageTiling::eOptimal && (props.optimalTilingFeatures & features) == features) + { + return format; + } + } + + throw std::runtime_error("failed to find supported format!"); + } + + [[nodiscard]] vk::Format findDepthFormat() const + { + return findSupportedFormat( + {vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, vk::Format::eD24UnormS8Uint}, + vk::ImageTiling::eOptimal, + vk::FormatFeatureFlagBits::eDepthStencilAttachment); + } + + static bool hasStencilComponent(vk::Format format) + { + return format == vk::Format::eD32SfloatS8Uint || format == vk::Format::eD24UnormS8Uint; + } + + void createTextureImage() + { + int texWidth, texHeight, texChannels; + stbi_uc *pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + vk::DeviceSize imageSize = texWidth * texHeight * 4; + mipLevels = static_cast(std::floor(std::log2(std::max(texWidth, texHeight)))) + 1; + + if (!pixels) + { + throw std::runtime_error("failed to load texture image!"); + } + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, imageSize); + memcpy(data, pixels, imageSize); + stagingBufferMemory.unmapMemory(); + + stbi_image_free(pixels); + + createImage(texWidth, texHeight, mipLevels, vk::Format::eR8G8B8A8Srgb, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory); + + transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, mipLevels); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); + + generateMipmaps(textureImage, vk::Format::eR8G8B8A8Srgb, texWidth, texHeight, mipLevels); + } + + void generateMipmaps(vk::raii::Image &image, vk::Format imageFormat, int32_t texWidth, int32_t texHeight, uint32_t mipLevels) + { + // Check if image format supports linear blit-ing + vk::FormatProperties formatProperties = physicalDevice.getFormatProperties(imageFormat); + + if (!(formatProperties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eSampledImageFilterLinear)) + { + throw std::runtime_error("texture image format does not support linear blitting!"); + } + + std::unique_ptr commandBuffer = beginSingleTimeCommands(); + + vk::ImageMemoryBarrier barrier = {.srcAccessMask = vk::AccessFlagBits::eTransferWrite, .dstAccessMask = vk::AccessFlagBits::eTransferRead, .oldLayout = vk::ImageLayout::eTransferDstOptimal, .newLayout = vk::ImageLayout::eTransferSrcOptimal, .srcQueueFamilyIndex = vk::QueueFamilyIgnored, .dstQueueFamilyIndex = vk::QueueFamilyIgnored, .image = image}; + barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + barrier.subresourceRange.levelCount = 1; + + int32_t mipWidth = texWidth; + int32_t mipHeight = texHeight; + + for (uint32_t i = 1; i < mipLevels; i++) + { + barrier.subresourceRange.baseMipLevel = i - 1; + barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal; + barrier.newLayout = vk::ImageLayout::eTransferSrcOptimal; + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eTransferRead; + + commandBuffer->pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer, {}, {}, {}, barrier); + + vk::ArrayWrapper1D offsets, dstOffsets; + offsets[0] = vk::Offset3D(0, 0, 0); + offsets[1] = vk::Offset3D(mipWidth, mipHeight, 1); + dstOffsets[0] = vk::Offset3D(0, 0, 0); + dstOffsets[1] = vk::Offset3D(mipWidth > 1 ? mipWidth / 2 : 1, mipHeight > 1 ? mipHeight / 2 : 1, 1); + vk::ImageBlit blit = {.srcSubresource = {}, .srcOffsets = offsets, .dstSubresource = {}, .dstOffsets = dstOffsets}; + blit.srcSubresource = vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, i - 1, 0, 1); + blit.dstSubresource = vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, i, 0, 1); + + commandBuffer->blitImage(image, vk::ImageLayout::eTransferSrcOptimal, image, vk::ImageLayout::eTransferDstOptimal, {blit}, vk::Filter::eLinear); + + barrier.oldLayout = vk::ImageLayout::eTransferSrcOptimal; + barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal; + barrier.srcAccessMask = vk::AccessFlagBits::eTransferRead; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + + commandBuffer->pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {}, barrier); + + if (mipWidth > 1) + mipWidth /= 2; + if (mipHeight > 1) + mipHeight /= 2; + } + + barrier.subresourceRange.baseMipLevel = mipLevels - 1; + barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal; + barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal; + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + + commandBuffer->pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {}, barrier); + + endSingleTimeCommands(*commandBuffer); + } + + void createTextureImageView() + { + textureImageView = createImageView(textureImage, vk::Format::eR8G8B8A8Srgb, vk::ImageAspectFlagBits::eColor, mipLevels); + } + + void createTextureSampler() + { + vk::PhysicalDeviceProperties properties = physicalDevice.getProperties(); + vk::SamplerCreateInfo samplerInfo{ + .magFilter = vk::Filter::eLinear, + .minFilter = vk::Filter::eLinear, + .mipmapMode = vk::SamplerMipmapMode::eLinear, + .addressModeU = vk::SamplerAddressMode::eRepeat, + .addressModeV = vk::SamplerAddressMode::eRepeat, + .addressModeW = vk::SamplerAddressMode::eRepeat, + .mipLodBias = 0.0f, + .anisotropyEnable = vk::True, + .maxAnisotropy = properties.limits.maxSamplerAnisotropy, + .compareEnable = vk::False, + .compareOp = vk::CompareOp::eAlways}; + textureSampler = vk::raii::Sampler(device, samplerInfo); + } + + [[nodiscard]] vk::raii::ImageView createImageView(const vk::raii::Image &image, vk::Format format, vk::ImageAspectFlags aspectFlags, uint32_t mipLevels) const + { + vk::ImageViewCreateInfo viewInfo{ + .image = image, + .viewType = vk::ImageViewType::e2D, + .format = format, + .subresourceRange = {aspectFlags, 0, mipLevels, 0, 1}}; + return vk::raii::ImageView(device, viewInfo); + } + + void createImage(uint32_t width, uint32_t height, uint32_t mipLevels, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image &image, vk::raii::DeviceMemory &imageMemory) + { + vk::ImageCreateInfo imageInfo{ + .imageType = vk::ImageType::e2D, + .format = format, + .extent = {width, height, 1}, + .mipLevels = mipLevels, + .arrayLayers = 1, + .samples = vk::SampleCountFlagBits::e1, + .tiling = tiling, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive, + .initialLayout = vk::ImageLayout::eUndefined}; + image = vk::raii::Image(device, imageInfo); + + vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{ + .allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + imageMemory = vk::raii::DeviceMemory(device, allocInfo); + image.bindMemory(imageMemory, 0); + } + + void transitionImageLayout(const vk::raii::Image &image, const vk::ImageLayout oldLayout, const vk::ImageLayout newLayout, uint32_t mipLevels) + { + const auto commandBuffer = beginSingleTimeCommands(); + + vk::ImageMemoryBarrier barrier{ + .oldLayout = oldLayout, + .newLayout = newLayout, + .image = image, + .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, mipLevels, 0, 1}}; + + vk::PipelineStageFlags sourceStage; + vk::PipelineStageFlags destinationStage; + + if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) + { + barrier.srcAccessMask = {}; + barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; + + sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; + destinationStage = vk::PipelineStageFlagBits::eTransfer; + } + else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) + { + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + + sourceStage = vk::PipelineStageFlagBits::eTransfer; + destinationStage = vk::PipelineStageFlagBits::eFragmentShader; + } + else + { + throw std::invalid_argument("unsupported layout transition!"); + } + commandBuffer->pipelineBarrier(sourceStage, destinationStage, {}, {}, nullptr, barrier); + endSingleTimeCommands(*commandBuffer); + } + + void copyBufferToImage(const vk::raii::Buffer &buffer, const vk::raii::Image &image, uint32_t width, uint32_t height) + { + std::unique_ptr commandBuffer = beginSingleTimeCommands(); + vk::BufferImageCopy region{ + .bufferOffset = 0, + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource = {vk::ImageAspectFlagBits::eColor, 0, 0, 1}, + .imageOffset = {0, 0, 0}, + .imageExtent = {width, height, 1}}; + commandBuffer->copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, {region}); + endSingleTimeCommands(*commandBuffer); + } + + void loadModel() + { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn, err; + + if (!LoadObj(&attrib, &shapes, &materials, &warn, &err, MODEL_PATH.c_str())) + { + throw std::runtime_error(warn + err); + } + + std::unordered_map uniqueVertices{}; + + for (const auto &shape : shapes) + { + for (const auto &index : shape.mesh.indices) + { + Vertex vertex{}; + + vertex.pos = { + attrib.vertices[3 * index.vertex_index + 0], + attrib.vertices[3 * index.vertex_index + 1], + attrib.vertices[3 * index.vertex_index + 2]}; + + vertex.texCoord = { + attrib.texcoords[2 * index.texcoord_index + 0], + 1.0f - attrib.texcoords[2 * index.texcoord_index + 1]}; + + vertex.color = {1.0f, 1.0f, 1.0f}; + + if (!uniqueVertices.contains(vertex)) + { + uniqueVertices[vertex] = static_cast(vertices.size()); + vertices.push_back(vertex); + } + + indices.push_back(uniqueVertices[vertex]); + } + } + } + + void createVertexBuffer() + { + vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(dataStaging, vertices.data(), bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + } + + void createIndexBuffer() + { + vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(data, indices.data(), bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); + + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + } + + void createUniformBuffers() + { + uniformBuffers.clear(); + uniformBuffersMemory.clear(); + uniformBuffersMapped.clear(); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DeviceSize bufferSize = sizeof(UniformBufferObject); + vk::raii::Buffer buffer({}); + vk::raii::DeviceMemory bufferMem({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); + uniformBuffers.emplace_back(std::move(buffer)); + uniformBuffersMemory.emplace_back(std::move(bufferMem)); + uniformBuffersMapped.emplace_back(uniformBuffersMemory[i].mapMemory(0, bufferSize)); + } + } + + void createDescriptorPool() + { + std::array poolSize{ + vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT), + vk::DescriptorPoolSize(vk::DescriptorType::eCombinedImageSampler, MAX_FRAMES_IN_FLIGHT)}; + vk::DescriptorPoolCreateInfo poolInfo{ + .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, + .maxSets = MAX_FRAMES_IN_FLIGHT, + .poolSizeCount = static_cast(poolSize.size()), + .pPoolSizes = poolSize.data()}; + descriptorPool = vk::raii::DescriptorPool(device, poolInfo); + } + + void createDescriptorSets() + { + std::vector layouts(MAX_FRAMES_IN_FLIGHT, descriptorSetLayout); + vk::DescriptorSetAllocateInfo allocInfo{ + .descriptorPool = descriptorPool, + .descriptorSetCount = static_cast(layouts.size()), + .pSetLayouts = layouts.data()}; + + descriptorSets.clear(); + descriptorSets = device.allocateDescriptorSets(allocInfo); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DescriptorBufferInfo bufferInfo{ + .buffer = uniformBuffers[i], + .offset = 0, + .range = sizeof(UniformBufferObject)}; + vk::DescriptorImageInfo imageInfo{ + .sampler = textureSampler, + .imageView = textureImageView, + .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal}; + std::array descriptorWrites{ + vk::WriteDescriptorSet{ + .dstSet = descriptorSets[i], + .dstBinding = 0, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eUniformBuffer, + .pBufferInfo = &bufferInfo}, + vk::WriteDescriptorSet{ + .dstSet = descriptorSets[i], + .dstBinding = 1, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .pImageInfo = &imageInfo}}; + device.updateDescriptorSets(descriptorWrites, {}); + } + } + + void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer &buffer, vk::raii::DeviceMemory &bufferMemory) + { + vk::BufferCreateInfo bufferInfo{ + .size = size, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive}; + buffer = vk::raii::Buffer(device, bufferInfo); + vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{ + .allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + bufferMemory = vk::raii::DeviceMemory(device, allocInfo); + buffer.bindMemory(bufferMemory, 0); + } + + std::unique_ptr beginSingleTimeCommands() + { + vk::CommandBufferAllocateInfo allocInfo{ + .commandPool = commandPool, + .level = vk::CommandBufferLevel::ePrimary, + .commandBufferCount = 1}; + std::unique_ptr commandBuffer = std::make_unique(std::move(vk::raii::CommandBuffers(device, allocInfo).front())); + + vk::CommandBufferBeginInfo beginInfo{ + .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; + commandBuffer->begin(beginInfo); + + return commandBuffer; + } + + void endSingleTimeCommands(const vk::raii::CommandBuffer &commandBuffer) const + { + commandBuffer.end(); + + vk::SubmitInfo submitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandBuffer}; + queue.submit(submitInfo, nullptr); + queue.waitIdle(); + } + + void copyBuffer(vk::raii::Buffer &srcBuffer, vk::raii::Buffer &dstBuffer, vk::DeviceSize size) + { + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1}; + vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); + commandCopyBuffer.begin(vk::CommandBufferBeginInfo{.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); + commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy{.size = size}); + commandCopyBuffer.end(); + queue.submit(vk::SubmitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer}, nullptr); + queue.waitIdle(); + } + + uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) + { + vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) + { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) + { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + void createCommandBuffers() + { + commandBuffers.clear(); + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = MAX_FRAMES_IN_FLIGHT}; + commandBuffers = vk::raii::CommandBuffers(device, allocInfo); + } + + void recordCommandBuffer(uint32_t imageIndex) + { + commandBuffers[currentFrame].begin({}); + // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL + transition_image_layout( + imageIndex, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage + ); + // Transition depth image to depth attachment optimal layout + vk::ImageMemoryBarrier2 depthBarrier = { + .srcStageMask = vk::PipelineStageFlagBits2::eTopOfPipe, + .srcAccessMask = {}, + .dstStageMask = vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, + .dstAccessMask = vk::AccessFlagBits2::eDepthStencilAttachmentRead | vk::AccessFlagBits2::eDepthStencilAttachmentWrite, + .oldLayout = vk::ImageLayout::eUndefined, + .newLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = depthImage, + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eDepth, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo depthDependencyInfo = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &depthBarrier}; + commandBuffers[currentFrame].pipelineBarrier2(depthDependencyInfo); + + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::ClearValue clearDepth = vk::ClearDepthStencilValue(1.0f, 0); + + vk::RenderingAttachmentInfo colorAttachmentInfo = { + .imageView = swapChainImageViews[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor}; + + vk::RenderingAttachmentInfo depthAttachmentInfo = { + .imageView = depthImageView, + .imageLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eDontCare, + .clearValue = clearDepth}; + + vk::RenderingInfo renderingInfo = { + .renderArea = {.offset = {0, 0}, .extent = swapChainExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &colorAttachmentInfo, + .pDepthAttachment = &depthAttachmentInfo}; + commandBuffers[currentFrame].beginRendering(renderingInfo); + commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint32); + commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); + commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); + commandBuffers[currentFrame].endRendering(); + // After rendering, transition the swapchain image to PRESENT_SRC + transition_image_layout( + imageIndex, + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage + ); + commandBuffers[currentFrame].end(); + } + + void transition_image_layout( + uint32_t imageIndex, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout, + vk::AccessFlags2 src_access_mask, + vk::AccessFlags2 dst_access_mask, + vk::PipelineStageFlags2 src_stage_mask, + vk::PipelineStageFlags2 dst_stage_mask) + { + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = src_stage_mask, + .srcAccessMask = src_access_mask, + .dstStageMask = dst_stage_mask, + .dstAccessMask = dst_access_mask, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo dependency_info = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + } + + void createSyncObjects() + { + presentCompleteSemaphore.clear(); + renderFinishedSemaphore.clear(); + inFlightFences.clear(); + + for (size_t i = 0; i < swapChainImages.size(); i++) + { + presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); + } + } + + void updateUniformBuffer(uint32_t currentImage) const + { + static auto startTime = std::chrono::high_resolution_clock::now(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + UniformBufferObject ubo{}; + ubo.model = rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.view = lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); + ubo.proj[1][1] *= -1; + + memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); + } + + void drawFrame() + { + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + ; + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr); + + if (result == vk::Result::eErrorOutOfDateKHR) + { + recreateSwapChain(); + return; + } + if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) + { + throw std::runtime_error("failed to acquire swap chain image!"); + } + updateUniformBuffer(currentFrame); + + device.resetFences(*inFlightFences[currentFrame]); + commandBuffers[currentFrame].reset(); + recordCommandBuffer(imageIndex); + + vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[currentFrame]); + + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + result = queue.presentKHR(presentInfoKHR); + if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) + { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != vk::Result::eSuccess) + { + throw std::runtime_error("failed to present swap chain image!"); + } + semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size(), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const &surfaceCapabilities) + { + auto minImageCount = std::max(3u, surfaceCapabilities.minImageCount); + if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) + { + minImageCount = surfaceCapabilities.maxImageCount; + } + return minImageCount; + } + + static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + assert(!availableFormats.empty()); + const auto formatIt = std::ranges::find_if( + availableFormats, + [](const auto &format) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; }); + return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + assert(std::ranges::any_of(availablePresentModes, [](auto presentMode) { return presentMode == vk::PresentModeKHR::eFifo; })); + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + vk::PresentModeKHR::eFifo; + } + + [[nodiscard]] vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR &capabilities) const + { + if (capabilities.currentExtent.width != 0xFFFFFFFF) + { + 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)}; + } + + [[nodiscard]] std::vector getRequiredExtensions() const + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + 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; + } }; -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/30_multisampling.cpp b/attachments/30_multisampling.cpp index ed8132fb..25f33d97 100644 --- a/attachments/30_multisampling.cpp +++ b/attachments/30_multisampling.cpp @@ -12,14 +12,14 @@ #include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include #define GLM_FORCE_RADIANS @@ -35,16 +35,15 @@ import vulkan_hpp; #define TINYOBJLOADER_IMPLEMENTATION #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -constexpr uint64_t FenceTimeout = 100000000; -const std::string MODEL_PATH = "models/viking_room.obj"; -const std::string TEXTURE_PATH = "textures/viking_room.png"; -constexpr int MAX_FRAMES_IN_FLIGHT = 2; +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; +constexpr uint64_t FenceTimeout = 100000000; +const std::string MODEL_PATH = "models/viking_room.obj"; +const std::string TEXTURE_PATH = "textures/viking_room.png"; +constexpr int MAX_FRAMES_IN_FLIGHT = 2; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -52,1265 +51,1329 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -struct Vertex { - glm::vec3 pos; - glm::vec3 color; - glm::vec2 texCoord; - - static vk::VertexInputBindingDescription getBindingDescription() { - return { 0, sizeof(Vertex), vk::VertexInputRate::eVertex }; - } - - static std::array getAttributeDescriptions() { - return { - vk::VertexInputAttributeDescription( 0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, pos) ), - vk::VertexInputAttributeDescription( 1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color) ), - vk::VertexInputAttributeDescription( 2, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, texCoord) ) - }; - } - - bool operator==(const Vertex& other) const { - return pos == other.pos && color == other.color && texCoord == other.texCoord; - } +struct Vertex +{ + glm::vec3 pos; + glm::vec3 color; + glm::vec2 texCoord; + + static vk::VertexInputBindingDescription getBindingDescription() + { + return {0, sizeof(Vertex), vk::VertexInputRate::eVertex}; + } + + static std::array getAttributeDescriptions() + { + return { + vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, pos)), + vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color)), + vk::VertexInputAttributeDescription(2, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, texCoord))}; + } + + bool operator==(const Vertex &other) const + { + return pos == other.pos && color == other.color && texCoord == other.texCoord; + } }; -template<> struct std::hash { - size_t operator()(Vertex const& vertex) const noexcept { - return ((hash()(vertex.pos) ^ (hash()(vertex.color) << 1)) >> 1) ^ (hash()(vertex.texCoord) << 1); - } - }; +template <> +struct std::hash +{ + size_t operator()(Vertex const &vertex) const noexcept + { + return ((hash()(vertex.pos) ^ (hash()(vertex.color) << 1)) >> 1) ^ (hash()(vertex.texCoord) << 1); + } +}; -struct UniformBufferObject { - alignas(16) glm::mat4 model; - alignas(16) glm::mat4 view; - alignas(16) glm::mat4 proj; +struct UniformBufferObject +{ + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; }; -class HelloTriangleApplication { -public: - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } - -private: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::SampleCountFlagBits msaaSamples = vk::SampleCountFlagBits::e1; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::SurfaceFormatKHR swapChainSurfaceFormat; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - - vk::raii::Image colorImage = nullptr; - vk::raii::DeviceMemory colorImageMemory = nullptr; - vk::raii::ImageView colorImageView = nullptr; - - vk::raii::Image depthImage = nullptr; - vk::raii::DeviceMemory depthImageMemory = nullptr; - vk::raii::ImageView depthImageView = nullptr; - - uint32_t mipLevels = 0; - vk::raii::Image textureImage = nullptr; - vk::raii::DeviceMemory textureImageMemory = nullptr; - vk::raii::ImageView textureImageView = nullptr; - vk::raii::Sampler textureSampler = nullptr; - - std::vector vertices; - std::vector indices; - vk::raii::Buffer vertexBuffer = nullptr; - vk::raii::DeviceMemory vertexBufferMemory = nullptr; - vk::raii::Buffer indexBuffer = nullptr; - vk::raii::DeviceMemory indexBufferMemory = nullptr; - - std::vector uniformBuffers; - std::vector uniformBuffersMemory; - std::vector uniformBuffersMapped; - - vk::raii::DescriptorPool descriptorPool = nullptr; - std::vector descriptorSets; - - vk::raii::CommandPool commandPool = nullptr; - std::vector commandBuffers; - - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; - std::vector inFlightFences; - uint32_t semaphoreIndex = 0; - uint32_t currentFrame = 0; - - bool framebufferResized = false; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); - } - - static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { - auto app = static_cast(glfwGetWindowUserPointer(window)); - app->framebufferResized = true; - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - createSurface(); - pickPhysicalDevice(); - msaaSamples = getMaxUsableSampleCount(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createDescriptorSetLayout(); - createGraphicsPipeline(); - createCommandPool(); - createColorResources(); - createDepthResources(); - createTextureImage(); - createTextureImageView(); - createTextureSampler(); - loadModel(); - createVertexBuffer(); - createIndexBuffer(); - createUniformBuffers(); - createDescriptorPool(); - createDescriptorSets(); - createCommandBuffers(); - createSyncObjects(); - } - - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - drawFrame(); - } - - device.waitIdle(); - } - - void cleanupSwapChain() { - swapChainImageViews.clear(); - swapChain = nullptr; - } - - void cleanup() const { - glfwDestroyWindow(window); - - glfwTerminate(); - } - - void recreateSwapChain() { - int width = 0, height = 0; - glfwGetFramebufferSize(window, &width, &height); - while (width == 0 || height == 0) { - glfwGetFramebufferSize(window, &width, &height); - glfwWaitEvents(); - } - - device.waitIdle(); - - cleanupSwapChain(); - createSwapChain(); - createImageViews(); - createColorResources(); - createDepthResources(); - } - - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations +class HelloTriangleApplication +{ + public: + void run() + { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + + private: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::SampleCountFlagBits msaaSamples = vk::SampleCountFlagBits::e1; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::SurfaceFormatKHR swapChainSurfaceFormat; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + + vk::raii::Image colorImage = nullptr; + vk::raii::DeviceMemory colorImageMemory = nullptr; + vk::raii::ImageView colorImageView = nullptr; + + vk::raii::Image depthImage = nullptr; + vk::raii::DeviceMemory depthImageMemory = nullptr; + vk::raii::ImageView depthImageView = nullptr; + + uint32_t mipLevels = 0; + vk::raii::Image textureImage = nullptr; + vk::raii::DeviceMemory textureImageMemory = nullptr; + vk::raii::ImageView textureImageView = nullptr; + vk::raii::Sampler textureSampler = nullptr; + + std::vector vertices; + std::vector indices; + vk::raii::Buffer vertexBuffer = nullptr; + vk::raii::DeviceMemory vertexBufferMemory = nullptr; + vk::raii::Buffer indexBuffer = nullptr; + vk::raii::DeviceMemory indexBufferMemory = nullptr; + + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + std::vector uniformBuffersMapped; + + vk::raii::DescriptorPool descriptorPool = nullptr; + std::vector descriptorSets; + + vk::raii::CommandPool commandPool = nullptr; + std::vector commandBuffers; + + std::vector presentCompleteSemaphore; + std::vector renderFinishedSemaphore; + std::vector inFlightFences; + uint32_t semaphoreIndex = 0; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow *window, int width, int height) + { + auto app = static_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + msaaSamples = getMaxUsableSampleCount(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createDescriptorSetLayout(); + createGraphicsPipeline(); + createCommandPool(); + createColorResources(); + createDepthResources(); + createTextureImage(); + createTextureImageView(); + createTextureSampler(); + loadModel(); + createVertexBuffer(); + createIndexBuffer(); + createUniformBuffers(); + createDescriptorPool(); + createDescriptorSets(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() + { + while (!glfwWindowShouldClose(window)) + { + glfwPollEvents(); + drawFrame(); + } + + device.waitIdle(); + } + + void cleanupSwapChain() + { + swapChainImageViews.clear(); + swapChain = nullptr; + } + + void cleanup() const + { + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() + { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) + { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + device.waitIdle(); + + cleanupSwapChain(); + createSwapChain(); + createImageViews(); + createColorResources(); + createDepthResources(); + } + + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().features.samplerAnisotropy && - features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - }); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error("failed to find a suitable GPU!"); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain featureChain = { - {.features = {.samplerAnisotropy = true } }, // vk::PhysicalDeviceFeatures2 - {.synchronization2 = true, .dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( *surface ); - swapChainExtent = chooseSwapExtent( surfaceCapabilities ); - swapChainSurfaceFormat = chooseSwapSurfaceFormat( physicalDevice.getSurfaceFormatsKHR( *surface ) ); - vk::SwapchainCreateInfoKHR swapChainCreateInfo{ .surface = *surface, - .minImageCount = chooseSwapMinImageCount( surfaceCapabilities ), - .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 }; - - swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); - swapChainImages = swapChain.getImages(); - } - - void createImageViews() { - vk::ImageViewCreateInfo imageViewCreateInfo{ - .viewType = vk::ImageViewType::e2D, - .format = swapChainSurfaceFormat.format, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } - }; - for ( auto image : swapChainImages ) - { - imageViewCreateInfo.image = image; - swapChainImageViews.emplace_back( device, imageViewCreateInfo ); - } - } - - void createDescriptorSetLayout() { - std::array bindings = { - vk::DescriptorSetLayoutBinding( 0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr), - vk::DescriptorSetLayoutBinding( 1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment, nullptr) - }; - - vk::DescriptorSetLayoutCreateInfo layoutInfo{ .bindingCount = static_cast(bindings.size()), .pBindings = bindings.data() }; - descriptorSetLayout = vk::raii::DescriptorSetLayout(device, layoutInfo); - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; - - auto bindingDescription = Vertex::getBindingDescription(); - auto attributeDescriptions = Vertex::getAttributeDescriptions(); - vk::PipelineVertexInputStateCreateInfo vertexInputInfo{ - .vertexBindingDescriptionCount = 1, - .pVertexBindingDescriptions = &bindingDescription, - .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), - .pVertexAttributeDescriptions = attributeDescriptions.data() - }; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ - .topology = vk::PrimitiveTopology::eTriangleList, - .primitiveRestartEnable = vk::False - }; - vk::PipelineViewportStateCreateInfo viewportState{ - .viewportCount = 1, - .scissorCount = 1 - }; - vk::PipelineRasterizationStateCreateInfo rasterizer{ - .depthClampEnable = vk::False, - .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, - .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eCounterClockwise, - .depthBiasEnable = vk::False - }; - rasterizer.lineWidth = 1.0f; - vk::PipelineMultisampleStateCreateInfo multisampling{ - .rasterizationSamples = msaaSamples, - .sampleShadingEnable = vk::False - }; - vk::PipelineDepthStencilStateCreateInfo depthStencil{ - .depthTestEnable = vk::True, - .depthWriteEnable = vk::True, - .depthCompareOp = vk::CompareOp::eLess, - .depthBoundsTestEnable = vk::False, - .stencilTestEnable = vk::False - }; - vk::PipelineColorBlendAttachmentState colorBlendAttachment; - colorBlendAttachment.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA; - colorBlendAttachment.blendEnable = vk::False; - - vk::PipelineColorBlendStateCreateInfo colorBlending{ - .logicOpEnable = vk::False, - .logicOp = vk::LogicOp::eCopy, - .attachmentCount = 1, - .pAttachments = &colorBlendAttachment - }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0 }; - - pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); - - vk::Format depthFormat = findDepthFormat(); - vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ - .colorAttachmentCount = 1, - .pColorAttachmentFormats = &swapChainSurfaceFormat.format, - .depthAttachmentFormat = depthFormat - }; - vk::GraphicsPipelineCreateInfo pipelineInfo{ .pNext = &pipelineRenderingCreateInfo, - .stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pDepthStencilState = &depthStencil, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = pipelineLayout, - .renderPass = nullptr - }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{ - .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex - }; - commandPool = vk::raii::CommandPool(device, poolInfo); - } - - void createColorResources() { - vk::Format colorFormat = swapChainSurfaceFormat.format; - - createImage(swapChainExtent.width, swapChainExtent.height, 1, msaaSamples, colorFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransientAttachment | vk::ImageUsageFlagBits::eColorAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, colorImage, colorImageMemory); - colorImageView = createImageView(colorImage, colorFormat, vk::ImageAspectFlagBits::eColor, 1); - } - - void createDepthResources() { - vk::Format depthFormat = findDepthFormat(); - - createImage(swapChainExtent.width, swapChainExtent.height, 1, msaaSamples, depthFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eDepthStencilAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, depthImage, depthImageMemory); - depthImageView = createImageView(depthImage, depthFormat, vk::ImageAspectFlagBits::eDepth, 1); - } - - vk::Format findSupportedFormat(const std::vector& candidates, vk::ImageTiling tiling, vk::FormatFeatureFlags features) const { - for (const auto format : candidates) { - vk::FormatProperties props = physicalDevice.getFormatProperties(format); - - if (tiling == vk::ImageTiling::eLinear && (props.linearTilingFeatures & features) == features) { - return format; - } - if (tiling == vk::ImageTiling::eOptimal && (props.optimalTilingFeatures & features) == features) { - return format; - } - } - - throw std::runtime_error("failed to find supported format!"); - } - - [[nodiscard]] vk::Format findDepthFormat() const { - return findSupportedFormat( - {vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, vk::Format::eD24UnormS8Uint}, - vk::ImageTiling::eOptimal, - vk::FormatFeatureFlagBits::eDepthStencilAttachment - ); - } - - static bool hasStencilComponent(vk::Format format) { - return format == vk::Format::eD32SfloatS8Uint || format == vk::Format::eD24UnormS8Uint; - } - - void createTextureImage() { - int texWidth, texHeight, texChannels; - stbi_uc* pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); - vk::DeviceSize imageSize = texWidth * texHeight * 4; - mipLevels = static_cast(std::floor(std::log2(std::max(texWidth, texHeight)))) + 1; - - if (!pixels) { - throw std::runtime_error("failed to load texture image!"); - } - - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, imageSize); - memcpy(data, pixels, imageSize); - stagingBufferMemory.unmapMemory(); - - stbi_image_free(pixels); - - createImage(texWidth, texHeight, mipLevels, vk::SampleCountFlagBits::e1, vk::Format::eR8G8B8A8Srgb, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory); - - transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, mipLevels); - copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); - - generateMipmaps(textureImage, vk::Format::eR8G8B8A8Srgb, texWidth, texHeight, mipLevels); - } - - void generateMipmaps(vk::raii::Image& image, vk::Format imageFormat, int32_t texWidth, int32_t texHeight, uint32_t mipLevels) { - // Check if image format supports linear blit-ing - vk::FormatProperties formatProperties = physicalDevice.getFormatProperties(imageFormat); - - if (!(formatProperties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eSampledImageFilterLinear)) { - throw std::runtime_error("texture image format does not support linear blitting!"); - } - - std::unique_ptr commandBuffer = beginSingleTimeCommands(); - - vk::ImageMemoryBarrier barrier = { .srcAccessMask = vk::AccessFlagBits::eTransferWrite, .dstAccessMask =vk::AccessFlagBits::eTransferRead - , .oldLayout = vk::ImageLayout::eTransferDstOptimal, .newLayout = vk::ImageLayout::eTransferSrcOptimal - , .srcQueueFamilyIndex = vk::QueueFamilyIgnored, .dstQueueFamilyIndex = vk::QueueFamilyIgnored, .image = image }; - barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; - barrier.subresourceRange.baseArrayLayer = 0; - barrier.subresourceRange.layerCount = 1; - barrier.subresourceRange.levelCount = 1; - - int32_t mipWidth = texWidth; - int32_t mipHeight = texHeight; - - for (uint32_t i = 1; i < mipLevels; i++) { - barrier.subresourceRange.baseMipLevel = i - 1; - barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal; - barrier.newLayout = vk::ImageLayout::eTransferSrcOptimal; - barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; - barrier.dstAccessMask = vk::AccessFlagBits::eTransferRead; - - commandBuffer->pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer, {}, {}, {}, barrier); - - vk::ArrayWrapper1D offsets, dstOffsets; - offsets[0] = vk::Offset3D(0, 0, 0); - offsets[1] = vk::Offset3D(mipWidth, mipHeight, 1); - dstOffsets[0] = vk::Offset3D(0, 0, 0); - dstOffsets[1] = vk::Offset3D(mipWidth > 1 ? mipWidth / 2 : 1, mipHeight > 1 ? mipHeight / 2 : 1, 1); - vk::ImageBlit blit = { .srcSubresource = {}, .srcOffsets = offsets, - .dstSubresource = {}, .dstOffsets = dstOffsets }; - blit.srcSubresource = vk::ImageSubresourceLayers( vk::ImageAspectFlagBits::eColor, i - 1, 0, 1); - blit.dstSubresource = vk::ImageSubresourceLayers( vk::ImageAspectFlagBits::eColor, i, 0, 1); - - commandBuffer->blitImage(image, vk::ImageLayout::eTransferSrcOptimal, image, vk::ImageLayout::eTransferDstOptimal, { blit }, vk::Filter::eLinear); - - barrier.oldLayout = vk::ImageLayout::eTransferSrcOptimal; - barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal; - barrier.srcAccessMask = vk::AccessFlagBits::eTransferRead; - barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - - commandBuffer->pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {}, barrier); - - if (mipWidth > 1) mipWidth /= 2; - if (mipHeight > 1) mipHeight /= 2; - } - - barrier.subresourceRange.baseMipLevel = mipLevels - 1; - barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal; - barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal; - barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; - barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - - commandBuffer->pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {}, barrier); - - endSingleTimeCommands(*commandBuffer); - } - - vk::SampleCountFlagBits getMaxUsableSampleCount() { - vk::PhysicalDeviceProperties physicalDeviceProperties = physicalDevice.getProperties(); - - vk::SampleCountFlags counts = physicalDeviceProperties.limits.framebufferColorSampleCounts & physicalDeviceProperties.limits.framebufferDepthSampleCounts; - if (counts & vk::SampleCountFlagBits::e64) { return vk::SampleCountFlagBits::e64; } - if (counts & vk::SampleCountFlagBits::e32) { return vk::SampleCountFlagBits::e32; } - if (counts & vk::SampleCountFlagBits::e16) { return vk::SampleCountFlagBits::e16; } - if (counts & vk::SampleCountFlagBits::e8) { return vk::SampleCountFlagBits::e8; } - if (counts & vk::SampleCountFlagBits::e4) { return vk::SampleCountFlagBits::e4; } - if (counts & vk::SampleCountFlagBits::e2) { return vk::SampleCountFlagBits::e2; } - - return vk::SampleCountFlagBits::e1; - } - - void createTextureImageView() { - textureImageView = createImageView(textureImage, vk::Format::eR8G8B8A8Srgb, vk::ImageAspectFlagBits::eColor, mipLevels); - } - - void createTextureSampler() { - vk::PhysicalDeviceProperties properties = physicalDevice.getProperties(); - vk::SamplerCreateInfo samplerInfo { - .magFilter = vk::Filter::eLinear, - .minFilter = vk::Filter::eLinear, - .mipmapMode = vk::SamplerMipmapMode::eLinear, - .addressModeU = vk::SamplerAddressMode::eRepeat, - .addressModeV = vk::SamplerAddressMode::eRepeat, - .addressModeW = vk::SamplerAddressMode::eRepeat, - .mipLodBias = 0.0f, - .anisotropyEnable = vk::True, - .maxAnisotropy = properties.limits.maxSamplerAnisotropy, - .compareEnable = vk::False, - .compareOp = vk::CompareOp::eAlways - }; - textureSampler = vk::raii::Sampler(device, samplerInfo); - } - - [[nodiscard]] vk::raii::ImageView createImageView(const vk::raii::Image& image, vk::Format format, vk::ImageAspectFlags aspectFlags, uint32_t mipLevels) const { - vk::ImageViewCreateInfo viewInfo{ - .image = image, - .viewType = vk::ImageViewType::e2D, - .format = format, - .subresourceRange = { aspectFlags, 0, mipLevels, 0, 1 } - }; - return vk::raii::ImageView( device, viewInfo ); - } - - void createImage(uint32_t width, uint32_t height, uint32_t mipLevels, vk::SampleCountFlagBits numSamples, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image& image, vk::raii::DeviceMemory& imageMemory) { - vk::ImageCreateInfo imageInfo{ - .imageType = vk::ImageType::e2D, - .format = format, - .extent = {width, height, 1}, - .mipLevels = mipLevels, - .arrayLayers = 1, - .samples = numSamples, - .tiling = tiling, - .usage = usage, - .sharingMode = vk::SharingMode::eExclusive, - .initialLayout = vk::ImageLayout::eUndefined - }; - image = vk::raii::Image(device, imageInfo); - - vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ - .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) - }; - imageMemory = vk::raii::DeviceMemory(device, allocInfo); - image.bindMemory(imageMemory, 0); - } - - void transitionImageLayout(const vk::raii::Image& image, const vk::ImageLayout oldLayout, const vk::ImageLayout newLayout, uint32_t mipLevels) { - const auto commandBuffer = beginSingleTimeCommands(); - - vk::ImageMemoryBarrier barrier{ - .oldLayout = oldLayout, - .newLayout = newLayout, - .image = image, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, mipLevels, 0, 1 } - }; - - vk::PipelineStageFlags sourceStage; - vk::PipelineStageFlags destinationStage; - - if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) { - barrier.srcAccessMask = {}; - barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; - - sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; - destinationStage = vk::PipelineStageFlagBits::eTransfer; - } else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) { - barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; - barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - - sourceStage = vk::PipelineStageFlagBits::eTransfer; - destinationStage = vk::PipelineStageFlagBits::eFragmentShader; - } else { - throw std::invalid_argument("unsupported layout transition!"); - } - commandBuffer->pipelineBarrier( sourceStage, destinationStage, {}, {}, nullptr, barrier ); - endSingleTimeCommands(*commandBuffer); - } - - void copyBufferToImage(const vk::raii::Buffer& buffer, const vk::raii::Image& image, uint32_t width, uint32_t height) { - std::unique_ptr commandBuffer = beginSingleTimeCommands(); - vk::BufferImageCopy region{ - .bufferOffset = 0, - .bufferRowLength = 0, - .bufferImageHeight = 0, - .imageSubresource = { vk::ImageAspectFlagBits::eColor, 0, 0, 1 }, - .imageOffset = {0, 0, 0}, - .imageExtent = {width, height, 1} - }; - commandBuffer->copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, {region}); - endSingleTimeCommands(*commandBuffer); - } - - void loadModel() { - tinyobj::attrib_t attrib; - std::vector shapes; - std::vector materials; - std::string warn, err; - - if (!LoadObj(&attrib, &shapes, &materials, &warn, &err, MODEL_PATH.c_str())) { - throw std::runtime_error(warn + err); - } - - std::unordered_map uniqueVertices{}; - - for (const auto& shape : shapes) { - for (const auto& index : shape.mesh.indices) { - Vertex vertex{}; - - vertex.pos = { - attrib.vertices[3 * index.vertex_index + 0], - attrib.vertices[3 * index.vertex_index + 1], - attrib.vertices[3 * index.vertex_index + 2] - }; - - vertex.texCoord = { - attrib.texcoords[2 * index.texcoord_index + 0], - 1.0f - attrib.texcoords[2 * index.texcoord_index + 1] - }; - - vertex.color = {1.0f, 1.0f, 1.0f}; - - if (!uniqueVertices.contains(vertex)) { - uniqueVertices[vertex] = static_cast(vertices.size()); - vertices.push_back(vertex); - } - - indices.push_back(uniqueVertices[vertex]); - } - } - } - - void createVertexBuffer() { - vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(dataStaging, vertices.data(), bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); - - copyBuffer(stagingBuffer, vertexBuffer, bufferSize); - } - - void createIndexBuffer() { - vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); - - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(data, indices.data(), bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); - - copyBuffer(stagingBuffer, indexBuffer, bufferSize); - } - - void createUniformBuffers() { - uniformBuffers.clear(); - uniformBuffersMemory.clear(); - uniformBuffersMapped.clear(); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DeviceSize bufferSize = sizeof(UniformBufferObject); - vk::raii::Buffer buffer({}); - vk::raii::DeviceMemory bufferMem({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); - uniformBuffers.emplace_back(std::move(buffer)); - uniformBuffersMemory.emplace_back(std::move(bufferMem)); - uniformBuffersMapped.emplace_back( uniformBuffersMemory[i].mapMemory(0, bufferSize)); - } - } - - void createDescriptorPool() { - std::array poolSize { - vk::DescriptorPoolSize( vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT), - vk::DescriptorPoolSize( vk::DescriptorType::eCombinedImageSampler, MAX_FRAMES_IN_FLIGHT) - }; - vk::DescriptorPoolCreateInfo poolInfo{ - .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, - .maxSets = MAX_FRAMES_IN_FLIGHT, - .poolSizeCount = static_cast(poolSize.size()), - .pPoolSizes = poolSize.data() - }; - descriptorPool = vk::raii::DescriptorPool(device, poolInfo); - } - - void createDescriptorSets() { - std::vector layouts(MAX_FRAMES_IN_FLIGHT, descriptorSetLayout); - vk::DescriptorSetAllocateInfo allocInfo{ - .descriptorPool = descriptorPool, - .descriptorSetCount = static_cast(layouts.size()), - .pSetLayouts = layouts.data() - }; - - descriptorSets.clear(); - descriptorSets = device.allocateDescriptorSets(allocInfo); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DescriptorBufferInfo bufferInfo{ - .buffer = uniformBuffers[i], - .offset = 0, - .range = sizeof(UniformBufferObject) - }; - vk::DescriptorImageInfo imageInfo{ - .sampler = textureSampler, - .imageView = textureImageView, - .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal - }; - std::array descriptorWrites{ - vk::WriteDescriptorSet{ - .dstSet = descriptorSets[i], - .dstBinding = 0, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eUniformBuffer, - .pBufferInfo = &bufferInfo - }, - vk::WriteDescriptorSet{ - .dstSet = descriptorSets[i], - .dstBinding = 1, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eCombinedImageSampler, - .pImageInfo = &imageInfo - } - }; - device.updateDescriptorSets(descriptorWrites, {}); - } - } - - void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer& buffer, vk::raii::DeviceMemory& bufferMemory) { - vk::BufferCreateInfo bufferInfo{ - .size = size, - .usage = usage, - .sharingMode = vk::SharingMode::eExclusive - }; - buffer = vk::raii::Buffer(device, bufferInfo); - vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ - .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) - }; - bufferMemory = vk::raii::DeviceMemory(device, allocInfo); - buffer.bindMemory(bufferMemory, 0); - } - - std::unique_ptr beginSingleTimeCommands() { - vk::CommandBufferAllocateInfo allocInfo{ - .commandPool = commandPool, - .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = 1 - }; - std::unique_ptr commandBuffer = std::make_unique(std::move(vk::raii::CommandBuffers(device, allocInfo).front())); - - vk::CommandBufferBeginInfo beginInfo{ - .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit - }; - commandBuffer->begin(beginInfo); - - return commandBuffer; - } - - void endSingleTimeCommands(const vk::raii::CommandBuffer& commandBuffer) const { - commandBuffer.end(); - - vk::SubmitInfo submitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandBuffer }; - queue.submit(submitInfo, nullptr); - queue.waitIdle(); - } - - void copyBuffer(vk::raii::Buffer & srcBuffer, vk::raii::Buffer & dstBuffer, vk::DeviceSize size) { - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1 }; - vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); - commandCopyBuffer.begin(vk::CommandBufferBeginInfo{ .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit }); - commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy{ .size = size }); - commandCopyBuffer.end(); - queue.submit(vk::SubmitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer }, nullptr); - queue.waitIdle(); - } - - uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) { - vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); - - for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { - if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { - return i; - } - } - - throw std::runtime_error("failed to find suitable memory type!"); - } - - void createCommandBuffers() { - commandBuffers.clear(); - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = MAX_FRAMES_IN_FLIGHT }; - commandBuffers = vk::raii::CommandBuffers(device, allocInfo); - } - - void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin({}); - // Before starting rendering, transition the images to the appropriate layouts - // Transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL - transition_image_layout( - imageIndex, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage - ); - - // Transition the multisampled color image to COLOR_ATTACHMENT_OPTIMAL - transition_image_layout_custom( - colorImage, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, - vk::AccessFlagBits2::eColorAttachmentWrite, - vk::PipelineStageFlagBits2::eTopOfPipe, - vk::PipelineStageFlagBits2::eColorAttachmentOutput, - vk::ImageAspectFlagBits::eColor - ); - - // Transition the depth image to DEPTH_ATTACHMENT_OPTIMAL - transition_image_layout_custom( - depthImage, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eDepthAttachmentOptimal, - {}, - vk::AccessFlagBits2::eDepthStencilAttachmentWrite, - vk::PipelineStageFlagBits2::eTopOfPipe, - vk::PipelineStageFlagBits2::eEarlyFragmentTests, - vk::ImageAspectFlagBits::eDepth - ); - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::ClearValue clearDepth = vk::ClearDepthStencilValue(1.0f, 0); - - // Color attachment (multisampled) with resolve attachment - vk::RenderingAttachmentInfo colorAttachment = { - .imageView = colorImageView, - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .resolveMode = vk::ResolveModeFlagBits::eAverage, - .resolveImageView = swapChainImageViews[imageIndex], - .resolveImageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor - }; - - // Depth attachment - vk::RenderingAttachmentInfo depthAttachment = { - .imageView = depthImageView, - .imageLayout = vk::ImageLayout::eDepthAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eDontCare, - .clearValue = clearDepth - }; - - vk::RenderingInfo renderingInfo = { - .renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &colorAttachment, - .pDepthAttachment = &depthAttachment - }; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].bindIndexBuffer( *indexBuffer, 0, vk::IndexType::eUint32 ); - commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); - commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); - commandBuffers[currentFrame].endRendering(); - // After rendering, transition the images to appropriate layouts - - // Transition the swapchain image to PRESENT_SRC - transition_image_layout( - imageIndex, - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage - ); - commandBuffers[currentFrame].end(); - } - - void transition_image_layout( - uint32_t imageIndex, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::AccessFlags2 src_access_mask, - vk::AccessFlags2 dst_access_mask, - vk::PipelineStageFlags2 src_stage_mask, - vk::PipelineStageFlags2 dst_stage_mask - ) { - vk::ImageMemoryBarrier2 barrier = { - .srcStageMask = src_stage_mask, - .srcAccessMask = src_access_mask, - .dstStageMask = dst_stage_mask, - .dstAccessMask = dst_access_mask, - .oldLayout = old_layout, - .newLayout = new_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); - } - - void transition_image_layout_custom( - vk::raii::Image& image, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::AccessFlags2 src_access_mask, - vk::AccessFlags2 dst_access_mask, - vk::PipelineStageFlags2 src_stage_mask, - vk::PipelineStageFlags2 dst_stage_mask, - vk::ImageAspectFlags aspect_mask - ) { - vk::ImageMemoryBarrier2 barrier = { - .srcStageMask = src_stage_mask, - .srcAccessMask = src_access_mask, - .dstStageMask = dst_stage_mask, - .dstAccessMask = dst_access_mask, - .oldLayout = old_layout, - .newLayout = new_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = image, - .subresourceRange = { - .aspectMask = aspect_mask, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); - } - - void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); - - for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - } - - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - inFlightFences.emplace_back(device, vk::FenceCreateInfo{ .flags = vk::FenceCreateFlagBits::eSignaled }); - } - } - - void updateUniformBuffer(uint32_t currentImage) const { - static auto startTime = std::chrono::high_resolution_clock::now(); - - auto currentTime = std::chrono::high_resolution_clock::now(); - float time = std::chrono::duration(currentTime - startTime).count(); - - UniformBufferObject ubo{}; - ubo.model = rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.view = lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); - ubo.proj[1][1] *= -1; - - memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); - } - - void drawFrame() { - while ( vk::Result::eTimeout == device.waitForFences( *inFlightFences[currentFrame], vk::True, UINT64_MAX ) ) - ; - auto [result, imageIndex] = swapChain.acquireNextImage( UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr ); - - if (result == vk::Result::eErrorOutOfDateKHR) { - recreateSwapChain(); - return; - } - if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) { - throw std::runtime_error("failed to acquire swap chain image!"); - } - updateUniformBuffer(currentFrame); - - device.resetFences( *inFlightFences[currentFrame] ); - commandBuffers[currentFrame].reset(); - recordCommandBuffer(imageIndex); - - vk::PipelineStageFlags waitDestinationStageMask( vk::PipelineStageFlagBits::eColorAttachmentOutput ); - const vk::SubmitInfo submitInfo{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], - .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], - .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex] }; - queue.submit(submitInfo, *inFlightFences[currentFrame]); - - - const vk::PresentInfoKHR presentInfoKHR{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], - .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex }; - result = queue.presentKHR(presentInfoKHR); - if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { - framebufferResized = false; - recreateSwapChain(); - } else if (result != vk::Result::eSuccess) { - throw std::runtime_error("failed to present swap chain image!"); - } - semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; - } - - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size(), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const & surfaceCapabilities) { - auto minImageCount = std::max( 3u, surfaceCapabilities.minImageCount ); - if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) { - minImageCount = surfaceCapabilities.maxImageCount; - } - return minImageCount; - } - - static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { - assert(!availableFormats.empty()); - const auto formatIt = std::ranges::find_if( - availableFormats, - []( const auto & format ) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; } ); - return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - assert(std::ranges::any_of(availablePresentModes, [](auto presentMode){ return presentMode == vk::PresentModeKHR::eFifo; })); - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eFifo; - } - - [[nodiscard]] vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) const { - if (capabilities.currentExtent.width != 0xFFFFFFFF) { - 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) - }; - } - - [[nodiscard]] std::vector getRequiredExtensions() const { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - 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; - } + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().features.samplerAnisotropy && + features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain featureChain = { + {.features = {.samplerAnisotropy = true}}, // vk::PhysicalDeviceFeatures2 + {.synchronization2 = true, .dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(*surface); + swapChainExtent = chooseSwapExtent(surfaceCapabilities); + swapChainSurfaceFormat = chooseSwapSurfaceFormat(physicalDevice.getSurfaceFormatsKHR(*surface)); + vk::SwapchainCreateInfoKHR swapChainCreateInfo{.surface = *surface, + .minImageCount = chooseSwapMinImageCount(surfaceCapabilities), + .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + void createImageViews() + { + vk::ImageViewCreateInfo imageViewCreateInfo{ + .viewType = vk::ImageViewType::e2D, + .format = swapChainSurfaceFormat.format, + .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + for (auto image : swapChainImages) + { + imageViewCreateInfo.image = image; + swapChainImageViews.emplace_back(device, imageViewCreateInfo); + } + } + + void createDescriptorSetLayout() + { + std::array bindings = { + vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr), + vk::DescriptorSetLayoutBinding(1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment, nullptr)}; + + vk::DescriptorSetLayoutCreateInfo layoutInfo{.bindingCount = static_cast(bindings.size()), .pBindings = bindings.data()}; + descriptorSetLayout = vk::raii::DescriptorSetLayout(device, layoutInfo); + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; + + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{ + .vertexBindingDescriptionCount = 1, + .pVertexBindingDescriptions = &bindingDescription, + .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), + .pVertexAttributeDescriptions = attributeDescriptions.data()}; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ + .topology = vk::PrimitiveTopology::eTriangleList, + .primitiveRestartEnable = vk::False}; + vk::PipelineViewportStateCreateInfo viewportState{ + .viewportCount = 1, + .scissorCount = 1}; + vk::PipelineRasterizationStateCreateInfo rasterizer{ + .depthClampEnable = vk::False, + .rasterizerDiscardEnable = vk::False, + .polygonMode = vk::PolygonMode::eFill, + .cullMode = vk::CullModeFlagBits::eBack, + .frontFace = vk::FrontFace::eCounterClockwise, + .depthBiasEnable = vk::False}; + rasterizer.lineWidth = 1.0f; + vk::PipelineMultisampleStateCreateInfo multisampling{ + .rasterizationSamples = msaaSamples, + .sampleShadingEnable = vk::False}; + vk::PipelineDepthStencilStateCreateInfo depthStencil{ + .depthTestEnable = vk::True, + .depthWriteEnable = vk::True, + .depthCompareOp = vk::CompareOp::eLess, + .depthBoundsTestEnable = vk::False, + .stencilTestEnable = vk::False}; + vk::PipelineColorBlendAttachmentState colorBlendAttachment; + colorBlendAttachment.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA; + colorBlendAttachment.blendEnable = vk::False; + + vk::PipelineColorBlendStateCreateInfo colorBlending{ + .logicOpEnable = vk::False, + .logicOp = vk::LogicOp::eCopy, + .attachmentCount = 1, + .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0}; + + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::Format depthFormat = findDepthFormat(); + vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ + .colorAttachmentCount = 1, + .pColorAttachmentFormats = &swapChainSurfaceFormat.format, + .depthAttachmentFormat = depthFormat}; + vk::GraphicsPipelineCreateInfo pipelineInfo{.pNext = &pipelineRenderingCreateInfo, + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pDepthStencilState = &depthStencil, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{ + .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex}; + commandPool = vk::raii::CommandPool(device, poolInfo); + } + + void createColorResources() + { + vk::Format colorFormat = swapChainSurfaceFormat.format; + + createImage(swapChainExtent.width, swapChainExtent.height, 1, msaaSamples, colorFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransientAttachment | vk::ImageUsageFlagBits::eColorAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, colorImage, colorImageMemory); + colorImageView = createImageView(colorImage, colorFormat, vk::ImageAspectFlagBits::eColor, 1); + } + + void createDepthResources() + { + vk::Format depthFormat = findDepthFormat(); + + createImage(swapChainExtent.width, swapChainExtent.height, 1, msaaSamples, depthFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eDepthStencilAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, depthImage, depthImageMemory); + depthImageView = createImageView(depthImage, depthFormat, vk::ImageAspectFlagBits::eDepth, 1); + } + + vk::Format findSupportedFormat(const std::vector &candidates, vk::ImageTiling tiling, vk::FormatFeatureFlags features) const + { + for (const auto format : candidates) + { + vk::FormatProperties props = physicalDevice.getFormatProperties(format); + + if (tiling == vk::ImageTiling::eLinear && (props.linearTilingFeatures & features) == features) + { + return format; + } + if (tiling == vk::ImageTiling::eOptimal && (props.optimalTilingFeatures & features) == features) + { + return format; + } + } + + throw std::runtime_error("failed to find supported format!"); + } + + [[nodiscard]] vk::Format findDepthFormat() const + { + return findSupportedFormat( + {vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, vk::Format::eD24UnormS8Uint}, + vk::ImageTiling::eOptimal, + vk::FormatFeatureFlagBits::eDepthStencilAttachment); + } + + static bool hasStencilComponent(vk::Format format) + { + return format == vk::Format::eD32SfloatS8Uint || format == vk::Format::eD24UnormS8Uint; + } + + void createTextureImage() + { + int texWidth, texHeight, texChannels; + stbi_uc *pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + vk::DeviceSize imageSize = texWidth * texHeight * 4; + mipLevels = static_cast(std::floor(std::log2(std::max(texWidth, texHeight)))) + 1; + + if (!pixels) + { + throw std::runtime_error("failed to load texture image!"); + } + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, imageSize); + memcpy(data, pixels, imageSize); + stagingBufferMemory.unmapMemory(); + + stbi_image_free(pixels); + + createImage(texWidth, texHeight, mipLevels, vk::SampleCountFlagBits::e1, vk::Format::eR8G8B8A8Srgb, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory); + + transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, mipLevels); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); + + generateMipmaps(textureImage, vk::Format::eR8G8B8A8Srgb, texWidth, texHeight, mipLevels); + } + + void generateMipmaps(vk::raii::Image &image, vk::Format imageFormat, int32_t texWidth, int32_t texHeight, uint32_t mipLevels) + { + // Check if image format supports linear blit-ing + vk::FormatProperties formatProperties = physicalDevice.getFormatProperties(imageFormat); + + if (!(formatProperties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eSampledImageFilterLinear)) + { + throw std::runtime_error("texture image format does not support linear blitting!"); + } + + std::unique_ptr commandBuffer = beginSingleTimeCommands(); + + vk::ImageMemoryBarrier barrier = {.srcAccessMask = vk::AccessFlagBits::eTransferWrite, .dstAccessMask = vk::AccessFlagBits::eTransferRead, .oldLayout = vk::ImageLayout::eTransferDstOptimal, .newLayout = vk::ImageLayout::eTransferSrcOptimal, .srcQueueFamilyIndex = vk::QueueFamilyIgnored, .dstQueueFamilyIndex = vk::QueueFamilyIgnored, .image = image}; + barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + barrier.subresourceRange.levelCount = 1; + + int32_t mipWidth = texWidth; + int32_t mipHeight = texHeight; + + for (uint32_t i = 1; i < mipLevels; i++) + { + barrier.subresourceRange.baseMipLevel = i - 1; + barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal; + barrier.newLayout = vk::ImageLayout::eTransferSrcOptimal; + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eTransferRead; + + commandBuffer->pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer, {}, {}, {}, barrier); + + vk::ArrayWrapper1D offsets, dstOffsets; + offsets[0] = vk::Offset3D(0, 0, 0); + offsets[1] = vk::Offset3D(mipWidth, mipHeight, 1); + dstOffsets[0] = vk::Offset3D(0, 0, 0); + dstOffsets[1] = vk::Offset3D(mipWidth > 1 ? mipWidth / 2 : 1, mipHeight > 1 ? mipHeight / 2 : 1, 1); + vk::ImageBlit blit = {.srcSubresource = {}, .srcOffsets = offsets, .dstSubresource = {}, .dstOffsets = dstOffsets}; + blit.srcSubresource = vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, i - 1, 0, 1); + blit.dstSubresource = vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, i, 0, 1); + + commandBuffer->blitImage(image, vk::ImageLayout::eTransferSrcOptimal, image, vk::ImageLayout::eTransferDstOptimal, {blit}, vk::Filter::eLinear); + + barrier.oldLayout = vk::ImageLayout::eTransferSrcOptimal; + barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal; + barrier.srcAccessMask = vk::AccessFlagBits::eTransferRead; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + + commandBuffer->pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {}, barrier); + + if (mipWidth > 1) + mipWidth /= 2; + if (mipHeight > 1) + mipHeight /= 2; + } + + barrier.subresourceRange.baseMipLevel = mipLevels - 1; + barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal; + barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal; + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + + commandBuffer->pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {}, barrier); + + endSingleTimeCommands(*commandBuffer); + } + + vk::SampleCountFlagBits getMaxUsableSampleCount() + { + vk::PhysicalDeviceProperties physicalDeviceProperties = physicalDevice.getProperties(); + + vk::SampleCountFlags counts = physicalDeviceProperties.limits.framebufferColorSampleCounts & physicalDeviceProperties.limits.framebufferDepthSampleCounts; + if (counts & vk::SampleCountFlagBits::e64) + { + return vk::SampleCountFlagBits::e64; + } + if (counts & vk::SampleCountFlagBits::e32) + { + return vk::SampleCountFlagBits::e32; + } + if (counts & vk::SampleCountFlagBits::e16) + { + return vk::SampleCountFlagBits::e16; + } + if (counts & vk::SampleCountFlagBits::e8) + { + return vk::SampleCountFlagBits::e8; + } + if (counts & vk::SampleCountFlagBits::e4) + { + return vk::SampleCountFlagBits::e4; + } + if (counts & vk::SampleCountFlagBits::e2) + { + return vk::SampleCountFlagBits::e2; + } + + return vk::SampleCountFlagBits::e1; + } + + void createTextureImageView() + { + textureImageView = createImageView(textureImage, vk::Format::eR8G8B8A8Srgb, vk::ImageAspectFlagBits::eColor, mipLevels); + } + + void createTextureSampler() + { + vk::PhysicalDeviceProperties properties = physicalDevice.getProperties(); + vk::SamplerCreateInfo samplerInfo{ + .magFilter = vk::Filter::eLinear, + .minFilter = vk::Filter::eLinear, + .mipmapMode = vk::SamplerMipmapMode::eLinear, + .addressModeU = vk::SamplerAddressMode::eRepeat, + .addressModeV = vk::SamplerAddressMode::eRepeat, + .addressModeW = vk::SamplerAddressMode::eRepeat, + .mipLodBias = 0.0f, + .anisotropyEnable = vk::True, + .maxAnisotropy = properties.limits.maxSamplerAnisotropy, + .compareEnable = vk::False, + .compareOp = vk::CompareOp::eAlways}; + textureSampler = vk::raii::Sampler(device, samplerInfo); + } + + [[nodiscard]] vk::raii::ImageView createImageView(const vk::raii::Image &image, vk::Format format, vk::ImageAspectFlags aspectFlags, uint32_t mipLevels) const + { + vk::ImageViewCreateInfo viewInfo{ + .image = image, + .viewType = vk::ImageViewType::e2D, + .format = format, + .subresourceRange = {aspectFlags, 0, mipLevels, 0, 1}}; + return vk::raii::ImageView(device, viewInfo); + } + + void createImage(uint32_t width, uint32_t height, uint32_t mipLevels, vk::SampleCountFlagBits numSamples, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image &image, vk::raii::DeviceMemory &imageMemory) + { + vk::ImageCreateInfo imageInfo{ + .imageType = vk::ImageType::e2D, + .format = format, + .extent = {width, height, 1}, + .mipLevels = mipLevels, + .arrayLayers = 1, + .samples = numSamples, + .tiling = tiling, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive, + .initialLayout = vk::ImageLayout::eUndefined}; + image = vk::raii::Image(device, imageInfo); + + vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{ + .allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + imageMemory = vk::raii::DeviceMemory(device, allocInfo); + image.bindMemory(imageMemory, 0); + } + + void transitionImageLayout(const vk::raii::Image &image, const vk::ImageLayout oldLayout, const vk::ImageLayout newLayout, uint32_t mipLevels) + { + const auto commandBuffer = beginSingleTimeCommands(); + + vk::ImageMemoryBarrier barrier{ + .oldLayout = oldLayout, + .newLayout = newLayout, + .image = image, + .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, mipLevels, 0, 1}}; + + vk::PipelineStageFlags sourceStage; + vk::PipelineStageFlags destinationStage; + + if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) + { + barrier.srcAccessMask = {}; + barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; + + sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; + destinationStage = vk::PipelineStageFlagBits::eTransfer; + } + else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) + { + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + + sourceStage = vk::PipelineStageFlagBits::eTransfer; + destinationStage = vk::PipelineStageFlagBits::eFragmentShader; + } + else + { + throw std::invalid_argument("unsupported layout transition!"); + } + commandBuffer->pipelineBarrier(sourceStage, destinationStage, {}, {}, nullptr, barrier); + endSingleTimeCommands(*commandBuffer); + } + + void copyBufferToImage(const vk::raii::Buffer &buffer, const vk::raii::Image &image, uint32_t width, uint32_t height) + { + std::unique_ptr commandBuffer = beginSingleTimeCommands(); + vk::BufferImageCopy region{ + .bufferOffset = 0, + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource = {vk::ImageAspectFlagBits::eColor, 0, 0, 1}, + .imageOffset = {0, 0, 0}, + .imageExtent = {width, height, 1}}; + commandBuffer->copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, {region}); + endSingleTimeCommands(*commandBuffer); + } + + void loadModel() + { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn, err; + + if (!LoadObj(&attrib, &shapes, &materials, &warn, &err, MODEL_PATH.c_str())) + { + throw std::runtime_error(warn + err); + } + + std::unordered_map uniqueVertices{}; + + for (const auto &shape : shapes) + { + for (const auto &index : shape.mesh.indices) + { + Vertex vertex{}; + + vertex.pos = { + attrib.vertices[3 * index.vertex_index + 0], + attrib.vertices[3 * index.vertex_index + 1], + attrib.vertices[3 * index.vertex_index + 2]}; + + vertex.texCoord = { + attrib.texcoords[2 * index.texcoord_index + 0], + 1.0f - attrib.texcoords[2 * index.texcoord_index + 1]}; + + vertex.color = {1.0f, 1.0f, 1.0f}; + + if (!uniqueVertices.contains(vertex)) + { + uniqueVertices[vertex] = static_cast(vertices.size()); + vertices.push_back(vertex); + } + + indices.push_back(uniqueVertices[vertex]); + } + } + } + + void createVertexBuffer() + { + vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(dataStaging, vertices.data(), bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + } + + void createIndexBuffer() + { + vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(data, indices.data(), bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); + + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + } + + void createUniformBuffers() + { + uniformBuffers.clear(); + uniformBuffersMemory.clear(); + uniformBuffersMapped.clear(); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DeviceSize bufferSize = sizeof(UniformBufferObject); + vk::raii::Buffer buffer({}); + vk::raii::DeviceMemory bufferMem({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); + uniformBuffers.emplace_back(std::move(buffer)); + uniformBuffersMemory.emplace_back(std::move(bufferMem)); + uniformBuffersMapped.emplace_back(uniformBuffersMemory[i].mapMemory(0, bufferSize)); + } + } + + void createDescriptorPool() + { + std::array poolSize{ + vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT), + vk::DescriptorPoolSize(vk::DescriptorType::eCombinedImageSampler, MAX_FRAMES_IN_FLIGHT)}; + vk::DescriptorPoolCreateInfo poolInfo{ + .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, + .maxSets = MAX_FRAMES_IN_FLIGHT, + .poolSizeCount = static_cast(poolSize.size()), + .pPoolSizes = poolSize.data()}; + descriptorPool = vk::raii::DescriptorPool(device, poolInfo); + } + + void createDescriptorSets() + { + std::vector layouts(MAX_FRAMES_IN_FLIGHT, descriptorSetLayout); + vk::DescriptorSetAllocateInfo allocInfo{ + .descriptorPool = descriptorPool, + .descriptorSetCount = static_cast(layouts.size()), + .pSetLayouts = layouts.data()}; + + descriptorSets.clear(); + descriptorSets = device.allocateDescriptorSets(allocInfo); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DescriptorBufferInfo bufferInfo{ + .buffer = uniformBuffers[i], + .offset = 0, + .range = sizeof(UniformBufferObject)}; + vk::DescriptorImageInfo imageInfo{ + .sampler = textureSampler, + .imageView = textureImageView, + .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal}; + std::array descriptorWrites{ + vk::WriteDescriptorSet{ + .dstSet = descriptorSets[i], + .dstBinding = 0, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eUniformBuffer, + .pBufferInfo = &bufferInfo}, + vk::WriteDescriptorSet{ + .dstSet = descriptorSets[i], + .dstBinding = 1, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .pImageInfo = &imageInfo}}; + device.updateDescriptorSets(descriptorWrites, {}); + } + } + + void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer &buffer, vk::raii::DeviceMemory &bufferMemory) + { + vk::BufferCreateInfo bufferInfo{ + .size = size, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive}; + buffer = vk::raii::Buffer(device, bufferInfo); + vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{ + .allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + bufferMemory = vk::raii::DeviceMemory(device, allocInfo); + buffer.bindMemory(bufferMemory, 0); + } + + std::unique_ptr beginSingleTimeCommands() + { + vk::CommandBufferAllocateInfo allocInfo{ + .commandPool = commandPool, + .level = vk::CommandBufferLevel::ePrimary, + .commandBufferCount = 1}; + std::unique_ptr commandBuffer = std::make_unique(std::move(vk::raii::CommandBuffers(device, allocInfo).front())); + + vk::CommandBufferBeginInfo beginInfo{ + .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; + commandBuffer->begin(beginInfo); + + return commandBuffer; + } + + void endSingleTimeCommands(const vk::raii::CommandBuffer &commandBuffer) const + { + commandBuffer.end(); + + vk::SubmitInfo submitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandBuffer}; + queue.submit(submitInfo, nullptr); + queue.waitIdle(); + } + + void copyBuffer(vk::raii::Buffer &srcBuffer, vk::raii::Buffer &dstBuffer, vk::DeviceSize size) + { + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1}; + vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); + commandCopyBuffer.begin(vk::CommandBufferBeginInfo{.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); + commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy{.size = size}); + commandCopyBuffer.end(); + queue.submit(vk::SubmitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer}, nullptr); + queue.waitIdle(); + } + + uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) + { + vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) + { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) + { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + void createCommandBuffers() + { + commandBuffers.clear(); + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = MAX_FRAMES_IN_FLIGHT}; + commandBuffers = vk::raii::CommandBuffers(device, allocInfo); + } + + void recordCommandBuffer(uint32_t imageIndex) + { + commandBuffers[currentFrame].begin({}); + // Before starting rendering, transition the images to the appropriate layouts + // Transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL + transition_image_layout( + imageIndex, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage + ); + + // Transition the multisampled color image to COLOR_ATTACHMENT_OPTIMAL + transition_image_layout_custom( + colorImage, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, + vk::AccessFlagBits2::eColorAttachmentWrite, + vk::PipelineStageFlagBits2::eTopOfPipe, + vk::PipelineStageFlagBits2::eColorAttachmentOutput, + vk::ImageAspectFlagBits::eColor); + + // Transition the depth image to DEPTH_ATTACHMENT_OPTIMAL + transition_image_layout_custom( + depthImage, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eDepthAttachmentOptimal, + {}, + vk::AccessFlagBits2::eDepthStencilAttachmentWrite, + vk::PipelineStageFlagBits2::eTopOfPipe, + vk::PipelineStageFlagBits2::eEarlyFragmentTests, + vk::ImageAspectFlagBits::eDepth); + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::ClearValue clearDepth = vk::ClearDepthStencilValue(1.0f, 0); + + // Color attachment (multisampled) with resolve attachment + vk::RenderingAttachmentInfo colorAttachment = { + .imageView = colorImageView, + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .resolveMode = vk::ResolveModeFlagBits::eAverage, + .resolveImageView = swapChainImageViews[imageIndex], + .resolveImageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor}; + + // Depth attachment + vk::RenderingAttachmentInfo depthAttachment = { + .imageView = depthImageView, + .imageLayout = vk::ImageLayout::eDepthAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eDontCare, + .clearValue = clearDepth}; + + vk::RenderingInfo renderingInfo = { + .renderArea = {.offset = {0, 0}, .extent = swapChainExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &colorAttachment, + .pDepthAttachment = &depthAttachment}; + commandBuffers[currentFrame].beginRendering(renderingInfo); + commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint32); + commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); + commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); + commandBuffers[currentFrame].endRendering(); + // After rendering, transition the images to appropriate layouts + + // Transition the swapchain image to PRESENT_SRC + transition_image_layout( + imageIndex, + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage + ); + commandBuffers[currentFrame].end(); + } + + void transition_image_layout( + uint32_t imageIndex, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout, + vk::AccessFlags2 src_access_mask, + vk::AccessFlags2 dst_access_mask, + vk::PipelineStageFlags2 src_stage_mask, + vk::PipelineStageFlags2 dst_stage_mask) + { + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = src_stage_mask, + .srcAccessMask = src_access_mask, + .dstStageMask = dst_stage_mask, + .dstAccessMask = dst_access_mask, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo dependency_info = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + } + + void transition_image_layout_custom( + vk::raii::Image &image, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout, + vk::AccessFlags2 src_access_mask, + vk::AccessFlags2 dst_access_mask, + vk::PipelineStageFlags2 src_stage_mask, + vk::PipelineStageFlags2 dst_stage_mask, + vk::ImageAspectFlags aspect_mask) + { + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = src_stage_mask, + .srcAccessMask = src_access_mask, + .dstStageMask = dst_stage_mask, + .dstAccessMask = dst_access_mask, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = image, + .subresourceRange = { + .aspectMask = aspect_mask, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo dependency_info = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + } + + void createSyncObjects() + { + presentCompleteSemaphore.clear(); + renderFinishedSemaphore.clear(); + inFlightFences.clear(); + + for (size_t i = 0; i < swapChainImages.size(); i++) + { + presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); + } + } + + void updateUniformBuffer(uint32_t currentImage) const + { + static auto startTime = std::chrono::high_resolution_clock::now(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + UniformBufferObject ubo{}; + ubo.model = rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.view = lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); + ubo.proj[1][1] *= -1; + + memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); + } + + void drawFrame() + { + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + ; + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[semaphoreIndex], nullptr); + + if (result == vk::Result::eErrorOutOfDateKHR) + { + recreateSwapChain(); + return; + } + if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) + { + throw std::runtime_error("failed to acquire swap chain image!"); + } + updateUniformBuffer(currentFrame); + + device.resetFences(*inFlightFences[currentFrame]); + commandBuffers[currentFrame].reset(); + recordCommandBuffer(imageIndex); + + vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[semaphoreIndex], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[currentFrame]); + + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + result = queue.presentKHR(presentInfoKHR); + if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) + { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != vk::Result::eSuccess) + { + throw std::runtime_error("failed to present swap chain image!"); + } + semaphoreIndex = (semaphoreIndex + 1) % presentCompleteSemaphore.size(); + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size(), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const &surfaceCapabilities) + { + auto minImageCount = std::max(3u, surfaceCapabilities.minImageCount); + if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) + { + minImageCount = surfaceCapabilities.maxImageCount; + } + return minImageCount; + } + + static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + assert(!availableFormats.empty()); + const auto formatIt = std::ranges::find_if( + availableFormats, + [](const auto &format) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; }); + return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + assert(std::ranges::any_of(availablePresentModes, [](auto presentMode) { return presentMode == vk::PresentModeKHR::eFifo; })); + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + vk::PresentModeKHR::eFifo; + } + + [[nodiscard]] vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR &capabilities) const + { + if (capabilities.currentExtent.width != 0xFFFFFFFF) + { + 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)}; + } + + [[nodiscard]] std::vector getRequiredExtensions() const + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + 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; + } }; -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/31_compute_shader.cpp b/attachments/31_compute_shader.cpp index 420dad15..a07031f8 100644 --- a/attachments/31_compute_shader.cpp +++ b/attachments/31_compute_shader.cpp @@ -16,30 +16,29 @@ #include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include #define GLM_FORCE_RADIANS #include #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -constexpr uint64_t FenceTimeout = 100000000; +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; +constexpr uint64_t FenceTimeout = 100000000; constexpr uint32_t PARTICLE_COUNT = 8192; constexpr int MAX_FRAMES_IN_FLIGHT = 2; const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -47,917 +46,963 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -struct UniformBufferObject { - float deltaTime = 1.0f; +struct UniformBufferObject +{ + float deltaTime = 1.0f; }; -struct Particle { - glm::vec2 position; - glm::vec2 velocity; - glm::vec4 color; - - static vk::VertexInputBindingDescription getBindingDescription() { - return { 0, sizeof(Particle), vk::VertexInputRate::eVertex }; - } - - static std::array getAttributeDescriptions() { - return { - vk::VertexInputAttributeDescription( 0, 0, vk::Format::eR32G32Sfloat, offsetof(Particle, position) ), - vk::VertexInputAttributeDescription( 1, 0, vk::Format::eR32G32B32A32Sfloat, offsetof(Particle, color) ), - }; - } +struct Particle +{ + glm::vec2 position; + glm::vec2 velocity; + glm::vec4 color; + + static vk::VertexInputBindingDescription getBindingDescription() + { + return {0, sizeof(Particle), vk::VertexInputRate::eVertex}; + } + + static std::array getAttributeDescriptions() + { + return { + vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32Sfloat, offsetof(Particle, position)), + vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32A32Sfloat, offsetof(Particle, color)), + }; + } }; -class ComputeShaderApplication { -public: - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } - -private: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::SurfaceFormatKHR swapChainSurfaceFormat; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - - vk::raii::DescriptorSetLayout computeDescriptorSetLayout = nullptr; - vk::raii::PipelineLayout computePipelineLayout = nullptr; - vk::raii::Pipeline computePipeline = nullptr; - - - std::vector shaderStorageBuffers; - std::vector shaderStorageBuffersMemory; - - std::vector uniformBuffers; - std::vector uniformBuffersMemory; - std::vector uniformBuffersMapped; - - vk::raii::DescriptorPool descriptorPool = nullptr; - std::vector computeDescriptorSets; - - vk::raii::CommandPool commandPool = nullptr; - std::vector commandBuffers; - std::vector computeCommandBuffers; - - vk::raii::Semaphore semaphore = nullptr; - uint64_t timelineValue = 0; - std::vector inFlightFences; - uint32_t currentFrame = 0; - - double lastFrameTime = 0.0; - - bool framebufferResized = false; - - double lastTime = 0.0f; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); - - lastTime = glfwGetTime(); - } - - static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { - auto app = static_cast(glfwGetWindowUserPointer(window)); - app->framebufferResized = true; - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - createSurface(); - pickPhysicalDevice(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createComputeDescriptorSetLayout(); - createGraphicsPipeline(); - createComputePipeline(); - createCommandPool(); - createShaderStorageBuffers(); - createUniformBuffers(); - createDescriptorPool(); - createComputeDescriptorSets(); - createCommandBuffers(); - createComputeCommandBuffers(); - createSyncObjects(); - } - - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - drawFrame(); - // We want to animate the particle system using the last frames time to get smooth, frame-rate independent animation - double currentTime = glfwGetTime(); - lastFrameTime = (currentTime - lastTime) * 1000.0; - lastTime = currentTime; - } - - device.waitIdle(); - } - - void cleanupSwapChain() { - swapChainImageViews.clear(); - swapChain = nullptr; - } - - void cleanup() const { - glfwDestroyWindow(window); - - glfwTerminate(); - } - - void recreateSwapChain() { - int width = 0, height = 0; - glfwGetFramebufferSize(window, &width, &height); - while (width == 0 || height == 0) { - glfwGetFramebufferSize(window, &width, &height); - glfwWaitEvents(); - } - - device.waitIdle(); - - cleanupSwapChain(); - createSwapChain(); - createImageViews(); - } - - 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(); - for (auto const& requiredLayer : requiredLayers) - { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const& layerProperty) - { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } - } - - // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); - - // Check if the required extensions are supported by the Vulkan implementation. - auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const& requiredExtension : requiredExtensions) - { - if (std::ranges::none_of(extensionProperties, - [requiredExtension](auto const& extensionProperty) - { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) - { - throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); - } - } - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data() }; - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - if (!enableValidationLayers) return; - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if the device supports the Vulkan 1.3 API version - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - // Check if any of the queue families support graphics operations +class ComputeShaderApplication +{ + public: + void run() + { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + + private: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::SurfaceFormatKHR swapChainSurfaceFormat; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + + vk::raii::DescriptorSetLayout computeDescriptorSetLayout = nullptr; + vk::raii::PipelineLayout computePipelineLayout = nullptr; + vk::raii::Pipeline computePipeline = nullptr; + + std::vector shaderStorageBuffers; + std::vector shaderStorageBuffersMemory; + + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + std::vector uniformBuffersMapped; + + vk::raii::DescriptorPool descriptorPool = nullptr; + std::vector computeDescriptorSets; + + vk::raii::CommandPool commandPool = nullptr; + std::vector commandBuffers; + std::vector computeCommandBuffers; + + vk::raii::Semaphore semaphore = nullptr; + uint64_t timelineValue = 0; + std::vector inFlightFences; + uint32_t currentFrame = 0; + + double lastFrameTime = 0.0; + + bool framebufferResized = false; + + double lastTime = 0.0f; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + + lastTime = glfwGetTime(); + } + + static void framebufferResizeCallback(GLFWwindow *window, int width, int height) + { + auto app = static_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createComputeDescriptorSetLayout(); + createGraphicsPipeline(); + createComputePipeline(); + createCommandPool(); + createShaderStorageBuffers(); + createUniformBuffers(); + createDescriptorPool(); + createComputeDescriptorSets(); + createCommandBuffers(); + createComputeCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() + { + while (!glfwWindowShouldClose(window)) + { + glfwPollEvents(); + drawFrame(); + // We want to animate the particle system using the last frames time to get smooth, frame-rate independent animation + double currentTime = glfwGetTime(); + lastFrameTime = (currentTime - lastTime) * 1000.0; + lastTime = currentTime; + } + + device.waitIdle(); + } + + void cleanupSwapChain() + { + swapChainImageViews.clear(); + swapChain = nullptr; + } + + void cleanup() const + { + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() + { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) + { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + device.waitIdle(); + + cleanupSwapChain(); + createSwapChain(); + createImageViews(); + } + + 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(); + for (auto const &requiredLayer : requiredLayers) + { + if (std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) + { + throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); + } + } + + // Get the required extensions. + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + if (!enableValidationLayers) + return; + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().features.samplerAnisotropy && - features.template get().dynamicRendering && - features.template get().extendedDynamicState && - features.template get().timelineSemaphore; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - }); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error("failed to find a suitable GPU!"); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().features.samplerAnisotropy && + features.template get().dynamicRendering && + features.template get().extendedDynamicState && + features.template get().timelineSemaphore; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && (queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eCompute) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - vk::StructureChain - featureChain = { - {.features = {.samplerAnisotropy = true } }, // vk::PhysicalDeviceFeatures2 - {.synchronization2 = true, .dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true }, // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - {.timelineSemaphore = true } // vk::PhysicalDeviceTimelineSemaphoreFeaturesKHR - }; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( *surface ); - swapChainExtent = chooseSwapExtent( surfaceCapabilities ); - swapChainSurfaceFormat = chooseSwapSurfaceFormat( physicalDevice.getSurfaceFormatsKHR( *surface ) ); - vk::SwapchainCreateInfoKHR swapChainCreateInfo{ .surface = *surface, - .minImageCount = chooseSwapMinImageCount( surfaceCapabilities ), - .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 }; - - swapChain = vk::raii::SwapchainKHR( device, swapChainCreateInfo ); - swapChainImages = swapChain.getImages(); - } - - void createImageViews() { - vk::ImageViewCreateInfo imageViewCreateInfo{ - .viewType = vk::ImageViewType::e2D, - .format = swapChainSurfaceFormat.format, - .components = {vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity}, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } - }; - for ( auto image : swapChainImages ) - { - imageViewCreateInfo.image = image; - swapChainImageViews.emplace_back( device, imageViewCreateInfo ); - } - } - - void createComputeDescriptorSetLayout() { - std::array layoutBindings{ - vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eCompute, nullptr), - vk::DescriptorSetLayoutBinding(1, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eCompute, nullptr), - vk::DescriptorSetLayoutBinding(2, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eCompute, nullptr) - }; - - vk::DescriptorSetLayoutCreateInfo layoutInfo{ .bindingCount = static_cast(layoutBindings.size()), .pBindings = layoutBindings.data() }; - computeDescriptorSetLayout = vk::raii::DescriptorSetLayout( device, layoutInfo ); - } - - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; - - auto bindingDescription = Particle::getBindingDescription(); - auto attributeDescriptions = Particle::getAttributeDescriptions(); - - vk::PipelineVertexInputStateCreateInfo vertexInputInfo{ .vertexBindingDescriptionCount = 1, .pVertexBindingDescriptions = &bindingDescription, .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), .pVertexAttributeDescriptions = attributeDescriptions.data() }; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ .topology = vk::PrimitiveTopology::ePointList, .primitiveRestartEnable = vk::False }; - vk::PipelineViewportStateCreateInfo viewportState{ .viewportCount = 1, .scissorCount = 1 }; - vk::PipelineRasterizationStateCreateInfo rasterizer{ - .depthClampEnable = vk::False, - .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, - .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eCounterClockwise, - .depthBiasEnable = vk::False, - .lineWidth = 1.0f - }; - vk::PipelineMultisampleStateCreateInfo multisampling{ .rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False }; - - vk::PipelineColorBlendAttachmentState colorBlendAttachment{ - .blendEnable = vk::True, - .srcColorBlendFactor = vk::BlendFactor::eSrcAlpha, - .dstColorBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha, - .colorBlendOp = vk::BlendOp::eAdd, - .srcAlphaBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha, - .dstAlphaBlendFactor = vk::BlendFactor::eZero, - .alphaBlendOp = vk::BlendOp::eAdd, - .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA, - }; - - vk::PipelineColorBlendStateCreateInfo colorBlending{ .logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo; - pipelineLayout = vk::raii::PipelineLayout( device, pipelineLayoutInfo ); - - vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ .colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format }; - vk::GraphicsPipelineCreateInfo pipelineInfo{ .pNext = &pipelineRenderingCreateInfo, - .stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = *pipelineLayout, - .subpass = 0 - }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - void createComputePipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.spv")); - - vk::PipelineShaderStageCreateInfo computeShaderStageInfo{ .stage = vk::ShaderStageFlagBits::eCompute, .module = shaderModule, .pName = "compMain" }; - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 1, .pSetLayouts = &*computeDescriptorSetLayout }; - computePipelineLayout = vk::raii::PipelineLayout( device, pipelineLayoutInfo ); - vk::ComputePipelineCreateInfo pipelineInfo{ .stage = computeShaderStageInfo, .layout = *computePipelineLayout }; - computePipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{}; - poolInfo.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer; - poolInfo.queueFamilyIndex = queueIndex; - commandPool = vk::raii::CommandPool(device, poolInfo); - } - - void createShaderStorageBuffers() { - // Initialize particles - std::default_random_engine rndEngine(static_cast(time(nullptr))); - std::uniform_real_distribution rndDist(0.0f, 1.0f); - - // Initial particle positions on a circle - std::vector particles(PARTICLE_COUNT); - for (auto& particle : particles) { - float r = 0.25f * sqrtf(rndDist(rndEngine)); - float theta = rndDist(rndEngine) * 2.0f * 3.14159265358979323846f; - float x = r * cosf(theta) * HEIGHT / WIDTH; - float y = r * sinf(theta); - particle.position = glm::vec2(x, y); - particle.velocity = normalize(glm::vec2(x,y)) * 0.00025f; - particle.color = glm::vec4(rndDist(rndEngine), rndDist(rndEngine), rndDist(rndEngine), 1.0f); - } - - vk::DeviceSize bufferSize = sizeof(Particle) * PARTICLE_COUNT; - - // Create a staging buffer used to upload data to the gpu - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(dataStaging, particles.data(), (size_t)bufferSize); - stagingBufferMemory.unmapMemory(); - - shaderStorageBuffers.clear(); - shaderStorageBuffersMemory.clear(); - - // Copy initial particle data to all storage buffers - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::raii::Buffer shaderStorageBufferTemp({}); - vk::raii::DeviceMemory shaderStorageBufferTempMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eStorageBuffer | vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eTransferDst, vk::MemoryPropertyFlagBits::eDeviceLocal, shaderStorageBufferTemp, shaderStorageBufferTempMemory); - copyBuffer(stagingBuffer, shaderStorageBufferTemp, bufferSize); - shaderStorageBuffers.emplace_back(std::move(shaderStorageBufferTemp)); - shaderStorageBuffersMemory.emplace_back(std::move(shaderStorageBufferTempMemory)); - } - } - - void createUniformBuffers() { - uniformBuffers.clear(); - uniformBuffersMemory.clear(); - uniformBuffersMapped.clear(); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DeviceSize bufferSize = sizeof(UniformBufferObject); - vk::raii::Buffer buffer({}); - vk::raii::DeviceMemory bufferMem({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); - uniformBuffers.emplace_back(std::move(buffer)); - uniformBuffersMemory.emplace_back(std::move(bufferMem)); - uniformBuffersMapped.emplace_back( uniformBuffersMemory[i].mapMemory(0, bufferSize)); - } - } - - void createDescriptorPool() { - std::array poolSize { - vk::DescriptorPoolSize( vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT), - vk::DescriptorPoolSize( vk::DescriptorType::eStorageBuffer, MAX_FRAMES_IN_FLIGHT * 2) - }; - vk::DescriptorPoolCreateInfo poolInfo{}; - poolInfo.flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet; - poolInfo.maxSets = MAX_FRAMES_IN_FLIGHT; - poolInfo.poolSizeCount = poolSize.size(); - poolInfo.pPoolSizes = poolSize.data(); - descriptorPool = vk::raii::DescriptorPool(device, poolInfo); - } - - void createComputeDescriptorSets() { - std::vector layouts(MAX_FRAMES_IN_FLIGHT, computeDescriptorSetLayout); - vk::DescriptorSetAllocateInfo allocInfo{}; - allocInfo.descriptorPool = *descriptorPool; - allocInfo.descriptorSetCount = MAX_FRAMES_IN_FLIGHT; - allocInfo.pSetLayouts = layouts.data(); - computeDescriptorSets.clear(); - computeDescriptorSets = device.allocateDescriptorSets(allocInfo); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DescriptorBufferInfo bufferInfo(uniformBuffers[i], 0, sizeof(UniformBufferObject)); - - vk::DescriptorBufferInfo storageBufferInfoLastFrame(shaderStorageBuffers[(i - 1) % MAX_FRAMES_IN_FLIGHT], 0, sizeof(Particle) * PARTICLE_COUNT); - vk::DescriptorBufferInfo storageBufferInfoCurrentFrame(shaderStorageBuffers[i], 0, sizeof(Particle) * PARTICLE_COUNT); - std::array descriptorWrites{ - vk::WriteDescriptorSet{ .dstSet = *computeDescriptorSets[i], .dstBinding = 0, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eUniformBuffer, .pImageInfo = nullptr, .pBufferInfo = &bufferInfo, .pTexelBufferView = nullptr }, - vk::WriteDescriptorSet{ .dstSet = *computeDescriptorSets[i], .dstBinding = 1, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eStorageBuffer, .pImageInfo = nullptr, .pBufferInfo = &storageBufferInfoLastFrame, .pTexelBufferView = nullptr }, - vk::WriteDescriptorSet{ .dstSet = *computeDescriptorSets[i], .dstBinding = 2, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eStorageBuffer, .pImageInfo = nullptr, .pBufferInfo = &storageBufferInfoCurrentFrame, .pTexelBufferView = nullptr }, - }; - device.updateDescriptorSets(descriptorWrites, {}); - } - } - - - void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer& buffer, vk::raii::DeviceMemory& bufferMemory) const { - vk::BufferCreateInfo bufferInfo{}; - bufferInfo.size = size; - bufferInfo.usage = usage; - bufferInfo.sharingMode = vk::SharingMode::eExclusive; - buffer = vk::raii::Buffer(device, bufferInfo); - vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{}; - allocInfo.allocationSize = memRequirements.size; - allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); - bufferMemory = vk::raii::DeviceMemory(device, allocInfo); - buffer.bindMemory(bufferMemory, 0); - } - - [[nodiscard]] vk::raii::CommandBuffer beginSingleTimeCommands() const { - vk::CommandBufferAllocateInfo allocInfo{}; - allocInfo.commandPool = *commandPool; - allocInfo.level = vk::CommandBufferLevel::ePrimary; - allocInfo.commandBufferCount = 1; - vk::raii::CommandBuffer commandBuffer = std::move(vk::raii::CommandBuffers( device, allocInfo ).front()); - - vk::CommandBufferBeginInfo beginInfo{ .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit }; - commandBuffer.begin(beginInfo); - - return commandBuffer; - } - - void endSingleTimeCommands(const vk::raii::CommandBuffer& commandBuffer) const { - commandBuffer.end(); - - vk::SubmitInfo submitInfo{}; - submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &*commandBuffer; - queue.submit(submitInfo, nullptr); - queue.waitIdle(); - } - - void copyBuffer(const vk::raii::Buffer & srcBuffer, const vk::raii::Buffer & dstBuffer, vk::DeviceSize size) const { - vk::raii::CommandBuffer commandCopyBuffer = beginSingleTimeCommands(); - commandCopyBuffer.copyBuffer(srcBuffer, dstBuffer, vk::BufferCopy(0, 0, size)); - endSingleTimeCommands(commandCopyBuffer); - } - - [[nodiscard]] uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) const { - vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); - - for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { - if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { - return i; - } - } - - throw std::runtime_error("failed to find suitable memory type!"); - } - - void createCommandBuffers() { - commandBuffers.clear(); - vk::CommandBufferAllocateInfo allocInfo{}; - allocInfo.commandPool = *commandPool; - allocInfo.level = vk::CommandBufferLevel::ePrimary; - allocInfo.commandBufferCount = MAX_FRAMES_IN_FLIGHT; - commandBuffers = vk::raii::CommandBuffers(device, allocInfo); - } - - void createComputeCommandBuffers() { - computeCommandBuffers.clear(); - vk::CommandBufferAllocateInfo allocInfo{}; - allocInfo.commandPool = *commandPool; - allocInfo.level = vk::CommandBufferLevel::ePrimary; - allocInfo.commandBufferCount = MAX_FRAMES_IN_FLIGHT; - computeCommandBuffers = vk::raii::CommandBuffers(device, allocInfo); - } - - void recordCommandBuffer( uint32_t imageIndex) { - commandBuffers[currentFrame].reset(); - commandBuffers[currentFrame].begin( {} ); - // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL - transition_image_layout( - imageIndex, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage - ); - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::RenderingAttachmentInfo attachmentInfo = { - .imageView = swapChainImageViews[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor - }; - vk::RenderingInfo renderingInfo = { - .renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &attachmentInfo - }; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor( 0, vk::Rect2D( vk::Offset2D( 0, 0 ), swapChainExtent ) ); - commandBuffers[currentFrame].bindVertexBuffers(0, { shaderStorageBuffers[currentFrame] }, {0}); - commandBuffers[currentFrame].draw( PARTICLE_COUNT, 1, 0, 0 ); - commandBuffers[currentFrame].endRendering(); - // After rendering, transition the swapchain image to PRESENT_SRC - transition_image_layout( - imageIndex, - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage - ); - commandBuffers[currentFrame].end(); - } - - void transition_image_layout( - uint32_t imageIndex, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::AccessFlags2 src_access_mask, - vk::AccessFlags2 dst_access_mask, - vk::PipelineStageFlags2 src_stage_mask, - vk::PipelineStageFlags2 dst_stage_mask - ) { - vk::ImageMemoryBarrier2 barrier = { - .srcStageMask = src_stage_mask, - .srcAccessMask = src_access_mask, - .dstStageMask = dst_stage_mask, - .dstAccessMask = dst_access_mask, - .oldLayout = old_layout, - .newLayout = new_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); - } - - void recordComputeCommandBuffer() { - computeCommandBuffers[currentFrame].reset(); - computeCommandBuffers[currentFrame].begin({}); - computeCommandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eCompute, computePipeline); - computeCommandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eCompute, computePipelineLayout, 0, {computeDescriptorSets[currentFrame]}, {}); - computeCommandBuffers[currentFrame].dispatch( PARTICLE_COUNT / 256, 1, 1 ); - computeCommandBuffers[currentFrame].end(); - } - - void createSyncObjects() { - inFlightFences.clear(); - - vk::SemaphoreTypeCreateInfo semaphoreType{ .semaphoreType = vk::SemaphoreType::eTimeline, .initialValue = 0 }; - semaphore = vk::raii::Semaphore(device, {.pNext = &semaphoreType}); - timelineValue = 0; - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::FenceCreateInfo fenceInfo{}; - inFlightFences.emplace_back(device, fenceInfo); - } - } - - void updateUniformBuffer(uint32_t currentImage) { - UniformBufferObject ubo{}; - ubo.deltaTime = static_cast(lastFrameTime) * 2.0f; - - memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); - } - - void drawFrame() { - auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, nullptr, *inFlightFences[currentFrame]); - while ( vk::Result::eTimeout == device.waitForFences( *inFlightFences[currentFrame], vk::True, UINT64_MAX ) ) - ; - device.resetFences(*inFlightFences[currentFrame]); - - // Update timeline value for this frame - uint64_t computeWaitValue = timelineValue; - uint64_t computeSignalValue = ++timelineValue; - uint64_t graphicsWaitValue = computeSignalValue; - uint64_t graphicsSignalValue = ++timelineValue; - - updateUniformBuffer(currentFrame); - - { - recordComputeCommandBuffer(); - // Submit compute work - vk::TimelineSemaphoreSubmitInfo computeTimelineInfo{ - .waitSemaphoreValueCount = 1, - .pWaitSemaphoreValues = &computeWaitValue, - .signalSemaphoreValueCount = 1, - .pSignalSemaphoreValues = &computeSignalValue - }; - - vk::PipelineStageFlags waitStages[] = {vk::PipelineStageFlagBits::eComputeShader}; - - vk::SubmitInfo computeSubmitInfo{ - .pNext = &computeTimelineInfo, - .waitSemaphoreCount = 1, - .pWaitSemaphores = &*semaphore, - .pWaitDstStageMask = waitStages, - .commandBufferCount = 1, - .pCommandBuffers = &*computeCommandBuffers[currentFrame], - .signalSemaphoreCount = 1, - .pSignalSemaphores = &*semaphore - }; - - queue.submit(computeSubmitInfo, nullptr); - } - { - // Record graphics command buffer - recordCommandBuffer(imageIndex); - - // Submit graphics work (waits for compute to finish) - vk::PipelineStageFlags waitStage = vk::PipelineStageFlagBits::eVertexInput; - vk::TimelineSemaphoreSubmitInfo graphicsTimelineInfo{ - .waitSemaphoreValueCount = 1, - .pWaitSemaphoreValues = &graphicsWaitValue, - .signalSemaphoreValueCount = 1, - .pSignalSemaphoreValues = &graphicsSignalValue - }; - - vk::SubmitInfo graphicsSubmitInfo{ - .pNext = &graphicsTimelineInfo, - .waitSemaphoreCount = 1, - .pWaitSemaphores = &*semaphore, - .pWaitDstStageMask = &waitStage, - .commandBufferCount = 1, - .pCommandBuffers = &*commandBuffers[currentFrame], - .signalSemaphoreCount = 1, - .pSignalSemaphores = &*semaphore + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain + featureChain = { + {.features = {.samplerAnisotropy = true}}, // vk::PhysicalDeviceFeatures2 + {.synchronization2 = true, .dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true}, // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + {.timelineSemaphore = true} // vk::PhysicalDeviceTimelineSemaphoreFeaturesKHR + }; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{.pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(*surface); + swapChainExtent = chooseSwapExtent(surfaceCapabilities); + swapChainSurfaceFormat = chooseSwapSurfaceFormat(physicalDevice.getSurfaceFormatsKHR(*surface)); + vk::SwapchainCreateInfoKHR swapChainCreateInfo{.surface = *surface, + .minImageCount = chooseSwapMinImageCount(surfaceCapabilities), + .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + void createImageViews() + { + vk::ImageViewCreateInfo imageViewCreateInfo{ + .viewType = vk::ImageViewType::e2D, + .format = swapChainSurfaceFormat.format, + .components = {vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity}, + .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + for (auto image : swapChainImages) + { + imageViewCreateInfo.image = image; + swapChainImageViews.emplace_back(device, imageViewCreateInfo); + } + } + + void createComputeDescriptorSetLayout() + { + std::array layoutBindings{ + vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eCompute, nullptr), + vk::DescriptorSetLayoutBinding(1, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eCompute, nullptr), + vk::DescriptorSetLayoutBinding(2, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eCompute, nullptr)}; + + vk::DescriptorSetLayoutCreateInfo layoutInfo{.bindingCount = static_cast(layoutBindings.size()), .pBindings = layoutBindings.data()}; + computeDescriptorSetLayout = vk::raii::DescriptorSetLayout(device, layoutInfo); + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; + + auto bindingDescription = Particle::getBindingDescription(); + auto attributeDescriptions = Particle::getAttributeDescriptions(); + + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{.vertexBindingDescriptionCount = 1, .pVertexBindingDescriptions = &bindingDescription, .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), .pVertexAttributeDescriptions = attributeDescriptions.data()}; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{.topology = vk::PrimitiveTopology::ePointList, .primitiveRestartEnable = vk::False}; + vk::PipelineViewportStateCreateInfo viewportState{.viewportCount = 1, .scissorCount = 1}; + vk::PipelineRasterizationStateCreateInfo rasterizer{ + .depthClampEnable = vk::False, + .rasterizerDiscardEnable = vk::False, + .polygonMode = vk::PolygonMode::eFill, + .cullMode = vk::CullModeFlagBits::eBack, + .frontFace = vk::FrontFace::eCounterClockwise, + .depthBiasEnable = vk::False, + .lineWidth = 1.0f}; + vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; + + vk::PipelineColorBlendAttachmentState colorBlendAttachment{ + .blendEnable = vk::True, + .srcColorBlendFactor = vk::BlendFactor::eSrcAlpha, + .dstColorBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha, + .colorBlendOp = vk::BlendOp::eAdd, + .srcAlphaBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha, + .dstAlphaBlendFactor = vk::BlendFactor::eZero, + .alphaBlendOp = vk::BlendOp::eAdd, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA, + }; + + vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo; + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format}; + vk::GraphicsPipelineCreateInfo pipelineInfo{.pNext = &pipelineRenderingCreateInfo, + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = *pipelineLayout, + .subpass = 0}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + void createComputePipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.spv")); + + vk::PipelineShaderStageCreateInfo computeShaderStageInfo{.stage = vk::ShaderStageFlagBits::eCompute, .module = shaderModule, .pName = "compMain"}; + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 1, .pSetLayouts = &*computeDescriptorSetLayout}; + computePipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + vk::ComputePipelineCreateInfo pipelineInfo{.stage = computeShaderStageInfo, .layout = *computePipelineLayout}; + computePipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{}; + poolInfo.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer; + poolInfo.queueFamilyIndex = queueIndex; + commandPool = vk::raii::CommandPool(device, poolInfo); + } + + void createShaderStorageBuffers() + { + // Initialize particles + std::default_random_engine rndEngine(static_cast(time(nullptr))); + std::uniform_real_distribution rndDist(0.0f, 1.0f); + + // Initial particle positions on a circle + std::vector particles(PARTICLE_COUNT); + for (auto &particle : particles) + { + float r = 0.25f * sqrtf(rndDist(rndEngine)); + float theta = rndDist(rndEngine) * 2.0f * 3.14159265358979323846f; + float x = r * cosf(theta) * HEIGHT / WIDTH; + float y = r * sinf(theta); + particle.position = glm::vec2(x, y); + particle.velocity = normalize(glm::vec2(x, y)) * 0.00025f; + particle.color = glm::vec4(rndDist(rndEngine), rndDist(rndEngine), rndDist(rndEngine), 1.0f); + } + + vk::DeviceSize bufferSize = sizeof(Particle) * PARTICLE_COUNT; + + // Create a staging buffer used to upload data to the gpu + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(dataStaging, particles.data(), (size_t) bufferSize); + stagingBufferMemory.unmapMemory(); + + shaderStorageBuffers.clear(); + shaderStorageBuffersMemory.clear(); + + // Copy initial particle data to all storage buffers + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::raii::Buffer shaderStorageBufferTemp({}); + vk::raii::DeviceMemory shaderStorageBufferTempMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eStorageBuffer | vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eTransferDst, vk::MemoryPropertyFlagBits::eDeviceLocal, shaderStorageBufferTemp, shaderStorageBufferTempMemory); + copyBuffer(stagingBuffer, shaderStorageBufferTemp, bufferSize); + shaderStorageBuffers.emplace_back(std::move(shaderStorageBufferTemp)); + shaderStorageBuffersMemory.emplace_back(std::move(shaderStorageBufferTempMemory)); + } + } + + void createUniformBuffers() + { + uniformBuffers.clear(); + uniformBuffersMemory.clear(); + uniformBuffersMapped.clear(); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DeviceSize bufferSize = sizeof(UniformBufferObject); + vk::raii::Buffer buffer({}); + vk::raii::DeviceMemory bufferMem({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); + uniformBuffers.emplace_back(std::move(buffer)); + uniformBuffersMemory.emplace_back(std::move(bufferMem)); + uniformBuffersMapped.emplace_back(uniformBuffersMemory[i].mapMemory(0, bufferSize)); + } + } + + void createDescriptorPool() + { + std::array poolSize{ + vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT), + vk::DescriptorPoolSize(vk::DescriptorType::eStorageBuffer, MAX_FRAMES_IN_FLIGHT * 2)}; + vk::DescriptorPoolCreateInfo poolInfo{}; + poolInfo.flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet; + poolInfo.maxSets = MAX_FRAMES_IN_FLIGHT; + poolInfo.poolSizeCount = poolSize.size(); + poolInfo.pPoolSizes = poolSize.data(); + descriptorPool = vk::raii::DescriptorPool(device, poolInfo); + } + + void createComputeDescriptorSets() + { + std::vector layouts(MAX_FRAMES_IN_FLIGHT, computeDescriptorSetLayout); + vk::DescriptorSetAllocateInfo allocInfo{}; + allocInfo.descriptorPool = *descriptorPool; + allocInfo.descriptorSetCount = MAX_FRAMES_IN_FLIGHT; + allocInfo.pSetLayouts = layouts.data(); + computeDescriptorSets.clear(); + computeDescriptorSets = device.allocateDescriptorSets(allocInfo); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DescriptorBufferInfo bufferInfo(uniformBuffers[i], 0, sizeof(UniformBufferObject)); + + vk::DescriptorBufferInfo storageBufferInfoLastFrame(shaderStorageBuffers[(i - 1) % MAX_FRAMES_IN_FLIGHT], 0, sizeof(Particle) * PARTICLE_COUNT); + vk::DescriptorBufferInfo storageBufferInfoCurrentFrame(shaderStorageBuffers[i], 0, sizeof(Particle) * PARTICLE_COUNT); + std::array descriptorWrites{ + vk::WriteDescriptorSet{.dstSet = *computeDescriptorSets[i], .dstBinding = 0, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eUniformBuffer, .pImageInfo = nullptr, .pBufferInfo = &bufferInfo, .pTexelBufferView = nullptr}, + vk::WriteDescriptorSet{.dstSet = *computeDescriptorSets[i], .dstBinding = 1, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eStorageBuffer, .pImageInfo = nullptr, .pBufferInfo = &storageBufferInfoLastFrame, .pTexelBufferView = nullptr}, + vk::WriteDescriptorSet{.dstSet = *computeDescriptorSets[i], .dstBinding = 2, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eStorageBuffer, .pImageInfo = nullptr, .pBufferInfo = &storageBufferInfoCurrentFrame, .pTexelBufferView = nullptr}, }; - - queue.submit(graphicsSubmitInfo, nullptr); - - // Present the image (wait for graphics to finish) - vk::SemaphoreWaitInfo waitInfo{ - .semaphoreCount = 1, - .pSemaphores = &*semaphore, - .pValues = &graphicsSignalValue - }; - - // Wait for graphics to complete before presenting - while ( vk::Result::eTimeout ==device.waitSemaphores(waitInfo, UINT64_MAX) ) - ; - - vk::PresentInfoKHR presentInfo{ - .waitSemaphoreCount = 0, // No binary semaphores needed - .pWaitSemaphores = nullptr, - .swapchainCount = 1, - .pSwapchains = &*swapChain, - .pImageIndices = &imageIndex - }; - - result = queue.presentKHR(presentInfo); - if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { - framebufferResized = false; - recreateSwapChain(); - } else if (result != vk::Result::eSuccess) { - throw std::runtime_error("failed to present swap chain image!"); - } - } - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; - } - - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size(), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const & surfaceCapabilities) { - auto minImageCount = std::max( 3u, surfaceCapabilities.minImageCount ); - if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) { - minImageCount = surfaceCapabilities.maxImageCount; - } - return minImageCount; - } - - static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { - assert(!availableFormats.empty()); - const auto formatIt = std::ranges::find_if( - availableFormats, - []( const auto & format ) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; } ); - return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - assert(std::ranges::any_of(availablePresentModes, [](auto presentMode){ return presentMode == vk::PresentModeKHR::eFifo; })); - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eFifo; - } - - [[nodiscard]] vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) const { - if (capabilities.currentExtent.width != 0xFFFFFFFF) { - 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) - }; - } - - [[nodiscard]] std::vector getRequiredExtensions() const { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - 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; - } + device.updateDescriptorSets(descriptorWrites, {}); + } + } + + void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer &buffer, vk::raii::DeviceMemory &bufferMemory) const + { + vk::BufferCreateInfo bufferInfo{}; + bufferInfo.size = size; + bufferInfo.usage = usage; + bufferInfo.sharingMode = vk::SharingMode::eExclusive; + buffer = vk::raii::Buffer(device, bufferInfo); + vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{}; + allocInfo.allocationSize = memRequirements.size; + allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); + bufferMemory = vk::raii::DeviceMemory(device, allocInfo); + buffer.bindMemory(bufferMemory, 0); + } + + [[nodiscard]] vk::raii::CommandBuffer beginSingleTimeCommands() const + { + vk::CommandBufferAllocateInfo allocInfo{}; + allocInfo.commandPool = *commandPool; + allocInfo.level = vk::CommandBufferLevel::ePrimary; + allocInfo.commandBufferCount = 1; + vk::raii::CommandBuffer commandBuffer = std::move(vk::raii::CommandBuffers(device, allocInfo).front()); + + vk::CommandBufferBeginInfo beginInfo{.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; + commandBuffer.begin(beginInfo); + + return commandBuffer; + } + + void endSingleTimeCommands(const vk::raii::CommandBuffer &commandBuffer) const + { + commandBuffer.end(); + + vk::SubmitInfo submitInfo{}; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &*commandBuffer; + queue.submit(submitInfo, nullptr); + queue.waitIdle(); + } + + void copyBuffer(const vk::raii::Buffer &srcBuffer, const vk::raii::Buffer &dstBuffer, vk::DeviceSize size) const + { + vk::raii::CommandBuffer commandCopyBuffer = beginSingleTimeCommands(); + commandCopyBuffer.copyBuffer(srcBuffer, dstBuffer, vk::BufferCopy(0, 0, size)); + endSingleTimeCommands(commandCopyBuffer); + } + + [[nodiscard]] uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) const + { + vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) + { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) + { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + void createCommandBuffers() + { + commandBuffers.clear(); + vk::CommandBufferAllocateInfo allocInfo{}; + allocInfo.commandPool = *commandPool; + allocInfo.level = vk::CommandBufferLevel::ePrimary; + allocInfo.commandBufferCount = MAX_FRAMES_IN_FLIGHT; + commandBuffers = vk::raii::CommandBuffers(device, allocInfo); + } + + void createComputeCommandBuffers() + { + computeCommandBuffers.clear(); + vk::CommandBufferAllocateInfo allocInfo{}; + allocInfo.commandPool = *commandPool; + allocInfo.level = vk::CommandBufferLevel::ePrimary; + allocInfo.commandBufferCount = MAX_FRAMES_IN_FLIGHT; + computeCommandBuffers = vk::raii::CommandBuffers(device, allocInfo); + } + + void recordCommandBuffer(uint32_t imageIndex) + { + commandBuffers[currentFrame].reset(); + commandBuffers[currentFrame].begin({}); + // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL + transition_image_layout( + imageIndex, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eTopOfPipe, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage + ); + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::RenderingAttachmentInfo attachmentInfo = { + .imageView = swapChainImageViews[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor}; + vk::RenderingInfo renderingInfo = { + .renderArea = {.offset = {0, 0}, .extent = swapChainExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &attachmentInfo}; + commandBuffers[currentFrame].beginRendering(renderingInfo); + commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffers[currentFrame].bindVertexBuffers(0, {shaderStorageBuffers[currentFrame]}, {0}); + commandBuffers[currentFrame].draw(PARTICLE_COUNT, 1, 0, 0); + commandBuffers[currentFrame].endRendering(); + // After rendering, transition the swapchain image to PRESENT_SRC + transition_image_layout( + imageIndex, + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage + ); + commandBuffers[currentFrame].end(); + } + + void transition_image_layout( + uint32_t imageIndex, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout, + vk::AccessFlags2 src_access_mask, + vk::AccessFlags2 dst_access_mask, + vk::PipelineStageFlags2 src_stage_mask, + vk::PipelineStageFlags2 dst_stage_mask) + { + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = src_stage_mask, + .srcAccessMask = src_access_mask, + .dstStageMask = dst_stage_mask, + .dstAccessMask = dst_access_mask, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo dependency_info = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + } + + void recordComputeCommandBuffer() + { + computeCommandBuffers[currentFrame].reset(); + computeCommandBuffers[currentFrame].begin({}); + computeCommandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eCompute, computePipeline); + computeCommandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eCompute, computePipelineLayout, 0, {computeDescriptorSets[currentFrame]}, {}); + computeCommandBuffers[currentFrame].dispatch(PARTICLE_COUNT / 256, 1, 1); + computeCommandBuffers[currentFrame].end(); + } + + void createSyncObjects() + { + inFlightFences.clear(); + + vk::SemaphoreTypeCreateInfo semaphoreType{.semaphoreType = vk::SemaphoreType::eTimeline, .initialValue = 0}; + semaphore = vk::raii::Semaphore(device, {.pNext = &semaphoreType}); + timelineValue = 0; + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::FenceCreateInfo fenceInfo{}; + inFlightFences.emplace_back(device, fenceInfo); + } + } + + void updateUniformBuffer(uint32_t currentImage) + { + UniformBufferObject ubo{}; + ubo.deltaTime = static_cast(lastFrameTime) * 2.0f; + + memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); + } + + void drawFrame() + { + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, nullptr, *inFlightFences[currentFrame]); + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + ; + device.resetFences(*inFlightFences[currentFrame]); + + // Update timeline value for this frame + uint64_t computeWaitValue = timelineValue; + uint64_t computeSignalValue = ++timelineValue; + uint64_t graphicsWaitValue = computeSignalValue; + uint64_t graphicsSignalValue = ++timelineValue; + + updateUniformBuffer(currentFrame); + + { + recordComputeCommandBuffer(); + // Submit compute work + vk::TimelineSemaphoreSubmitInfo computeTimelineInfo{ + .waitSemaphoreValueCount = 1, + .pWaitSemaphoreValues = &computeWaitValue, + .signalSemaphoreValueCount = 1, + .pSignalSemaphoreValues = &computeSignalValue}; + + vk::PipelineStageFlags waitStages[] = {vk::PipelineStageFlagBits::eComputeShader}; + + vk::SubmitInfo computeSubmitInfo{ + .pNext = &computeTimelineInfo, + .waitSemaphoreCount = 1, + .pWaitSemaphores = &*semaphore, + .pWaitDstStageMask = waitStages, + .commandBufferCount = 1, + .pCommandBuffers = &*computeCommandBuffers[currentFrame], + .signalSemaphoreCount = 1, + .pSignalSemaphores = &*semaphore}; + + queue.submit(computeSubmitInfo, nullptr); + } + { + // Record graphics command buffer + recordCommandBuffer(imageIndex); + + // Submit graphics work (waits for compute to finish) + vk::PipelineStageFlags waitStage = vk::PipelineStageFlagBits::eVertexInput; + vk::TimelineSemaphoreSubmitInfo graphicsTimelineInfo{ + .waitSemaphoreValueCount = 1, + .pWaitSemaphoreValues = &graphicsWaitValue, + .signalSemaphoreValueCount = 1, + .pSignalSemaphoreValues = &graphicsSignalValue}; + + vk::SubmitInfo graphicsSubmitInfo{ + .pNext = &graphicsTimelineInfo, + .waitSemaphoreCount = 1, + .pWaitSemaphores = &*semaphore, + .pWaitDstStageMask = &waitStage, + .commandBufferCount = 1, + .pCommandBuffers = &*commandBuffers[currentFrame], + .signalSemaphoreCount = 1, + .pSignalSemaphores = &*semaphore}; + + queue.submit(graphicsSubmitInfo, nullptr); + + // Present the image (wait for graphics to finish) + vk::SemaphoreWaitInfo waitInfo{ + .semaphoreCount = 1, + .pSemaphores = &*semaphore, + .pValues = &graphicsSignalValue}; + + // Wait for graphics to complete before presenting + while (vk::Result::eTimeout == device.waitSemaphores(waitInfo, UINT64_MAX)) + ; + + vk::PresentInfoKHR presentInfo{ + .waitSemaphoreCount = 0, // No binary semaphores needed + .pWaitSemaphores = nullptr, + .swapchainCount = 1, + .pSwapchains = &*swapChain, + .pImageIndices = &imageIndex}; + + result = queue.presentKHR(presentInfo); + if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) + { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != vk::Result::eSuccess) + { + throw std::runtime_error("failed to present swap chain image!"); + } + } + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size(), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const &surfaceCapabilities) + { + auto minImageCount = std::max(3u, surfaceCapabilities.minImageCount); + if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) + { + minImageCount = surfaceCapabilities.maxImageCount; + } + return minImageCount; + } + + static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + assert(!availableFormats.empty()); + const auto formatIt = std::ranges::find_if( + availableFormats, + [](const auto &format) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; }); + return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + assert(std::ranges::any_of(availablePresentModes, [](auto presentMode) { return presentMode == vk::PresentModeKHR::eFifo; })); + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + vk::PresentModeKHR::eFifo; + } + + [[nodiscard]] vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR &capabilities) const + { + if (capabilities.currentExtent.width != 0xFFFFFFFF) + { + 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)}; + } + + [[nodiscard]] std::vector getRequiredExtensions() const + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + 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; + } }; -int main() { - try { - ComputeShaderApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + ComputeShaderApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/32_ecosystem_utilities.cpp b/attachments/32_ecosystem_utilities.cpp index 18f9e914..96e06969 100644 --- a/attachments/32_ecosystem_utilities.cpp +++ b/attachments/32_ecosystem_utilities.cpp @@ -13,13 +13,13 @@ #include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include #define GLM_FORCE_RADIANS @@ -35,1671 +35,1779 @@ import vulkan_hpp; #define TINYOBJLOADER_IMPLEMENTATION #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -constexpr uint64_t FenceTimeout = 100000000; -const std::string MODEL_PATH = "models/viking_room.obj"; -const std::string TEXTURE_PATH = "textures/viking_room.png"; -constexpr int MAX_FRAMES_IN_FLIGHT = 2; +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; +constexpr uint64_t FenceTimeout = 100000000; +const std::string MODEL_PATH = "models/viking_room.obj"; +const std::string TEXTURE_PATH = "textures/viking_room.png"; +constexpr int MAX_FRAMES_IN_FLIGHT = 2; // Validation layers are now managed by vulkanconfig instead of being hard-coded // See the Ecosystem Utilities chapter for details on using vulkanconfig // Application info structure to store feature support flags -struct AppInfo { - bool dynamicRenderingSupported = false; - bool timelineSemaphoresSupported = false; - bool synchronization2Supported = false; +struct AppInfo +{ + bool dynamicRenderingSupported = false; + bool timelineSemaphoresSupported = false; + bool synchronization2Supported = false; }; -struct Vertex { - glm::vec3 pos; - glm::vec3 color; - glm::vec2 texCoord; - - static vk::VertexInputBindingDescription getBindingDescription() { - return { 0, sizeof(Vertex), vk::VertexInputRate::eVertex }; - } - - static std::array getAttributeDescriptions() { - return { - vk::VertexInputAttributeDescription( 0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, pos) ), - vk::VertexInputAttributeDescription( 1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color) ), - vk::VertexInputAttributeDescription( 2, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, texCoord) ) - }; - } - - bool operator==(const Vertex& other) const { - return pos == other.pos && color == other.color && texCoord == other.texCoord; - } +struct Vertex +{ + glm::vec3 pos; + glm::vec3 color; + glm::vec2 texCoord; + + static vk::VertexInputBindingDescription getBindingDescription() + { + return {0, sizeof(Vertex), vk::VertexInputRate::eVertex}; + } + + static std::array getAttributeDescriptions() + { + return { + vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, pos)), + vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color)), + vk::VertexInputAttributeDescription(2, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, texCoord))}; + } + + bool operator==(const Vertex &other) const + { + return pos == other.pos && color == other.color && texCoord == other.texCoord; + } }; -template<> struct std::hash { - size_t operator()(Vertex const& vertex) const noexcept { - return ((hash()(vertex.pos) ^ (hash()(vertex.color) << 1)) >> 1) ^ (hash()(vertex.texCoord) << 1); - } - }; +template <> +struct std::hash +{ + size_t operator()(Vertex const &vertex) const noexcept + { + return ((hash()(vertex.pos) ^ (hash()(vertex.color) << 1)) >> 1) ^ (hash()(vertex.texCoord) << 1); + } +}; -struct UniformBufferObject { - alignas(16) glm::mat4 model; - alignas(16) glm::mat4 view; - alignas(16) glm::mat4 proj; +struct UniformBufferObject +{ + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; }; -class HelloTriangleApplication { -public: - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } - -private: - AppInfo appInfo; - - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::SampleCountFlagBits msaaSamples = vk::SampleCountFlagBits::e1; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::SurfaceFormatKHR swapChainSurfaceFormat; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - // Traditional render pass (fallback for non-dynamic rendering) - vk::raii::RenderPass renderPass = nullptr; - std::vector swapChainFramebuffers; - - vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - - vk::raii::Image colorImage = nullptr; - vk::raii::DeviceMemory colorImageMemory = nullptr; - vk::raii::ImageView colorImageView = nullptr; - - vk::raii::Image depthImage = nullptr; - vk::raii::DeviceMemory depthImageMemory = nullptr; - vk::raii::ImageView depthImageView = nullptr; - - uint32_t mipLevels = 0; - vk::raii::Image textureImage = nullptr; - vk::raii::DeviceMemory textureImageMemory = nullptr; - vk::raii::ImageView textureImageView = nullptr; - vk::raii::Sampler textureSampler = nullptr; - - std::vector vertices; - std::vector indices; - vk::raii::Buffer vertexBuffer = nullptr; - vk::raii::DeviceMemory vertexBufferMemory = nullptr; - vk::raii::Buffer indexBuffer = nullptr; - vk::raii::DeviceMemory indexBufferMemory = nullptr; - - std::vector uniformBuffers; - std::vector uniformBuffersMemory; - std::vector uniformBuffersMapped; - - vk::raii::DescriptorPool descriptorPool = nullptr; - std::vector descriptorSets; - - vk::raii::CommandPool commandPool = nullptr; - std::vector commandBuffers; - - // Synchronization objects - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; - std::vector inFlightFences; - vk::raii::Semaphore timelineSemaphore = nullptr; - uint64_t timelineValue = 0; - uint32_t currentFrame = 0; - - bool framebufferResized = false; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName - }; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan Compatibility Example", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); - } - - static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { - auto app = static_cast(glfwGetWindowUserPointer(window)); - app->framebufferResized = true; - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - createSurface(); - pickPhysicalDevice(); - detectFeatureSupport(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - - // Create traditional render pass if dynamic rendering is not supported - if (!appInfo.dynamicRenderingSupported) { - createRenderPass(); - createFramebuffers(); - } - - createDescriptorSetLayout(); - createGraphicsPipeline(); - createCommandPool(); - createColorResources(); - createDepthResources(); - createTextureImage(); - createTextureImageView(); - createTextureSampler(); - loadModel(); - createVertexBuffer(); - createIndexBuffer(); - createUniformBuffers(); - createDescriptorPool(); - createDescriptorSets(); - createCommandBuffers(); - createSyncObjects(); - - // Print feature support summary - std::cout << "\nFeature support summary:\n"; - std::cout << "- Dynamic Rendering: " << (appInfo.dynamicRenderingSupported ? "Yes" : "No") << "\n"; - std::cout << "- Timeline Semaphores: " << (appInfo.timelineSemaphoresSupported ? "Yes" : "No") << "\n"; - std::cout << "- Synchronization2: " << (appInfo.synchronization2Supported ? "Yes" : "No") << "\n"; - } - - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - drawFrame(); - } - - device.waitIdle(); - } - - void cleanupSwapChain() { - swapChainFramebuffers.clear(); - swapChainImageViews.clear(); - swapChain = nullptr; - } - - void cleanup() const { - glfwDestroyWindow(window); - glfwTerminate(); - } - - void recreateSwapChain() { - int width = 0, height = 0; - glfwGetFramebufferSize(window, &width, &height); - while (width == 0 || height == 0) { - glfwGetFramebufferSize(window, &width, &height); - glfwWaitEvents(); - } - - device.waitIdle(); - - cleanupSwapChain(); - createSwapChain(); - createImageViews(); - - // Recreate traditional render pass and framebuffers if dynamic rendering is not supported - if (!appInfo.dynamicRenderingSupported) { - createRenderPass(); - createFramebuffers(); - } - - createColorResources(); - createDepthResources(); - } - - void createInstance() { - // Validation layers are now managed by vulkanconfig instead of being hard-coded - - 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 - }; - - auto extensions = getRequiredExtensions(); - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledExtensionCount = static_cast(extensions.size()), - .ppEnabledExtensionNames = extensions.data() - }; - - instance = vk::raii::Instance(context, createInfo); - } - - void setupDebugMessenger() { - // Always set up the debug messenger - // It will only be used if validation layers are enabled via vulkanconfig - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( - vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | - vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | - vk::DebugUtilsMessageSeverityFlagBitsEXT::eError - ); - - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( - vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | - vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | - vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation - ); - - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - - try { - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } catch (vk::SystemError& err) { - // If the debug utils extension is not available, this will fail - // That's okay; it just means validation layers aren't enabled - std::cout << "Debug messenger not available. Validation layers may not be enabled." << std::endl; - } - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&]( auto const & device ) - { - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of( requiredDeviceExtension, - [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) - { - return std::ranges::any_of( availableDeviceExtensions, - [requiredDeviceExtension]( auto const & availableDeviceExtension ) - { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); - } ); - - return supportsGraphics && supportsAllRequiredExtensions; - }); - if ( devIter != devices.end() ) - { - physicalDevice = *devIter; - msaaSamples = getMaxUsableSampleCount(); - } - else - { - throw std::runtime_error("failed to find a suitable GPU!"); - } - } - - void detectFeatureSupport() { - // Get device properties to check Vulkan version - vk::PhysicalDeviceProperties deviceProperties = physicalDevice.getProperties(); - - // Get available extensions - std::vector availableExtensions = physicalDevice.enumerateDeviceExtensionProperties(); - - // Check for dynamic rendering support - if (deviceProperties.apiVersion >= VK_VERSION_1_3) { - appInfo.dynamicRenderingSupported = true; - std::cout << "Dynamic rendering supported via Vulkan 1.3\n"; - } else { - // Check for the extension on older Vulkan versions - for (const auto& extension : availableExtensions) { - if (strcmp(extension.extensionName, VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME) == 0) { - appInfo.dynamicRenderingSupported = true; - std::cout << "Dynamic rendering supported via extension\n"; - break; - } - } - } - - // Check for timeline semaphores support - if (deviceProperties.apiVersion >= VK_VERSION_1_2) { - appInfo.timelineSemaphoresSupported = true; - std::cout << "Timeline semaphores supported via Vulkan 1.2\n"; - } else { - // Check for the extension on older Vulkan versions - for (const auto& extension : availableExtensions) { - if (strcmp(extension.extensionName, VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME) == 0) { - appInfo.timelineSemaphoresSupported = true; - std::cout << "Timeline semaphores supported via extension\n"; - break; - } - } - } - - // Check for synchronization2 support - if (deviceProperties.apiVersion >= VK_VERSION_1_3) { - appInfo.synchronization2Supported = true; - std::cout << "Synchronization2 supported via Vulkan 1.3\n"; - } else { - // Check for the extension on older Vulkan versions - for (const auto& extension : availableExtensions) { - if (strcmp(extension.extensionName, VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME) == 0) { - appInfo.synchronization2Supported = true; - std::cout << "Synchronization2 supported via extension\n"; - break; - } - } - } - - // Add required extensions based on feature support - if (appInfo.dynamicRenderingSupported && deviceProperties.apiVersion < VK_VERSION_1_3) { - requiredDeviceExtension.push_back(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME); - } - - if (appInfo.timelineSemaphoresSupported && deviceProperties.apiVersion < VK_VERSION_1_2) { - requiredDeviceExtension.push_back(VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME); - } - - if (appInfo.synchronization2Supported && deviceProperties.apiVersion < VK_VERSION_1_3) { - requiredDeviceExtension.push_back(VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // Create device with appropriate features - auto features = physicalDevice.getFeatures2(); - - // Setup feature chain based on detected support - void* pNext = nullptr; - - // Add dynamic rendering if supported - vk::PhysicalDeviceVulkan13Features vulkan13Features; - vk::PhysicalDeviceDynamicRenderingFeatures dynamicRenderingFeatures; - - if (appInfo.dynamicRenderingSupported) { - if (appInfo.synchronization2Supported) { - vulkan13Features.dynamicRendering = vk::True; - vulkan13Features.synchronization2 = vk::True; - vulkan13Features.pNext = pNext; - pNext = &vulkan13Features; - } else { - dynamicRenderingFeatures.dynamicRendering = vk::True; - dynamicRenderingFeatures.pNext = pNext; - pNext = &dynamicRenderingFeatures; - } - } - - // Add timeline semaphores if supported - vk::PhysicalDeviceTimelineSemaphoreFeatures timelineSemaphoreFeatures; - if (appInfo.timelineSemaphoresSupported) { - timelineSemaphoreFeatures.timelineSemaphore = vk::True; - timelineSemaphoreFeatures.pNext = pNext; - pNext = &timelineSemaphoreFeatures; - } - - features.pNext = pNext; - - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ - .pNext = &features, - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() - }; - - device = vk::raii::Device( physicalDevice, deviceCreateInfo ); - queue = vk::raii::Queue( device, queueIndex, 0 ); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( *surface ); - swapChainExtent = chooseSwapExtent( surfaceCapabilities ); - swapChainSurfaceFormat = chooseSwapSurfaceFormat( physicalDevice.getSurfaceFormatsKHR( *surface ) ); - vk::SwapchainCreateInfoKHR swapChainCreateInfo{ .surface = *surface, - .minImageCount = chooseSwapMinImageCount( surfaceCapabilities ), - .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 }; - - swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); - swapChainImages = swapChain.getImages(); - } - - void createImageViews() { - vk::ImageViewCreateInfo imageViewCreateInfo{ - .viewType = vk::ImageViewType::e2D, - .format = swapChainSurfaceFormat.format, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } - }; - for ( auto image : swapChainImages ) - { - imageViewCreateInfo.image = image; - swapChainImageViews.emplace_back( device, imageViewCreateInfo ); - } - } - - void createRenderPass() { - if (appInfo.dynamicRenderingSupported) { - // No render pass needed with dynamic rendering - std::cout << "Using dynamic rendering, skipping render pass creation\n"; - return; - } - - std::cout << "Creating traditional render pass\n"; - - // Color attachment description - vk::AttachmentDescription colorAttachment{ - .format = swapChainSurfaceFormat.format, - .samples = msaaSamples, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .stencilLoadOp = vk::AttachmentLoadOp::eDontCare, - .stencilStoreOp = vk::AttachmentStoreOp::eDontCare, - .initialLayout = vk::ImageLayout::eUndefined, - .finalLayout = vk::ImageLayout::eColorAttachmentOptimal - }; - - vk::AttachmentDescription depthAttachment{ - .format = findDepthFormat(), - .samples = msaaSamples, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eDontCare, - .stencilLoadOp = vk::AttachmentLoadOp::eDontCare, - .stencilStoreOp = vk::AttachmentStoreOp::eDontCare, - .initialLayout = vk::ImageLayout::eUndefined, - .finalLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal - }; - - vk::AttachmentDescription colorAttachmentResolve{ - .format = swapChainSurfaceFormat.format, - .samples = vk::SampleCountFlagBits::e1, - .loadOp = vk::AttachmentLoadOp::eDontCare, - .storeOp = vk::AttachmentStoreOp::eStore, - .stencilLoadOp = vk::AttachmentLoadOp::eDontCare, - .stencilStoreOp = vk::AttachmentStoreOp::eDontCare, - .initialLayout = vk::ImageLayout::eUndefined, - .finalLayout = vk::ImageLayout::ePresentSrcKHR - }; - - // Subpass references - vk::AttachmentReference colorAttachmentRef{ - .attachment = 0, - .layout = vk::ImageLayout::eColorAttachmentOptimal - }; - - vk::AttachmentReference depthAttachmentRef{ - .attachment = 1, - .layout = vk::ImageLayout::eDepthStencilAttachmentOptimal - }; - - vk::AttachmentReference colorAttachmentResolveRef{ - .attachment = 2, - .layout = vk::ImageLayout::eColorAttachmentOptimal - }; - - // Subpass description - vk::SubpassDescription subpass{ - .pipelineBindPoint = vk::PipelineBindPoint::eGraphics, - .colorAttachmentCount = 1, - .pColorAttachments = &colorAttachmentRef, - .pResolveAttachments = &colorAttachmentResolveRef, - .pDepthStencilAttachment = &depthAttachmentRef - }; - - // Dependency to ensure proper image layout transitions - vk::SubpassDependency dependency{ - .srcSubpass = VK_SUBPASS_EXTERNAL, - .dstSubpass = 0, - .srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eEarlyFragmentTests, - .dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eEarlyFragmentTests, - .srcAccessMask = vk::AccessFlagBits::eNone, - .dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite | vk::AccessFlagBits::eDepthStencilAttachmentWrite - }; - - // Create the render pass - std::array attachments = { colorAttachment, depthAttachment, colorAttachmentResolve }; - vk::RenderPassCreateInfo renderPassInfo{ - .attachmentCount = static_cast(attachments.size()), - .pAttachments = attachments.data(), - .subpassCount = 1, - .pSubpasses = &subpass, - .dependencyCount = 1, - .pDependencies = &dependency - }; - - renderPass = vk::raii::RenderPass(device, renderPassInfo); - } - - void createFramebuffers() { - if (appInfo.dynamicRenderingSupported) { - // No framebuffers needed with dynamic rendering - std::cout << "Using dynamic rendering, skipping framebuffer creation\n"; - return; - } - - std::cout << "Creating traditional framebuffers\n"; - - swapChainFramebuffers.clear(); - - for (size_t i = 0; i < swapChainImageViews.size(); i++) { - std::array attachments = { - *colorImageView, - *depthImageView, - *swapChainImageViews[i] - }; - - vk::FramebufferCreateInfo framebufferInfo{ - .renderPass = *renderPass, - .attachmentCount = static_cast(attachments.size()), - .pAttachments = attachments.data(), - .width = swapChainExtent.width, - .height = swapChainExtent.height, - .layers = 1 - }; - - swapChainFramebuffers.emplace_back(device, framebufferInfo); - } - } - - void createDescriptorSetLayout() { - std::array bindings = { - vk::DescriptorSetLayoutBinding( 0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr), - vk::DescriptorSetLayoutBinding( 1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment, nullptr) - }; - - vk::DescriptorSetLayoutCreateInfo layoutInfo{ .bindingCount = static_cast(bindings.size()), .pBindings = bindings.data() }; - descriptorSetLayout = vk::raii::DescriptorSetLayout(device, layoutInfo); - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; - - auto bindingDescription = Vertex::getBindingDescription(); - auto attributeDescriptions = Vertex::getAttributeDescriptions(); - vk::PipelineVertexInputStateCreateInfo vertexInputInfo{ - .vertexBindingDescriptionCount = 1, - .pVertexBindingDescriptions = &bindingDescription, - .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), - .pVertexAttributeDescriptions = attributeDescriptions.data() - }; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ - .topology = vk::PrimitiveTopology::eTriangleList, - .primitiveRestartEnable = vk::False - }; - vk::PipelineViewportStateCreateInfo viewportState{ - .viewportCount = 1, - .scissorCount = 1 - }; - vk::PipelineRasterizationStateCreateInfo rasterizer{ - .depthClampEnable = vk::False, - .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, - .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eCounterClockwise, - .depthBiasEnable = vk::False - }; - rasterizer.lineWidth = 1.0f; - vk::PipelineMultisampleStateCreateInfo multisampling{ - .rasterizationSamples = msaaSamples, - .sampleShadingEnable = vk::False - }; - vk::PipelineDepthStencilStateCreateInfo depthStencil{ - .depthTestEnable = vk::True, - .depthWriteEnable = vk::True, - .depthCompareOp = vk::CompareOp::eLess, - .depthBoundsTestEnable = vk::False, - .stencilTestEnable = vk::False - }; - vk::PipelineColorBlendAttachmentState colorBlendAttachment; - colorBlendAttachment.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA; - colorBlendAttachment.blendEnable = vk::False; - - vk::PipelineColorBlendStateCreateInfo colorBlending{ - .logicOpEnable = vk::False, - .logicOp = vk::LogicOp::eCopy, - .attachmentCount = 1, - .pAttachments = &colorBlendAttachment - }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0 }; - - pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); - - vk::GraphicsPipelineCreateInfo pipelineInfo{ - .stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pDepthStencilState = &depthStencil, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = pipelineLayout - }; - - // Configure pipeline based on dynamic rendering support - vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo; - if (appInfo.dynamicRenderingSupported) { - std::cout << "Configuring pipeline for dynamic rendering\n"; - pipelineRenderingCreateInfo.colorAttachmentCount = 1; - pipelineRenderingCreateInfo.pColorAttachmentFormats = &swapChainSurfaceFormat.format; - pipelineRenderingCreateInfo.depthAttachmentFormat = findDepthFormat(); - - pipelineInfo.pNext = &pipelineRenderingCreateInfo; - pipelineInfo.renderPass = nullptr; - } else { - std::cout << "Configuring pipeline for traditional render pass\n"; - pipelineInfo.pNext = nullptr; - pipelineInfo.renderPass = *renderPass; - pipelineInfo.subpass = 0; - } - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{ - .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex - }; - commandPool = vk::raii::CommandPool(device, poolInfo); - } - - void createColorResources() { - vk::Format colorFormat = swapChainSurfaceFormat.format; - - createImage(swapChainExtent.width, swapChainExtent.height, 1, msaaSamples, colorFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransientAttachment | vk::ImageUsageFlagBits::eColorAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, colorImage, colorImageMemory); - colorImageView = createImageView(colorImage, colorFormat, vk::ImageAspectFlagBits::eColor, 1); - } - - void createDepthResources() { - vk::Format depthFormat = findDepthFormat(); - - createImage(swapChainExtent.width, swapChainExtent.height, 1, msaaSamples, depthFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eDepthStencilAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, depthImage, depthImageMemory); - depthImageView = createImageView(depthImage, depthFormat, vk::ImageAspectFlagBits::eDepth, 1); - } - - vk::Format findSupportedFormat(const std::vector& candidates, vk::ImageTiling tiling, vk::FormatFeatureFlags features) const { - for (const auto format : candidates) { - vk::FormatProperties props = physicalDevice.getFormatProperties(format); - - if (tiling == vk::ImageTiling::eLinear && (props.linearTilingFeatures & features) == features) { - return format; - } - if (tiling == vk::ImageTiling::eOptimal && (props.optimalTilingFeatures & features) == features) { - return format; - } - } - - throw std::runtime_error("failed to find supported format!"); - } - - [[nodiscard]] vk::Format findDepthFormat() const { - return findSupportedFormat( - {vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, vk::Format::eD24UnormS8Uint}, - vk::ImageTiling::eOptimal, - vk::FormatFeatureFlagBits::eDepthStencilAttachment - ); - } - - static bool hasStencilComponent(vk::Format format) { - return format == vk::Format::eD32SfloatS8Uint || format == vk::Format::eD24UnormS8Uint; - } - - void createTextureImage() { - int texWidth, texHeight, texChannels; - stbi_uc* pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); - vk::DeviceSize imageSize = texWidth * texHeight * 4; - mipLevels = static_cast(std::floor(std::log2(std::max(texWidth, texHeight)))) + 1; - - if (!pixels) { - throw std::runtime_error("failed to load texture image!"); - } - - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, imageSize); - memcpy(data, pixels, imageSize); - stagingBufferMemory.unmapMemory(); - - stbi_image_free(pixels); - - createImage(texWidth, texHeight, mipLevels, vk::SampleCountFlagBits::e1, vk::Format::eR8G8B8A8Srgb, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory); - - transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, mipLevels); - copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); - - generateMipmaps(textureImage, vk::Format::eR8G8B8A8Srgb, texWidth, texHeight, mipLevels); - } - - void generateMipmaps(vk::raii::Image& image, vk::Format imageFormat, int32_t texWidth, int32_t texHeight, uint32_t mipLevels) { - // Check if image format supports linear blit-ing - vk::FormatProperties formatProperties = physicalDevice.getFormatProperties(imageFormat); - - if (!(formatProperties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eSampledImageFilterLinear)) { - throw std::runtime_error("texture image format does not support linear blitting!"); - } - - std::unique_ptr commandBuffer = beginSingleTimeCommands(); - - vk::ImageMemoryBarrier barrier = { .srcAccessMask = vk::AccessFlagBits::eTransferWrite, .dstAccessMask =vk::AccessFlagBits::eTransferRead - , .oldLayout = vk::ImageLayout::eTransferDstOptimal, .newLayout = vk::ImageLayout::eTransferSrcOptimal - , .srcQueueFamilyIndex = vk::QueueFamilyIgnored, .dstQueueFamilyIndex = vk::QueueFamilyIgnored, .image = image }; - barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; - barrier.subresourceRange.baseArrayLayer = 0; - barrier.subresourceRange.layerCount = 1; - barrier.subresourceRange.levelCount = 1; - - int32_t mipWidth = texWidth; - int32_t mipHeight = texHeight; - - for (uint32_t i = 1; i < mipLevels; i++) { - barrier.subresourceRange.baseMipLevel = i - 1; - barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal; - barrier.newLayout = vk::ImageLayout::eTransferSrcOptimal; - barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; - barrier.dstAccessMask = vk::AccessFlagBits::eTransferRead; - - commandBuffer->pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer, {}, {}, {}, barrier); - - vk::ArrayWrapper1D offsets, dstOffsets; - offsets[0] = vk::Offset3D(0, 0, 0); - offsets[1] = vk::Offset3D(mipWidth, mipHeight, 1); - dstOffsets[0] = vk::Offset3D(0, 0, 0); - dstOffsets[1] = vk::Offset3D(mipWidth > 1 ? mipWidth / 2 : 1, mipHeight > 1 ? mipHeight / 2 : 1, 1); - vk::ImageBlit blit = { .srcSubresource = {}, .srcOffsets = offsets, - .dstSubresource = {}, .dstOffsets = dstOffsets }; - blit.srcSubresource = vk::ImageSubresourceLayers( vk::ImageAspectFlagBits::eColor, i - 1, 0, 1); - blit.dstSubresource = vk::ImageSubresourceLayers( vk::ImageAspectFlagBits::eColor, i, 0, 1); - - commandBuffer->blitImage(image, vk::ImageLayout::eTransferSrcOptimal, image, vk::ImageLayout::eTransferDstOptimal, { blit }, vk::Filter::eLinear); - - barrier.oldLayout = vk::ImageLayout::eTransferSrcOptimal; - barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal; - barrier.srcAccessMask = vk::AccessFlagBits::eTransferRead; - barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - - commandBuffer->pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {}, barrier); - - if (mipWidth > 1) mipWidth /= 2; - if (mipHeight > 1) mipHeight /= 2; - } - - barrier.subresourceRange.baseMipLevel = mipLevels - 1; - barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal; - barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal; - barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; - barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - - commandBuffer->pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {}, barrier); - - endSingleTimeCommands(*commandBuffer); - } - - vk::SampleCountFlagBits getMaxUsableSampleCount() { - vk::PhysicalDeviceProperties physicalDeviceProperties = physicalDevice.getProperties(); - - vk::SampleCountFlags counts = physicalDeviceProperties.limits.framebufferColorSampleCounts & physicalDeviceProperties.limits.framebufferDepthSampleCounts; - if (counts & vk::SampleCountFlagBits::e64) { return vk::SampleCountFlagBits::e64; } - if (counts & vk::SampleCountFlagBits::e32) { return vk::SampleCountFlagBits::e32; } - if (counts & vk::SampleCountFlagBits::e16) { return vk::SampleCountFlagBits::e16; } - if (counts & vk::SampleCountFlagBits::e8) { return vk::SampleCountFlagBits::e8; } - if (counts & vk::SampleCountFlagBits::e4) { return vk::SampleCountFlagBits::e4; } - if (counts & vk::SampleCountFlagBits::e2) { return vk::SampleCountFlagBits::e2; } - - return vk::SampleCountFlagBits::e1; - } - - void createTextureImageView() { - textureImageView = createImageView(textureImage, vk::Format::eR8G8B8A8Srgb, vk::ImageAspectFlagBits::eColor, mipLevels); - } - - void createTextureSampler() { - vk::PhysicalDeviceProperties properties = physicalDevice.getProperties(); - vk::SamplerCreateInfo samplerInfo { - .magFilter = vk::Filter::eLinear, - .minFilter = vk::Filter::eLinear, - .mipmapMode = vk::SamplerMipmapMode::eLinear, - .addressModeU = vk::SamplerAddressMode::eRepeat, - .addressModeV = vk::SamplerAddressMode::eRepeat, - .addressModeW = vk::SamplerAddressMode::eRepeat, - .mipLodBias = 0.0f, - .anisotropyEnable = vk::True, - .maxAnisotropy = properties.limits.maxSamplerAnisotropy, - .compareEnable = vk::False, - .compareOp = vk::CompareOp::eAlways - }; - textureSampler = vk::raii::Sampler(device, samplerInfo); - } - - [[nodiscard]] vk::raii::ImageView createImageView(const vk::raii::Image& image, vk::Format format, vk::ImageAspectFlags aspectFlags, uint32_t mipLevels) const { - vk::ImageViewCreateInfo viewInfo{ - .image = image, - .viewType = vk::ImageViewType::e2D, - .format = format, - .subresourceRange = { aspectFlags, 0, mipLevels, 0, 1 } - }; - return vk::raii::ImageView( device, viewInfo ); - } - - void createImage(uint32_t width, uint32_t height, uint32_t mipLevels, vk::SampleCountFlagBits numSamples, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image& image, vk::raii::DeviceMemory& imageMemory) { - vk::ImageCreateInfo imageInfo{ - .imageType = vk::ImageType::e2D, - .format = format, - .extent = {width, height, 1}, - .mipLevels = mipLevels, - .arrayLayers = 1, - .samples = numSamples, - .tiling = tiling, - .usage = usage, - .sharingMode = vk::SharingMode::eExclusive, - .initialLayout = vk::ImageLayout::eUndefined - }; - image = vk::raii::Image(device, imageInfo); - - vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ - .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) - }; - imageMemory = vk::raii::DeviceMemory(device, allocInfo); - image.bindMemory(imageMemory, 0); - } - - void transitionImageLayout(const vk::raii::Image& image, const vk::ImageLayout oldLayout, const vk::ImageLayout newLayout, uint32_t mipLevels) { - const auto commandBuffer = beginSingleTimeCommands(); - - if (appInfo.synchronization2Supported) { - // Use Synchronization2 API - vk::ImageMemoryBarrier2 barrier{ - .srcStageMask = vk::PipelineStageFlagBits2::eAllCommands, - .dstStageMask = vk::PipelineStageFlagBits2::eAllCommands, - .oldLayout = oldLayout, - .newLayout = newLayout, - .image = image, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, mipLevels, 0, 1 } - }; - - if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) { - barrier.srcAccessMask = vk::AccessFlagBits2::eNone; - barrier.dstAccessMask = vk::AccessFlagBits2::eTransferWrite; - barrier.srcStageMask = vk::PipelineStageFlagBits2::eTopOfPipe; - barrier.dstStageMask = vk::PipelineStageFlagBits2::eTransfer; - } else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) { - barrier.srcAccessMask = vk::AccessFlagBits2::eTransferWrite; - barrier.dstAccessMask = vk::AccessFlagBits2::eShaderRead; - barrier.srcStageMask = vk::PipelineStageFlagBits2::eTransfer; - barrier.dstStageMask = vk::PipelineStageFlagBits2::eFragmentShader; - } else { - throw std::invalid_argument("unsupported layout transition!"); - } - - vk::DependencyInfo dependencyInfo{ - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - - commandBuffer->pipelineBarrier2(dependencyInfo); - } else { - // Use traditional synchronization API - vk::ImageMemoryBarrier barrier{ - .oldLayout = oldLayout, - .newLayout = newLayout, - .image = image, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, mipLevels, 0, 1 } - }; - - vk::PipelineStageFlags sourceStage; - vk::PipelineStageFlags destinationStage; - - if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) { - barrier.srcAccessMask = {}; - barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; - - sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; - destinationStage = vk::PipelineStageFlagBits::eTransfer; - } else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) { - barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; - barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - - sourceStage = vk::PipelineStageFlagBits::eTransfer; - destinationStage = vk::PipelineStageFlagBits::eFragmentShader; - } else { - throw std::invalid_argument("unsupported layout transition!"); - } - commandBuffer->pipelineBarrier(sourceStage, destinationStage, {}, {}, nullptr, barrier); - } - - endSingleTimeCommands(*commandBuffer); - } - - void copyBufferToImage(const vk::raii::Buffer& buffer, const vk::raii::Image& image, uint32_t width, uint32_t height) { - std::unique_ptr commandBuffer = beginSingleTimeCommands(); - vk::BufferImageCopy region{ - .bufferOffset = 0, - .bufferRowLength = 0, - .bufferImageHeight = 0, - .imageSubresource = { vk::ImageAspectFlagBits::eColor, 0, 0, 1 }, - .imageOffset = {0, 0, 0}, - .imageExtent = {width, height, 1} - }; - commandBuffer->copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, {region}); - endSingleTimeCommands(*commandBuffer); - } - - void loadModel() { - tinyobj::attrib_t attrib; - std::vector shapes; - std::vector materials; - std::string warn, err; - - if (!LoadObj(&attrib, &shapes, &materials, &warn, &err, MODEL_PATH.c_str())) { - throw std::runtime_error(warn + err); - } - - std::unordered_map uniqueVertices{}; - - for (const auto& shape : shapes) { - for (const auto& index : shape.mesh.indices) { - Vertex vertex{}; - - vertex.pos = { - attrib.vertices[3 * index.vertex_index + 0], - attrib.vertices[3 * index.vertex_index + 1], - attrib.vertices[3 * index.vertex_index + 2] - }; - - vertex.texCoord = { - attrib.texcoords[2 * index.texcoord_index + 0], - 1.0f - attrib.texcoords[2 * index.texcoord_index + 1] - }; - - vertex.color = {1.0f, 1.0f, 1.0f}; - - if (!uniqueVertices.contains(vertex)) { - uniqueVertices[vertex] = static_cast(vertices.size()); - vertices.push_back(vertex); - } - - indices.push_back(uniqueVertices[vertex]); - } - } - } - - void createVertexBuffer() { - vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(dataStaging, vertices.data(), bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); - - copyBuffer(stagingBuffer, vertexBuffer, bufferSize); - } - - void createIndexBuffer() { - vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); - - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(data, indices.data(), bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); - - copyBuffer(stagingBuffer, indexBuffer, bufferSize); - } - - void createUniformBuffers() { - uniformBuffers.clear(); - uniformBuffersMemory.clear(); - uniformBuffersMapped.clear(); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DeviceSize bufferSize = sizeof(UniformBufferObject); - vk::raii::Buffer buffer({}); - vk::raii::DeviceMemory bufferMem({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); - uniformBuffers.emplace_back(std::move(buffer)); - uniformBuffersMemory.emplace_back(std::move(bufferMem)); - uniformBuffersMapped.emplace_back(uniformBuffersMemory[i].mapMemory(0, bufferSize)); - } - } - - void createDescriptorPool() { - std::array poolSize { - vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT), - vk::DescriptorPoolSize(vk::DescriptorType::eCombinedImageSampler, MAX_FRAMES_IN_FLIGHT) - }; - vk::DescriptorPoolCreateInfo poolInfo{ - .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, - .maxSets = MAX_FRAMES_IN_FLIGHT, - .poolSizeCount = static_cast(poolSize.size()), - .pPoolSizes = poolSize.data() - }; - descriptorPool = vk::raii::DescriptorPool(device, poolInfo); - } - - void createDescriptorSets() { - std::vector layouts(MAX_FRAMES_IN_FLIGHT, descriptorSetLayout); - vk::DescriptorSetAllocateInfo allocInfo{ - .descriptorPool = descriptorPool, - .descriptorSetCount = static_cast(layouts.size()), - .pSetLayouts = layouts.data() - }; - - descriptorSets.clear(); - descriptorSets = device.allocateDescriptorSets(allocInfo); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DescriptorBufferInfo bufferInfo{ - .buffer = uniformBuffers[i], - .offset = 0, - .range = sizeof(UniformBufferObject) - }; - vk::DescriptorImageInfo imageInfo{ - .sampler = textureSampler, - .imageView = textureImageView, - .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal - }; - std::array descriptorWrites{ - vk::WriteDescriptorSet{ - .dstSet = descriptorSets[i], - .dstBinding = 0, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eUniformBuffer, - .pBufferInfo = &bufferInfo - }, - vk::WriteDescriptorSet{ - .dstSet = descriptorSets[i], - .dstBinding = 1, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eCombinedImageSampler, - .pImageInfo = &imageInfo - } - }; - device.updateDescriptorSets(descriptorWrites, {}); - } - } - - void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer& buffer, vk::raii::DeviceMemory& bufferMemory) { - vk::BufferCreateInfo bufferInfo{ - .size = size, - .usage = usage, - .sharingMode = vk::SharingMode::eExclusive - }; - buffer = vk::raii::Buffer(device, bufferInfo); - vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ - .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) - }; - bufferMemory = vk::raii::DeviceMemory(device, allocInfo); - buffer.bindMemory(bufferMemory, 0); - } - - std::unique_ptr beginSingleTimeCommands() { - vk::CommandBufferAllocateInfo allocInfo{ - .commandPool = commandPool, - .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = 1 - }; - std::unique_ptr commandBuffer = std::make_unique(std::move(vk::raii::CommandBuffers(device, allocInfo).front())); - - vk::CommandBufferBeginInfo beginInfo{ - .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit - }; - commandBuffer->begin(beginInfo); - - return commandBuffer; - } - - void endSingleTimeCommands(const vk::raii::CommandBuffer& commandBuffer) const { - commandBuffer.end(); - - vk::SubmitInfo submitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandBuffer }; - queue.submit(submitInfo, nullptr); - queue.waitIdle(); - } - - void copyBuffer(vk::raii::Buffer & srcBuffer, vk::raii::Buffer & dstBuffer, vk::DeviceSize size) { - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1 }; - vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); - commandCopyBuffer.begin(vk::CommandBufferBeginInfo{ .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit }); - commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy{ .size = size }); - commandCopyBuffer.end(); - queue.submit(vk::SubmitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer }, nullptr); - queue.waitIdle(); - } - - uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) { - vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); - - for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { - if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { - return i; - } - } - - throw std::runtime_error("failed to find suitable memory type!"); - } - - void createCommandBuffers() { - commandBuffers.clear(); - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = MAX_FRAMES_IN_FLIGHT }; - commandBuffers = vk::raii::CommandBuffers(device, allocInfo); - } - - void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin({}); - - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::ClearValue clearDepth = vk::ClearDepthStencilValue(1.0f, 0); - std::array clearValues = { clearColor, clearDepth }; - - if (appInfo.dynamicRenderingSupported) { - // Transition attachments to the correct layout - if (appInfo.synchronization2Supported) { - // Use Synchronization2 API for image transitions - vk::ImageMemoryBarrier2 colorBarrier{ - .srcStageMask = vk::PipelineStageFlagBits2::eTopOfPipe, - .srcAccessMask = vk::AccessFlagBits2::eNone, - .dstStageMask = vk::PipelineStageFlagBits2::eColorAttachmentOutput, - .dstAccessMask = vk::AccessFlagBits2::eColorAttachmentWrite, - .oldLayout = vk::ImageLayout::eUndefined, - .newLayout = vk::ImageLayout::eColorAttachmentOptimal, - .image = *colorImage, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } - }; - - vk::ImageMemoryBarrier2 depthBarrier{ - .srcStageMask = vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, - .srcAccessMask = vk::AccessFlagBits2::eNone, - .dstStageMask = vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, - .dstAccessMask = vk::AccessFlagBits2::eDepthStencilAttachmentWrite, - .oldLayout = vk::ImageLayout::eUndefined, - .newLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal, - .image = *depthImage, - .subresourceRange = { vk::ImageAspectFlagBits::eDepth, 0, 1, 0, 1 } - }; - - vk::ImageMemoryBarrier2 swapchainBarrier{ - .srcStageMask = vk::PipelineStageFlagBits2::eTopOfPipe, - .srcAccessMask = vk::AccessFlagBits2::eNone, - .dstStageMask = vk::PipelineStageFlagBits2::eColorAttachmentOutput, - .dstAccessMask = vk::AccessFlagBits2::eColorAttachmentWrite, - .oldLayout = vk::ImageLayout::eUndefined, - .newLayout = vk::ImageLayout::eColorAttachmentOptimal, - .image = swapChainImages[imageIndex], - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } - }; - - std::array barriers = { colorBarrier, depthBarrier, swapchainBarrier }; - vk::DependencyInfo dependencyInfo{ - .imageMemoryBarrierCount = static_cast(barriers.size()), - .pImageMemoryBarriers = barriers.data() - }; - - commandBuffers[currentFrame].pipelineBarrier2(dependencyInfo); - } else { - // Use traditional synchronization API - vk::ImageMemoryBarrier colorBarrier{ - .srcAccessMask = vk::AccessFlagBits::eNone, - .dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite, - .oldLayout = vk::ImageLayout::eUndefined, - .newLayout = vk::ImageLayout::eColorAttachmentOptimal, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = *colorImage, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } - }; - - vk::ImageMemoryBarrier depthBarrier{ - .srcAccessMask = vk::AccessFlagBits::eNone, - .dstAccessMask = vk::AccessFlagBits::eDepthStencilAttachmentWrite, - .oldLayout = vk::ImageLayout::eUndefined, - .newLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = *depthImage, - .subresourceRange = { vk::ImageAspectFlagBits::eDepth, 0, 1, 0, 1 } - }; - - vk::ImageMemoryBarrier swapchainBarrier{ - .srcAccessMask = vk::AccessFlagBits::eNone, - .dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite, - .oldLayout = vk::ImageLayout::eUndefined, - .newLayout = vk::ImageLayout::eColorAttachmentOptimal, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } - }; - - std::array barriers = { colorBarrier, depthBarrier, swapchainBarrier }; - commandBuffers[currentFrame].pipelineBarrier( - vk::PipelineStageFlagBits::eTopOfPipe, - vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eEarlyFragmentTests, - vk::DependencyFlagBits::eByRegion, - {}, - {}, - barriers - ); - } - - // Setup rendering attachments - vk::RenderingAttachmentInfo colorAttachment{ - .imageView = *colorImageView, - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .resolveMode = vk::ResolveModeFlagBits::eAverage, - .resolveImageView = *swapChainImageViews[imageIndex], - .resolveImageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor - }; - - vk::RenderingAttachmentInfo depthAttachment{ - .imageView = *depthImageView, - .imageLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eDontCare, - .clearValue = clearDepth - }; - - vk::RenderingInfo renderingInfo{ - .renderArea = {{0, 0}, swapChainExtent}, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &colorAttachment, - .pDepthAttachment = &depthAttachment - }; - - commandBuffers[currentFrame].beginRendering(renderingInfo); - } else { - // Use traditional render pass - std::cout << "Recording command buffer with traditional render pass\n"; - - vk::RenderPassBeginInfo renderPassInfo{ - .renderPass = *renderPass, - .framebuffer = *swapChainFramebuffers[imageIndex], - .renderArea = {{0, 0}, swapChainExtent}, - .clearValueCount = static_cast(clearValues.size()), - .pClearValues = clearValues.data() - }; - - commandBuffers[currentFrame].beginRenderPass(renderPassInfo, vk::SubpassContents::eInline); - } - - // Common rendering commands - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint32); - commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); - commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); - - if (appInfo.dynamicRenderingSupported) { - commandBuffers[currentFrame].endRendering(); - - // Transition swapchain image to present layout - if (appInfo.synchronization2Supported) { - vk::ImageMemoryBarrier2 barrier{ - .srcStageMask = vk::PipelineStageFlagBits2::eColorAttachmentOutput, - .srcAccessMask = vk::AccessFlagBits2::eColorAttachmentWrite, - .dstStageMask = vk::PipelineStageFlagBits2::eBottomOfPipe, - .dstAccessMask = vk::AccessFlagBits2::eNone, - .oldLayout = vk::ImageLayout::eColorAttachmentOptimal, - .newLayout = vk::ImageLayout::ePresentSrcKHR, - .image = swapChainImages[imageIndex], - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } - }; - - vk::DependencyInfo dependencyInfo{ - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - - commandBuffers[currentFrame].pipelineBarrier2(dependencyInfo); - } else { - vk::ImageMemoryBarrier barrier{ - .srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite, - .dstAccessMask = vk::AccessFlagBits::eNone, - .oldLayout = vk::ImageLayout::eColorAttachmentOptimal, - .newLayout = vk::ImageLayout::ePresentSrcKHR, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } - }; - - commandBuffers[currentFrame].pipelineBarrier( - vk::PipelineStageFlagBits::eColorAttachmentOutput, - vk::PipelineStageFlagBits::eBottomOfPipe, - vk::DependencyFlagBits::eByRegion, - {}, - {}, - { barrier } - ); - } - } else { - commandBuffers[currentFrame].endRenderPass(); - } - - commandBuffers[currentFrame].end(); - } - - void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); - - if (appInfo.timelineSemaphoresSupported) { - // Create timeline semaphore - std::cout << "Creating timeline semaphores\n"; - vk::SemaphoreTypeCreateInfo timelineCreateInfo{ - .semaphoreType = vk::SemaphoreType::eTimeline, - .initialValue = 0 - }; - - vk::SemaphoreCreateInfo semaphoreInfo{ - .pNext = &timelineCreateInfo - }; - - timelineSemaphore = vk::raii::Semaphore(device, semaphoreInfo); - - // Still need binary semaphores for swapchain operations - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - } - } else { - // Create binary semaphores and fences - std::cout << "Creating binary semaphores and fences\n"; - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - } - } - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - inFlightFences.emplace_back(device, vk::FenceCreateInfo{ .flags = vk::FenceCreateFlagBits::eSignaled }); - } - } - - void updateUniformBuffer(uint32_t currentImage) const { - static auto startTime = std::chrono::high_resolution_clock::now(); - - auto currentTime = std::chrono::high_resolution_clock::now(); - float time = std::chrono::duration(currentTime - startTime).count(); - - UniformBufferObject ubo{}; - ubo.model = rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.view = lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); - ubo.proj[1][1] *= -1; - - memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); - } - - void drawFrame() { - while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, FenceTimeout)) - ; - auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[currentFrame], nullptr); - - if (result == vk::Result::eErrorOutOfDateKHR) { - recreateSwapChain(); - return; - } - if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) { - throw std::runtime_error("failed to acquire swap chain image!"); - } - updateUniformBuffer(currentFrame); - - device.resetFences(*inFlightFences[currentFrame]); - commandBuffers[currentFrame].reset(); - recordCommandBuffer(imageIndex); - - if (appInfo.timelineSemaphoresSupported) { - // Use timeline semaphores for GPU synchronization - uint64_t waitValue = timelineValue; - uint64_t signalValue = ++timelineValue; - - vk::TimelineSemaphoreSubmitInfo timelineInfo{ - .waitSemaphoreValueCount = 0, // We'll still use binary semaphore for swapchain - .signalSemaphoreValueCount = 1, - .pSignalSemaphoreValues = &signalValue - }; - - std::array waitSemaphores = { *presentCompleteSemaphore[currentFrame], *timelineSemaphore }; - std::array waitStages = { vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::PipelineStageFlagBits::eVertexInput }; - std::array waitValues = { 0, waitValue }; // Binary semaphore value is ignored - - std::array signalSemaphores = { *renderFinishedSemaphore[currentFrame], *timelineSemaphore }; - std::array signalValues = { 0, signalValue }; // Binary semaphore value is ignored - - timelineInfo.waitSemaphoreValueCount = 1; // Only for the timeline semaphore - timelineInfo.pWaitSemaphoreValues = &waitValues[1]; - timelineInfo.signalSemaphoreValueCount = 1; // Only for the timeline semaphore - timelineInfo.pSignalSemaphoreValues = &signalValues[1]; - - vk::SubmitInfo submitInfo{ - .pNext = &timelineInfo, - .waitSemaphoreCount = 1, // Only wait on the binary semaphore - .pWaitSemaphores = &waitSemaphores[0], - .pWaitDstStageMask = &waitStages[0], - .commandBufferCount = 1, - .pCommandBuffers = &*commandBuffers[currentFrame], - .signalSemaphoreCount = 2, // Signal both semaphores - .pSignalSemaphores = signalSemaphores.data() - }; - - queue.submit(submitInfo, *inFlightFences[currentFrame]); - } else { - // Use traditional binary semaphores - vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); - const vk::SubmitInfo submitInfo{ - .waitSemaphoreCount = 1, - .pWaitSemaphores = &*presentCompleteSemaphore[currentFrame], - .pWaitDstStageMask = &waitDestinationStageMask, - .commandBufferCount = 1, - .pCommandBuffers = &*commandBuffers[currentFrame], - .signalSemaphoreCount = 1, - .pSignalSemaphores = &*renderFinishedSemaphore[currentFrame] - }; - queue.submit(submitInfo, *inFlightFences[currentFrame]); - } - - const vk::PresentInfoKHR presentInfoKHR{ - .waitSemaphoreCount = 1, - .pWaitSemaphores = &*renderFinishedSemaphore[currentFrame], - .swapchainCount = 1, - .pSwapchains = &*swapChain, - .pImageIndices = &imageIndex - }; - result = queue.presentKHR(presentInfoKHR); - if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { - framebufferResized = false; - recreateSwapChain(); - } else if (result != vk::Result::eSuccess) { - throw std::runtime_error("failed to present swap chain image!"); - } - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; - } - - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size(), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const & surfaceCapabilities) { - auto minImageCount = std::max( 3u, surfaceCapabilities.minImageCount ); - if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) { - minImageCount = surfaceCapabilities.maxImageCount; - } - return minImageCount; - } - - static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { - assert(!availableFormats.empty()); - const auto formatIt = std::ranges::find_if( - availableFormats, - []( const auto & format ) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; } ); - return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - assert(std::ranges::any_of(availablePresentModes, [](auto presentMode){ return presentMode == vk::PresentModeKHR::eFifo; })); - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eFifo; - } - - [[nodiscard]] vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) const { - if (capabilities.currentExtent.width != 0xFFFFFFFF) { - 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) - }; - } - - [[nodiscard]] std::vector getRequiredExtensions() const { - // Get the required extensions from GLFW - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - - // Check if the debug utils extension is available - std::vector props = context.enumerateInstanceExtensionProperties(); - bool debugUtilsAvailable = std::ranges::any_of(props, - [](vk::ExtensionProperties const & ep) { - return strcmp(ep.extensionName, vk::EXTDebugUtilsExtensionName) == 0; +class HelloTriangleApplication +{ + public: + void run() + { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + + private: + AppInfo appInfo; + + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::SampleCountFlagBits msaaSamples = vk::SampleCountFlagBits::e1; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::SurfaceFormatKHR swapChainSurfaceFormat; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + // Traditional render pass (fallback for non-dynamic rendering) + vk::raii::RenderPass renderPass = nullptr; + std::vector swapChainFramebuffers; + + vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + + vk::raii::Image colorImage = nullptr; + vk::raii::DeviceMemory colorImageMemory = nullptr; + vk::raii::ImageView colorImageView = nullptr; + + vk::raii::Image depthImage = nullptr; + vk::raii::DeviceMemory depthImageMemory = nullptr; + vk::raii::ImageView depthImageView = nullptr; + + uint32_t mipLevels = 0; + vk::raii::Image textureImage = nullptr; + vk::raii::DeviceMemory textureImageMemory = nullptr; + vk::raii::ImageView textureImageView = nullptr; + vk::raii::Sampler textureSampler = nullptr; + + std::vector vertices; + std::vector indices; + vk::raii::Buffer vertexBuffer = nullptr; + vk::raii::DeviceMemory vertexBufferMemory = nullptr; + vk::raii::Buffer indexBuffer = nullptr; + vk::raii::DeviceMemory indexBufferMemory = nullptr; + + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + std::vector uniformBuffersMapped; + + vk::raii::DescriptorPool descriptorPool = nullptr; + std::vector descriptorSets; + + vk::raii::CommandPool commandPool = nullptr; + std::vector commandBuffers; + + // Synchronization objects + std::vector presentCompleteSemaphore; + std::vector renderFinishedSemaphore; + std::vector inFlightFences; + vk::raii::Semaphore timelineSemaphore = nullptr; + uint64_t timelineValue = 0; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName}; + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan Compatibility Example", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow *window, int width, int height) + { + auto app = static_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + detectFeatureSupport(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + + // Create traditional render pass if dynamic rendering is not supported + if (!appInfo.dynamicRenderingSupported) + { + createRenderPass(); + createFramebuffers(); + } + + createDescriptorSetLayout(); + createGraphicsPipeline(); + createCommandPool(); + createColorResources(); + createDepthResources(); + createTextureImage(); + createTextureImageView(); + createTextureSampler(); + loadModel(); + createVertexBuffer(); + createIndexBuffer(); + createUniformBuffers(); + createDescriptorPool(); + createDescriptorSets(); + createCommandBuffers(); + createSyncObjects(); + + // Print feature support summary + std::cout << "\nFeature support summary:\n"; + std::cout << "- Dynamic Rendering: " << (appInfo.dynamicRenderingSupported ? "Yes" : "No") << "\n"; + std::cout << "- Timeline Semaphores: " << (appInfo.timelineSemaphoresSupported ? "Yes" : "No") << "\n"; + std::cout << "- Synchronization2: " << (appInfo.synchronization2Supported ? "Yes" : "No") << "\n"; + } + + void mainLoop() + { + while (!glfwWindowShouldClose(window)) + { + glfwPollEvents(); + drawFrame(); + } + + device.waitIdle(); + } + + void cleanupSwapChain() + { + swapChainFramebuffers.clear(); + swapChainImageViews.clear(); + swapChain = nullptr; + } + + void cleanup() const + { + glfwDestroyWindow(window); + glfwTerminate(); + } + + void recreateSwapChain() + { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) + { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + device.waitIdle(); + + cleanupSwapChain(); + createSwapChain(); + createImageViews(); + + // Recreate traditional render pass and framebuffers if dynamic rendering is not supported + if (!appInfo.dynamicRenderingSupported) + { + createRenderPass(); + createFramebuffers(); + } + + createColorResources(); + createDepthResources(); + } + + void createInstance() + { + // Validation layers are now managed by vulkanconfig instead of being hard-coded + + 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}; + + auto extensions = getRequiredExtensions(); + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledExtensionCount = static_cast(extensions.size()), + .ppEnabledExtensionNames = extensions.data()}; + + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + // Always set up the debug messenger + // It will only be used if validation layers are enabled via vulkanconfig + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( + vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | + vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | + vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( + vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | + vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | + vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + + try + { + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + catch (vk::SystemError &err) + { + // If the debug utils extension is not available, this will fail + // That's okay; it just means validation layers aren't enabled + std::cout << "Debug messenger not available. Validation layers may not be enabled." << std::endl; + } + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + return supportsGraphics && supportsAllRequiredExtensions; }); - - // Always include the debug utils extension if available - // This allows validation layers to be enabled via vulkanconfig - if (debugUtilsAvailable) { - extensions.push_back(vk::EXTDebugUtilsExtensionName); - } else { - std::cout << "VK_EXT_debug_utils extension not available. Validation layers may not work." << std::endl; - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - 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; - } + if (devIter != devices.end()) + { + physicalDevice = *devIter; + msaaSamples = getMaxUsableSampleCount(); + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void detectFeatureSupport() + { + // Get device properties to check Vulkan version + vk::PhysicalDeviceProperties deviceProperties = physicalDevice.getProperties(); + + // Get available extensions + std::vector availableExtensions = physicalDevice.enumerateDeviceExtensionProperties(); + + // Check for dynamic rendering support + if (deviceProperties.apiVersion >= VK_VERSION_1_3) + { + appInfo.dynamicRenderingSupported = true; + std::cout << "Dynamic rendering supported via Vulkan 1.3\n"; + } + else + { + // Check for the extension on older Vulkan versions + for (const auto &extension : availableExtensions) + { + if (strcmp(extension.extensionName, VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME) == 0) + { + appInfo.dynamicRenderingSupported = true; + std::cout << "Dynamic rendering supported via extension\n"; + break; + } + } + } + + // Check for timeline semaphores support + if (deviceProperties.apiVersion >= VK_VERSION_1_2) + { + appInfo.timelineSemaphoresSupported = true; + std::cout << "Timeline semaphores supported via Vulkan 1.2\n"; + } + else + { + // Check for the extension on older Vulkan versions + for (const auto &extension : availableExtensions) + { + if (strcmp(extension.extensionName, VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME) == 0) + { + appInfo.timelineSemaphoresSupported = true; + std::cout << "Timeline semaphores supported via extension\n"; + break; + } + } + } + + // Check for synchronization2 support + if (deviceProperties.apiVersion >= VK_VERSION_1_3) + { + appInfo.synchronization2Supported = true; + std::cout << "Synchronization2 supported via Vulkan 1.3\n"; + } + else + { + // Check for the extension on older Vulkan versions + for (const auto &extension : availableExtensions) + { + if (strcmp(extension.extensionName, VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME) == 0) + { + appInfo.synchronization2Supported = true; + std::cout << "Synchronization2 supported via extension\n"; + break; + } + } + } + + // Add required extensions based on feature support + if (appInfo.dynamicRenderingSupported && deviceProperties.apiVersion < VK_VERSION_1_3) + { + requiredDeviceExtension.push_back(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME); + } + + if (appInfo.timelineSemaphoresSupported && deviceProperties.apiVersion < VK_VERSION_1_2) + { + requiredDeviceExtension.push_back(VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME); + } + + if (appInfo.synchronization2Supported && deviceProperties.apiVersion < VK_VERSION_1_3) + { + requiredDeviceExtension.push_back(VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // Create device with appropriate features + auto features = physicalDevice.getFeatures2(); + + // Setup feature chain based on detected support + void *pNext = nullptr; + + // Add dynamic rendering if supported + vk::PhysicalDeviceVulkan13Features vulkan13Features; + vk::PhysicalDeviceDynamicRenderingFeatures dynamicRenderingFeatures; + + if (appInfo.dynamicRenderingSupported) + { + if (appInfo.synchronization2Supported) + { + vulkan13Features.dynamicRendering = vk::True; + vulkan13Features.synchronization2 = vk::True; + vulkan13Features.pNext = pNext; + pNext = &vulkan13Features; + } + else + { + dynamicRenderingFeatures.dynamicRendering = vk::True; + dynamicRenderingFeatures.pNext = pNext; + pNext = &dynamicRenderingFeatures; + } + } + + // Add timeline semaphores if supported + vk::PhysicalDeviceTimelineSemaphoreFeatures timelineSemaphoreFeatures; + if (appInfo.timelineSemaphoresSupported) + { + timelineSemaphoreFeatures.timelineSemaphore = vk::True; + timelineSemaphoreFeatures.pNext = pNext; + pNext = &timelineSemaphoreFeatures; + } + + features.pNext = pNext; + + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{ + .pNext = &features, + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(*surface); + swapChainExtent = chooseSwapExtent(surfaceCapabilities); + swapChainSurfaceFormat = chooseSwapSurfaceFormat(physicalDevice.getSurfaceFormatsKHR(*surface)); + vk::SwapchainCreateInfoKHR swapChainCreateInfo{.surface = *surface, + .minImageCount = chooseSwapMinImageCount(surfaceCapabilities), + .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + void createImageViews() + { + vk::ImageViewCreateInfo imageViewCreateInfo{ + .viewType = vk::ImageViewType::e2D, + .format = swapChainSurfaceFormat.format, + .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + for (auto image : swapChainImages) + { + imageViewCreateInfo.image = image; + swapChainImageViews.emplace_back(device, imageViewCreateInfo); + } + } + + void createRenderPass() + { + if (appInfo.dynamicRenderingSupported) + { + // No render pass needed with dynamic rendering + std::cout << "Using dynamic rendering, skipping render pass creation\n"; + return; + } + + std::cout << "Creating traditional render pass\n"; + + // Color attachment description + vk::AttachmentDescription colorAttachment{ + .format = swapChainSurfaceFormat.format, + .samples = msaaSamples, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .stencilLoadOp = vk::AttachmentLoadOp::eDontCare, + .stencilStoreOp = vk::AttachmentStoreOp::eDontCare, + .initialLayout = vk::ImageLayout::eUndefined, + .finalLayout = vk::ImageLayout::eColorAttachmentOptimal}; + + vk::AttachmentDescription depthAttachment{ + .format = findDepthFormat(), + .samples = msaaSamples, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eDontCare, + .stencilLoadOp = vk::AttachmentLoadOp::eDontCare, + .stencilStoreOp = vk::AttachmentStoreOp::eDontCare, + .initialLayout = vk::ImageLayout::eUndefined, + .finalLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal}; + + vk::AttachmentDescription colorAttachmentResolve{ + .format = swapChainSurfaceFormat.format, + .samples = vk::SampleCountFlagBits::e1, + .loadOp = vk::AttachmentLoadOp::eDontCare, + .storeOp = vk::AttachmentStoreOp::eStore, + .stencilLoadOp = vk::AttachmentLoadOp::eDontCare, + .stencilStoreOp = vk::AttachmentStoreOp::eDontCare, + .initialLayout = vk::ImageLayout::eUndefined, + .finalLayout = vk::ImageLayout::ePresentSrcKHR}; + + // Subpass references + vk::AttachmentReference colorAttachmentRef{ + .attachment = 0, + .layout = vk::ImageLayout::eColorAttachmentOptimal}; + + vk::AttachmentReference depthAttachmentRef{ + .attachment = 1, + .layout = vk::ImageLayout::eDepthStencilAttachmentOptimal}; + + vk::AttachmentReference colorAttachmentResolveRef{ + .attachment = 2, + .layout = vk::ImageLayout::eColorAttachmentOptimal}; + + // Subpass description + vk::SubpassDescription subpass{ + .pipelineBindPoint = vk::PipelineBindPoint::eGraphics, + .colorAttachmentCount = 1, + .pColorAttachments = &colorAttachmentRef, + .pResolveAttachments = &colorAttachmentResolveRef, + .pDepthStencilAttachment = &depthAttachmentRef}; + + // Dependency to ensure proper image layout transitions + vk::SubpassDependency dependency{ + .srcSubpass = VK_SUBPASS_EXTERNAL, + .dstSubpass = 0, + .srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eEarlyFragmentTests, + .dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eEarlyFragmentTests, + .srcAccessMask = vk::AccessFlagBits::eNone, + .dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite | vk::AccessFlagBits::eDepthStencilAttachmentWrite}; + + // Create the render pass + std::array attachments = {colorAttachment, depthAttachment, colorAttachmentResolve}; + vk::RenderPassCreateInfo renderPassInfo{ + .attachmentCount = static_cast(attachments.size()), + .pAttachments = attachments.data(), + .subpassCount = 1, + .pSubpasses = &subpass, + .dependencyCount = 1, + .pDependencies = &dependency}; + + renderPass = vk::raii::RenderPass(device, renderPassInfo); + } + + void createFramebuffers() + { + if (appInfo.dynamicRenderingSupported) + { + // No framebuffers needed with dynamic rendering + std::cout << "Using dynamic rendering, skipping framebuffer creation\n"; + return; + } + + std::cout << "Creating traditional framebuffers\n"; + + swapChainFramebuffers.clear(); + + for (size_t i = 0; i < swapChainImageViews.size(); i++) + { + std::array attachments = { + *colorImageView, + *depthImageView, + *swapChainImageViews[i]}; + + vk::FramebufferCreateInfo framebufferInfo{ + .renderPass = *renderPass, + .attachmentCount = static_cast(attachments.size()), + .pAttachments = attachments.data(), + .width = swapChainExtent.width, + .height = swapChainExtent.height, + .layers = 1}; + + swapChainFramebuffers.emplace_back(device, framebufferInfo); + } + } + + void createDescriptorSetLayout() + { + std::array bindings = { + vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr), + vk::DescriptorSetLayoutBinding(1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment, nullptr)}; + + vk::DescriptorSetLayoutCreateInfo layoutInfo{.bindingCount = static_cast(bindings.size()), .pBindings = bindings.data()}; + descriptorSetLayout = vk::raii::DescriptorSetLayout(device, layoutInfo); + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; + + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{ + .vertexBindingDescriptionCount = 1, + .pVertexBindingDescriptions = &bindingDescription, + .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), + .pVertexAttributeDescriptions = attributeDescriptions.data()}; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ + .topology = vk::PrimitiveTopology::eTriangleList, + .primitiveRestartEnable = vk::False}; + vk::PipelineViewportStateCreateInfo viewportState{ + .viewportCount = 1, + .scissorCount = 1}; + vk::PipelineRasterizationStateCreateInfo rasterizer{ + .depthClampEnable = vk::False, + .rasterizerDiscardEnable = vk::False, + .polygonMode = vk::PolygonMode::eFill, + .cullMode = vk::CullModeFlagBits::eBack, + .frontFace = vk::FrontFace::eCounterClockwise, + .depthBiasEnable = vk::False}; + rasterizer.lineWidth = 1.0f; + vk::PipelineMultisampleStateCreateInfo multisampling{ + .rasterizationSamples = msaaSamples, + .sampleShadingEnable = vk::False}; + vk::PipelineDepthStencilStateCreateInfo depthStencil{ + .depthTestEnable = vk::True, + .depthWriteEnable = vk::True, + .depthCompareOp = vk::CompareOp::eLess, + .depthBoundsTestEnable = vk::False, + .stencilTestEnable = vk::False}; + vk::PipelineColorBlendAttachmentState colorBlendAttachment; + colorBlendAttachment.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA; + colorBlendAttachment.blendEnable = vk::False; + + vk::PipelineColorBlendStateCreateInfo colorBlending{ + .logicOpEnable = vk::False, + .logicOp = vk::LogicOp::eCopy, + .attachmentCount = 1, + .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0}; + + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::GraphicsPipelineCreateInfo pipelineInfo{ + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pDepthStencilState = &depthStencil, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout}; + + // Configure pipeline based on dynamic rendering support + vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo; + if (appInfo.dynamicRenderingSupported) + { + std::cout << "Configuring pipeline for dynamic rendering\n"; + pipelineRenderingCreateInfo.colorAttachmentCount = 1; + pipelineRenderingCreateInfo.pColorAttachmentFormats = &swapChainSurfaceFormat.format; + pipelineRenderingCreateInfo.depthAttachmentFormat = findDepthFormat(); + + pipelineInfo.pNext = &pipelineRenderingCreateInfo; + pipelineInfo.renderPass = nullptr; + } + else + { + std::cout << "Configuring pipeline for traditional render pass\n"; + pipelineInfo.pNext = nullptr; + pipelineInfo.renderPass = *renderPass; + pipelineInfo.subpass = 0; + } + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{ + .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex}; + commandPool = vk::raii::CommandPool(device, poolInfo); + } + + void createColorResources() + { + vk::Format colorFormat = swapChainSurfaceFormat.format; + + createImage(swapChainExtent.width, swapChainExtent.height, 1, msaaSamples, colorFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransientAttachment | vk::ImageUsageFlagBits::eColorAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, colorImage, colorImageMemory); + colorImageView = createImageView(colorImage, colorFormat, vk::ImageAspectFlagBits::eColor, 1); + } + + void createDepthResources() + { + vk::Format depthFormat = findDepthFormat(); + + createImage(swapChainExtent.width, swapChainExtent.height, 1, msaaSamples, depthFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eDepthStencilAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, depthImage, depthImageMemory); + depthImageView = createImageView(depthImage, depthFormat, vk::ImageAspectFlagBits::eDepth, 1); + } + + vk::Format findSupportedFormat(const std::vector &candidates, vk::ImageTiling tiling, vk::FormatFeatureFlags features) const + { + for (const auto format : candidates) + { + vk::FormatProperties props = physicalDevice.getFormatProperties(format); + + if (tiling == vk::ImageTiling::eLinear && (props.linearTilingFeatures & features) == features) + { + return format; + } + if (tiling == vk::ImageTiling::eOptimal && (props.optimalTilingFeatures & features) == features) + { + return format; + } + } + + throw std::runtime_error("failed to find supported format!"); + } + + [[nodiscard]] vk::Format findDepthFormat() const + { + return findSupportedFormat( + {vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, vk::Format::eD24UnormS8Uint}, + vk::ImageTiling::eOptimal, + vk::FormatFeatureFlagBits::eDepthStencilAttachment); + } + + static bool hasStencilComponent(vk::Format format) + { + return format == vk::Format::eD32SfloatS8Uint || format == vk::Format::eD24UnormS8Uint; + } + + void createTextureImage() + { + int texWidth, texHeight, texChannels; + stbi_uc *pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + vk::DeviceSize imageSize = texWidth * texHeight * 4; + mipLevels = static_cast(std::floor(std::log2(std::max(texWidth, texHeight)))) + 1; + + if (!pixels) + { + throw std::runtime_error("failed to load texture image!"); + } + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, imageSize); + memcpy(data, pixels, imageSize); + stagingBufferMemory.unmapMemory(); + + stbi_image_free(pixels); + + createImage(texWidth, texHeight, mipLevels, vk::SampleCountFlagBits::e1, vk::Format::eR8G8B8A8Srgb, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory); + + transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, mipLevels); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); + + generateMipmaps(textureImage, vk::Format::eR8G8B8A8Srgb, texWidth, texHeight, mipLevels); + } + + void generateMipmaps(vk::raii::Image &image, vk::Format imageFormat, int32_t texWidth, int32_t texHeight, uint32_t mipLevels) + { + // Check if image format supports linear blit-ing + vk::FormatProperties formatProperties = physicalDevice.getFormatProperties(imageFormat); + + if (!(formatProperties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eSampledImageFilterLinear)) + { + throw std::runtime_error("texture image format does not support linear blitting!"); + } + + std::unique_ptr commandBuffer = beginSingleTimeCommands(); + + vk::ImageMemoryBarrier barrier = {.srcAccessMask = vk::AccessFlagBits::eTransferWrite, .dstAccessMask = vk::AccessFlagBits::eTransferRead, .oldLayout = vk::ImageLayout::eTransferDstOptimal, .newLayout = vk::ImageLayout::eTransferSrcOptimal, .srcQueueFamilyIndex = vk::QueueFamilyIgnored, .dstQueueFamilyIndex = vk::QueueFamilyIgnored, .image = image}; + barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + barrier.subresourceRange.levelCount = 1; + + int32_t mipWidth = texWidth; + int32_t mipHeight = texHeight; + + for (uint32_t i = 1; i < mipLevels; i++) + { + barrier.subresourceRange.baseMipLevel = i - 1; + barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal; + barrier.newLayout = vk::ImageLayout::eTransferSrcOptimal; + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eTransferRead; + + commandBuffer->pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer, {}, {}, {}, barrier); + + vk::ArrayWrapper1D offsets, dstOffsets; + offsets[0] = vk::Offset3D(0, 0, 0); + offsets[1] = vk::Offset3D(mipWidth, mipHeight, 1); + dstOffsets[0] = vk::Offset3D(0, 0, 0); + dstOffsets[1] = vk::Offset3D(mipWidth > 1 ? mipWidth / 2 : 1, mipHeight > 1 ? mipHeight / 2 : 1, 1); + vk::ImageBlit blit = {.srcSubresource = {}, .srcOffsets = offsets, .dstSubresource = {}, .dstOffsets = dstOffsets}; + blit.srcSubresource = vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, i - 1, 0, 1); + blit.dstSubresource = vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, i, 0, 1); + + commandBuffer->blitImage(image, vk::ImageLayout::eTransferSrcOptimal, image, vk::ImageLayout::eTransferDstOptimal, {blit}, vk::Filter::eLinear); + + barrier.oldLayout = vk::ImageLayout::eTransferSrcOptimal; + barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal; + barrier.srcAccessMask = vk::AccessFlagBits::eTransferRead; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + + commandBuffer->pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {}, barrier); + + if (mipWidth > 1) + mipWidth /= 2; + if (mipHeight > 1) + mipHeight /= 2; + } + + barrier.subresourceRange.baseMipLevel = mipLevels - 1; + barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal; + barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal; + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + + commandBuffer->pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {}, barrier); + + endSingleTimeCommands(*commandBuffer); + } + + vk::SampleCountFlagBits getMaxUsableSampleCount() + { + vk::PhysicalDeviceProperties physicalDeviceProperties = physicalDevice.getProperties(); + + vk::SampleCountFlags counts = physicalDeviceProperties.limits.framebufferColorSampleCounts & physicalDeviceProperties.limits.framebufferDepthSampleCounts; + if (counts & vk::SampleCountFlagBits::e64) + { + return vk::SampleCountFlagBits::e64; + } + if (counts & vk::SampleCountFlagBits::e32) + { + return vk::SampleCountFlagBits::e32; + } + if (counts & vk::SampleCountFlagBits::e16) + { + return vk::SampleCountFlagBits::e16; + } + if (counts & vk::SampleCountFlagBits::e8) + { + return vk::SampleCountFlagBits::e8; + } + if (counts & vk::SampleCountFlagBits::e4) + { + return vk::SampleCountFlagBits::e4; + } + if (counts & vk::SampleCountFlagBits::e2) + { + return vk::SampleCountFlagBits::e2; + } + + return vk::SampleCountFlagBits::e1; + } + + void createTextureImageView() + { + textureImageView = createImageView(textureImage, vk::Format::eR8G8B8A8Srgb, vk::ImageAspectFlagBits::eColor, mipLevels); + } + + void createTextureSampler() + { + vk::PhysicalDeviceProperties properties = physicalDevice.getProperties(); + vk::SamplerCreateInfo samplerInfo{ + .magFilter = vk::Filter::eLinear, + .minFilter = vk::Filter::eLinear, + .mipmapMode = vk::SamplerMipmapMode::eLinear, + .addressModeU = vk::SamplerAddressMode::eRepeat, + .addressModeV = vk::SamplerAddressMode::eRepeat, + .addressModeW = vk::SamplerAddressMode::eRepeat, + .mipLodBias = 0.0f, + .anisotropyEnable = vk::True, + .maxAnisotropy = properties.limits.maxSamplerAnisotropy, + .compareEnable = vk::False, + .compareOp = vk::CompareOp::eAlways}; + textureSampler = vk::raii::Sampler(device, samplerInfo); + } + + [[nodiscard]] vk::raii::ImageView createImageView(const vk::raii::Image &image, vk::Format format, vk::ImageAspectFlags aspectFlags, uint32_t mipLevels) const + { + vk::ImageViewCreateInfo viewInfo{ + .image = image, + .viewType = vk::ImageViewType::e2D, + .format = format, + .subresourceRange = {aspectFlags, 0, mipLevels, 0, 1}}; + return vk::raii::ImageView(device, viewInfo); + } + + void createImage(uint32_t width, uint32_t height, uint32_t mipLevels, vk::SampleCountFlagBits numSamples, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image &image, vk::raii::DeviceMemory &imageMemory) + { + vk::ImageCreateInfo imageInfo{ + .imageType = vk::ImageType::e2D, + .format = format, + .extent = {width, height, 1}, + .mipLevels = mipLevels, + .arrayLayers = 1, + .samples = numSamples, + .tiling = tiling, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive, + .initialLayout = vk::ImageLayout::eUndefined}; + image = vk::raii::Image(device, imageInfo); + + vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{ + .allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + imageMemory = vk::raii::DeviceMemory(device, allocInfo); + image.bindMemory(imageMemory, 0); + } + + void transitionImageLayout(const vk::raii::Image &image, const vk::ImageLayout oldLayout, const vk::ImageLayout newLayout, uint32_t mipLevels) + { + const auto commandBuffer = beginSingleTimeCommands(); + + if (appInfo.synchronization2Supported) + { + // Use Synchronization2 API + vk::ImageMemoryBarrier2 barrier{ + .srcStageMask = vk::PipelineStageFlagBits2::eAllCommands, + .dstStageMask = vk::PipelineStageFlagBits2::eAllCommands, + .oldLayout = oldLayout, + .newLayout = newLayout, + .image = image, + .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, mipLevels, 0, 1}}; + + if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) + { + barrier.srcAccessMask = vk::AccessFlagBits2::eNone; + barrier.dstAccessMask = vk::AccessFlagBits2::eTransferWrite; + barrier.srcStageMask = vk::PipelineStageFlagBits2::eTopOfPipe; + barrier.dstStageMask = vk::PipelineStageFlagBits2::eTransfer; + } + else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) + { + barrier.srcAccessMask = vk::AccessFlagBits2::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits2::eShaderRead; + barrier.srcStageMask = vk::PipelineStageFlagBits2::eTransfer; + barrier.dstStageMask = vk::PipelineStageFlagBits2::eFragmentShader; + } + else + { + throw std::invalid_argument("unsupported layout transition!"); + } + + vk::DependencyInfo dependencyInfo{ + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + + commandBuffer->pipelineBarrier2(dependencyInfo); + } + else + { + // Use traditional synchronization API + vk::ImageMemoryBarrier barrier{ + .oldLayout = oldLayout, + .newLayout = newLayout, + .image = image, + .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, mipLevels, 0, 1}}; + + vk::PipelineStageFlags sourceStage; + vk::PipelineStageFlags destinationStage; + + if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) + { + barrier.srcAccessMask = {}; + barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; + + sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; + destinationStage = vk::PipelineStageFlagBits::eTransfer; + } + else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) + { + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + + sourceStage = vk::PipelineStageFlagBits::eTransfer; + destinationStage = vk::PipelineStageFlagBits::eFragmentShader; + } + else + { + throw std::invalid_argument("unsupported layout transition!"); + } + commandBuffer->pipelineBarrier(sourceStage, destinationStage, {}, {}, nullptr, barrier); + } + + endSingleTimeCommands(*commandBuffer); + } + + void copyBufferToImage(const vk::raii::Buffer &buffer, const vk::raii::Image &image, uint32_t width, uint32_t height) + { + std::unique_ptr commandBuffer = beginSingleTimeCommands(); + vk::BufferImageCopy region{ + .bufferOffset = 0, + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource = {vk::ImageAspectFlagBits::eColor, 0, 0, 1}, + .imageOffset = {0, 0, 0}, + .imageExtent = {width, height, 1}}; + commandBuffer->copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, {region}); + endSingleTimeCommands(*commandBuffer); + } + + void loadModel() + { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn, err; + + if (!LoadObj(&attrib, &shapes, &materials, &warn, &err, MODEL_PATH.c_str())) + { + throw std::runtime_error(warn + err); + } + + std::unordered_map uniqueVertices{}; + + for (const auto &shape : shapes) + { + for (const auto &index : shape.mesh.indices) + { + Vertex vertex{}; + + vertex.pos = { + attrib.vertices[3 * index.vertex_index + 0], + attrib.vertices[3 * index.vertex_index + 1], + attrib.vertices[3 * index.vertex_index + 2]}; + + vertex.texCoord = { + attrib.texcoords[2 * index.texcoord_index + 0], + 1.0f - attrib.texcoords[2 * index.texcoord_index + 1]}; + + vertex.color = {1.0f, 1.0f, 1.0f}; + + if (!uniqueVertices.contains(vertex)) + { + uniqueVertices[vertex] = static_cast(vertices.size()); + vertices.push_back(vertex); + } + + indices.push_back(uniqueVertices[vertex]); + } + } + } + + void createVertexBuffer() + { + vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(dataStaging, vertices.data(), bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + } + + void createIndexBuffer() + { + vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(data, indices.data(), bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); + + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + } + + void createUniformBuffers() + { + uniformBuffers.clear(); + uniformBuffersMemory.clear(); + uniformBuffersMapped.clear(); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DeviceSize bufferSize = sizeof(UniformBufferObject); + vk::raii::Buffer buffer({}); + vk::raii::DeviceMemory bufferMem({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); + uniformBuffers.emplace_back(std::move(buffer)); + uniformBuffersMemory.emplace_back(std::move(bufferMem)); + uniformBuffersMapped.emplace_back(uniformBuffersMemory[i].mapMemory(0, bufferSize)); + } + } + + void createDescriptorPool() + { + std::array poolSize{ + vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT), + vk::DescriptorPoolSize(vk::DescriptorType::eCombinedImageSampler, MAX_FRAMES_IN_FLIGHT)}; + vk::DescriptorPoolCreateInfo poolInfo{ + .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, + .maxSets = MAX_FRAMES_IN_FLIGHT, + .poolSizeCount = static_cast(poolSize.size()), + .pPoolSizes = poolSize.data()}; + descriptorPool = vk::raii::DescriptorPool(device, poolInfo); + } + + void createDescriptorSets() + { + std::vector layouts(MAX_FRAMES_IN_FLIGHT, descriptorSetLayout); + vk::DescriptorSetAllocateInfo allocInfo{ + .descriptorPool = descriptorPool, + .descriptorSetCount = static_cast(layouts.size()), + .pSetLayouts = layouts.data()}; + + descriptorSets.clear(); + descriptorSets = device.allocateDescriptorSets(allocInfo); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DescriptorBufferInfo bufferInfo{ + .buffer = uniformBuffers[i], + .offset = 0, + .range = sizeof(UniformBufferObject)}; + vk::DescriptorImageInfo imageInfo{ + .sampler = textureSampler, + .imageView = textureImageView, + .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal}; + std::array descriptorWrites{ + vk::WriteDescriptorSet{ + .dstSet = descriptorSets[i], + .dstBinding = 0, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eUniformBuffer, + .pBufferInfo = &bufferInfo}, + vk::WriteDescriptorSet{ + .dstSet = descriptorSets[i], + .dstBinding = 1, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .pImageInfo = &imageInfo}}; + device.updateDescriptorSets(descriptorWrites, {}); + } + } + + void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer &buffer, vk::raii::DeviceMemory &bufferMemory) + { + vk::BufferCreateInfo bufferInfo{ + .size = size, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive}; + buffer = vk::raii::Buffer(device, bufferInfo); + vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{ + .allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + bufferMemory = vk::raii::DeviceMemory(device, allocInfo); + buffer.bindMemory(bufferMemory, 0); + } + + std::unique_ptr beginSingleTimeCommands() + { + vk::CommandBufferAllocateInfo allocInfo{ + .commandPool = commandPool, + .level = vk::CommandBufferLevel::ePrimary, + .commandBufferCount = 1}; + std::unique_ptr commandBuffer = std::make_unique(std::move(vk::raii::CommandBuffers(device, allocInfo).front())); + + vk::CommandBufferBeginInfo beginInfo{ + .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; + commandBuffer->begin(beginInfo); + + return commandBuffer; + } + + void endSingleTimeCommands(const vk::raii::CommandBuffer &commandBuffer) const + { + commandBuffer.end(); + + vk::SubmitInfo submitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandBuffer}; + queue.submit(submitInfo, nullptr); + queue.waitIdle(); + } + + void copyBuffer(vk::raii::Buffer &srcBuffer, vk::raii::Buffer &dstBuffer, vk::DeviceSize size) + { + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1}; + vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); + commandCopyBuffer.begin(vk::CommandBufferBeginInfo{.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); + commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy{.size = size}); + commandCopyBuffer.end(); + queue.submit(vk::SubmitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer}, nullptr); + queue.waitIdle(); + } + + uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) + { + vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) + { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) + { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + void createCommandBuffers() + { + commandBuffers.clear(); + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = MAX_FRAMES_IN_FLIGHT}; + commandBuffers = vk::raii::CommandBuffers(device, allocInfo); + } + + void recordCommandBuffer(uint32_t imageIndex) + { + commandBuffers[currentFrame].begin({}); + + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::ClearValue clearDepth = vk::ClearDepthStencilValue(1.0f, 0); + std::array clearValues = {clearColor, clearDepth}; + + if (appInfo.dynamicRenderingSupported) + { + // Transition attachments to the correct layout + if (appInfo.synchronization2Supported) + { + // Use Synchronization2 API for image transitions + vk::ImageMemoryBarrier2 colorBarrier{ + .srcStageMask = vk::PipelineStageFlagBits2::eTopOfPipe, + .srcAccessMask = vk::AccessFlagBits2::eNone, + .dstStageMask = vk::PipelineStageFlagBits2::eColorAttachmentOutput, + .dstAccessMask = vk::AccessFlagBits2::eColorAttachmentWrite, + .oldLayout = vk::ImageLayout::eUndefined, + .newLayout = vk::ImageLayout::eColorAttachmentOptimal, + .image = *colorImage, + .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + + vk::ImageMemoryBarrier2 depthBarrier{ + .srcStageMask = vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, + .srcAccessMask = vk::AccessFlagBits2::eNone, + .dstStageMask = vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, + .dstAccessMask = vk::AccessFlagBits2::eDepthStencilAttachmentWrite, + .oldLayout = vk::ImageLayout::eUndefined, + .newLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal, + .image = *depthImage, + .subresourceRange = {vk::ImageAspectFlagBits::eDepth, 0, 1, 0, 1}}; + + vk::ImageMemoryBarrier2 swapchainBarrier{ + .srcStageMask = vk::PipelineStageFlagBits2::eTopOfPipe, + .srcAccessMask = vk::AccessFlagBits2::eNone, + .dstStageMask = vk::PipelineStageFlagBits2::eColorAttachmentOutput, + .dstAccessMask = vk::AccessFlagBits2::eColorAttachmentWrite, + .oldLayout = vk::ImageLayout::eUndefined, + .newLayout = vk::ImageLayout::eColorAttachmentOptimal, + .image = swapChainImages[imageIndex], + .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + + std::array barriers = {colorBarrier, depthBarrier, swapchainBarrier}; + vk::DependencyInfo dependencyInfo{ + .imageMemoryBarrierCount = static_cast(barriers.size()), + .pImageMemoryBarriers = barriers.data()}; + + commandBuffers[currentFrame].pipelineBarrier2(dependencyInfo); + } + else + { + // Use traditional synchronization API + vk::ImageMemoryBarrier colorBarrier{ + .srcAccessMask = vk::AccessFlagBits::eNone, + .dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite, + .oldLayout = vk::ImageLayout::eUndefined, + .newLayout = vk::ImageLayout::eColorAttachmentOptimal, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = *colorImage, + .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + + vk::ImageMemoryBarrier depthBarrier{ + .srcAccessMask = vk::AccessFlagBits::eNone, + .dstAccessMask = vk::AccessFlagBits::eDepthStencilAttachmentWrite, + .oldLayout = vk::ImageLayout::eUndefined, + .newLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = *depthImage, + .subresourceRange = {vk::ImageAspectFlagBits::eDepth, 0, 1, 0, 1}}; + + vk::ImageMemoryBarrier swapchainBarrier{ + .srcAccessMask = vk::AccessFlagBits::eNone, + .dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite, + .oldLayout = vk::ImageLayout::eUndefined, + .newLayout = vk::ImageLayout::eColorAttachmentOptimal, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + + std::array barriers = {colorBarrier, depthBarrier, swapchainBarrier}; + commandBuffers[currentFrame].pipelineBarrier( + vk::PipelineStageFlagBits::eTopOfPipe, + vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eEarlyFragmentTests, + vk::DependencyFlagBits::eByRegion, + {}, + {}, + barriers); + } + + // Setup rendering attachments + vk::RenderingAttachmentInfo colorAttachment{ + .imageView = *colorImageView, + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .resolveMode = vk::ResolveModeFlagBits::eAverage, + .resolveImageView = *swapChainImageViews[imageIndex], + .resolveImageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor}; + + vk::RenderingAttachmentInfo depthAttachment{ + .imageView = *depthImageView, + .imageLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eDontCare, + .clearValue = clearDepth}; + + vk::RenderingInfo renderingInfo{ + .renderArea = {{0, 0}, swapChainExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &colorAttachment, + .pDepthAttachment = &depthAttachment}; + + commandBuffers[currentFrame].beginRendering(renderingInfo); + } + else + { + // Use traditional render pass + std::cout << "Recording command buffer with traditional render pass\n"; + + vk::RenderPassBeginInfo renderPassInfo{ + .renderPass = *renderPass, + .framebuffer = *swapChainFramebuffers[imageIndex], + .renderArea = {{0, 0}, swapChainExtent}, + .clearValueCount = static_cast(clearValues.size()), + .pClearValues = clearValues.data()}; + + commandBuffers[currentFrame].beginRenderPass(renderPassInfo, vk::SubpassContents::eInline); + } + + // Common rendering commands + commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint32); + commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); + commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); + + if (appInfo.dynamicRenderingSupported) + { + commandBuffers[currentFrame].endRendering(); + + // Transition swapchain image to present layout + if (appInfo.synchronization2Supported) + { + vk::ImageMemoryBarrier2 barrier{ + .srcStageMask = vk::PipelineStageFlagBits2::eColorAttachmentOutput, + .srcAccessMask = vk::AccessFlagBits2::eColorAttachmentWrite, + .dstStageMask = vk::PipelineStageFlagBits2::eBottomOfPipe, + .dstAccessMask = vk::AccessFlagBits2::eNone, + .oldLayout = vk::ImageLayout::eColorAttachmentOptimal, + .newLayout = vk::ImageLayout::ePresentSrcKHR, + .image = swapChainImages[imageIndex], + .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + + vk::DependencyInfo dependencyInfo{ + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + + commandBuffers[currentFrame].pipelineBarrier2(dependencyInfo); + } + else + { + vk::ImageMemoryBarrier barrier{ + .srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite, + .dstAccessMask = vk::AccessFlagBits::eNone, + .oldLayout = vk::ImageLayout::eColorAttachmentOptimal, + .newLayout = vk::ImageLayout::ePresentSrcKHR, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + + commandBuffers[currentFrame].pipelineBarrier( + vk::PipelineStageFlagBits::eColorAttachmentOutput, + vk::PipelineStageFlagBits::eBottomOfPipe, + vk::DependencyFlagBits::eByRegion, + {}, + {}, + {barrier}); + } + } + else + { + commandBuffers[currentFrame].endRenderPass(); + } + + commandBuffers[currentFrame].end(); + } + + void createSyncObjects() + { + presentCompleteSemaphore.clear(); + renderFinishedSemaphore.clear(); + inFlightFences.clear(); + + if (appInfo.timelineSemaphoresSupported) + { + // Create timeline semaphore + std::cout << "Creating timeline semaphores\n"; + vk::SemaphoreTypeCreateInfo timelineCreateInfo{ + .semaphoreType = vk::SemaphoreType::eTimeline, + .initialValue = 0}; + + vk::SemaphoreCreateInfo semaphoreInfo{ + .pNext = &timelineCreateInfo}; + + timelineSemaphore = vk::raii::Semaphore(device, semaphoreInfo); + + // Still need binary semaphores for swapchain operations + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + } + } + else + { + // Create binary semaphores and fences + std::cout << "Creating binary semaphores and fences\n"; + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + } + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); + } + } + + void updateUniformBuffer(uint32_t currentImage) const + { + static auto startTime = std::chrono::high_resolution_clock::now(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + UniformBufferObject ubo{}; + ubo.model = rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.view = lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); + ubo.proj[1][1] *= -1; + + memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); + } + + void drawFrame() + { + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, FenceTimeout)) + ; + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[currentFrame], nullptr); + + if (result == vk::Result::eErrorOutOfDateKHR) + { + recreateSwapChain(); + return; + } + if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) + { + throw std::runtime_error("failed to acquire swap chain image!"); + } + updateUniformBuffer(currentFrame); + + device.resetFences(*inFlightFences[currentFrame]); + commandBuffers[currentFrame].reset(); + recordCommandBuffer(imageIndex); + + if (appInfo.timelineSemaphoresSupported) + { + // Use timeline semaphores for GPU synchronization + uint64_t waitValue = timelineValue; + uint64_t signalValue = ++timelineValue; + + vk::TimelineSemaphoreSubmitInfo timelineInfo{ + .waitSemaphoreValueCount = 0, // We'll still use binary semaphore for swapchain + .signalSemaphoreValueCount = 1, + .pSignalSemaphoreValues = &signalValue}; + + std::array waitSemaphores = {*presentCompleteSemaphore[currentFrame], *timelineSemaphore}; + std::array waitStages = {vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::PipelineStageFlagBits::eVertexInput}; + std::array waitValues = {0, waitValue}; // Binary semaphore value is ignored + + std::array signalSemaphores = {*renderFinishedSemaphore[currentFrame], *timelineSemaphore}; + std::array signalValues = {0, signalValue}; // Binary semaphore value is ignored + + timelineInfo.waitSemaphoreValueCount = 1; // Only for the timeline semaphore + timelineInfo.pWaitSemaphoreValues = &waitValues[1]; + timelineInfo.signalSemaphoreValueCount = 1; // Only for the timeline semaphore + timelineInfo.pSignalSemaphoreValues = &signalValues[1]; + + vk::SubmitInfo submitInfo{ + .pNext = &timelineInfo, + .waitSemaphoreCount = 1, // Only wait on the binary semaphore + .pWaitSemaphores = &waitSemaphores[0], + .pWaitDstStageMask = &waitStages[0], + .commandBufferCount = 1, + .pCommandBuffers = &*commandBuffers[currentFrame], + .signalSemaphoreCount = 2, // Signal both semaphores + .pSignalSemaphores = signalSemaphores.data()}; + + queue.submit(submitInfo, *inFlightFences[currentFrame]); + } + else + { + // Use traditional binary semaphores + vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); + const vk::SubmitInfo submitInfo{ + .waitSemaphoreCount = 1, + .pWaitSemaphores = &*presentCompleteSemaphore[currentFrame], + .pWaitDstStageMask = &waitDestinationStageMask, + .commandBufferCount = 1, + .pCommandBuffers = &*commandBuffers[currentFrame], + .signalSemaphoreCount = 1, + .pSignalSemaphores = &*renderFinishedSemaphore[currentFrame]}; + queue.submit(submitInfo, *inFlightFences[currentFrame]); + } + + const vk::PresentInfoKHR presentInfoKHR{ + .waitSemaphoreCount = 1, + .pWaitSemaphores = &*renderFinishedSemaphore[currentFrame], + .swapchainCount = 1, + .pSwapchains = &*swapChain, + .pImageIndices = &imageIndex}; + result = queue.presentKHR(presentInfoKHR); + if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) + { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != vk::Result::eSuccess) + { + throw std::runtime_error("failed to present swap chain image!"); + } + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size(), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const &surfaceCapabilities) + { + auto minImageCount = std::max(3u, surfaceCapabilities.minImageCount); + if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) + { + minImageCount = surfaceCapabilities.maxImageCount; + } + return minImageCount; + } + + static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + assert(!availableFormats.empty()); + const auto formatIt = std::ranges::find_if( + availableFormats, + [](const auto &format) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; }); + return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + assert(std::ranges::any_of(availablePresentModes, [](auto presentMode) { return presentMode == vk::PresentModeKHR::eFifo; })); + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + vk::PresentModeKHR::eFifo; + } + + [[nodiscard]] vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR &capabilities) const + { + if (capabilities.currentExtent.width != 0xFFFFFFFF) + { + 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)}; + } + + [[nodiscard]] std::vector getRequiredExtensions() const + { + // Get the required extensions from GLFW + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + // Check if the debug utils extension is available + std::vector props = context.enumerateInstanceExtensionProperties(); + bool debugUtilsAvailable = std::ranges::any_of(props, + [](vk::ExtensionProperties const &ep) { + return strcmp(ep.extensionName, vk::EXTDebugUtilsExtensionName) == 0; + }); + + // Always include the debug utils extension if available + // This allows validation layers to be enabled via vulkanconfig + if (debugUtilsAvailable) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + else + { + std::cout << "VK_EXT_debug_utils extension not available. Validation layers may not work." << std::endl; + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + 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; + } }; -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/33_vulkan_profiles.cpp b/attachments/33_vulkan_profiles.cpp index 4dee36c6..8475d206 100644 --- a/attachments/33_vulkan_profiles.cpp +++ b/attachments/33_vulkan_profiles.cpp @@ -13,14 +13,14 @@ #include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include #define GLM_FORCE_RADIANS @@ -36,1675 +36,1740 @@ import vulkan_hpp; #define TINYOBJLOADER_IMPLEMENTATION #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -constexpr uint64_t FenceTimeout = 100000000; -const std::string MODEL_PATH = "models/viking_room.obj"; -const std::string TEXTURE_PATH = "textures/viking_room.png"; -constexpr int MAX_FRAMES_IN_FLIGHT = 2; +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; +constexpr uint64_t FenceTimeout = 100000000; +const std::string MODEL_PATH = "models/viking_room.obj"; +const std::string TEXTURE_PATH = "textures/viking_room.png"; +constexpr int MAX_FRAMES_IN_FLIGHT = 2; // Application info structure to store profile support flags -struct AppInfo { - bool profileSupported = false; - VpProfileProperties profile; +struct AppInfo +{ + bool profileSupported = false; + VpProfileProperties profile; }; // Moved struct definitions inside the class -struct Vertex { - glm::vec3 pos; - glm::vec3 color; - glm::vec2 texCoord; - - static vk::VertexInputBindingDescription getBindingDescription() { - return { 0, sizeof(Vertex), vk::VertexInputRate::eVertex }; - } - - static std::array getAttributeDescriptions() { - return { - vk::VertexInputAttributeDescription( 0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, pos) ), - vk::VertexInputAttributeDescription( 1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color) ), - vk::VertexInputAttributeDescription( 2, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, texCoord) ) - }; - } - - bool operator==(const Vertex& other) const { - return pos == other.pos && color == other.color && texCoord == other.texCoord; - } +struct Vertex +{ + glm::vec3 pos; + glm::vec3 color; + glm::vec2 texCoord; + + static vk::VertexInputBindingDescription getBindingDescription() + { + return {0, sizeof(Vertex), vk::VertexInputRate::eVertex}; + } + + static std::array getAttributeDescriptions() + { + return { + vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, pos)), + vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color)), + vk::VertexInputAttributeDescription(2, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, texCoord))}; + } + + bool operator==(const Vertex &other) const + { + return pos == other.pos && color == other.color && texCoord == other.texCoord; + } }; -template<> struct std::hash { - size_t operator()(Vertex const& vertex) const noexcept { - return ((hash()(vertex.pos) ^ (hash()(vertex.color) << 1)) >> 1) ^ (hash()(vertex.texCoord) << 1); - } - }; +template <> +struct std::hash +{ + size_t operator()(Vertex const &vertex) const noexcept + { + return ((hash()(vertex.pos) ^ (hash()(vertex.color) << 1)) >> 1) ^ (hash()(vertex.texCoord) << 1); + } +}; -struct UniformBufferObject { - alignas(16) glm::mat4 model; - alignas(16) glm::mat4 view; - alignas(16) glm::mat4 proj; +struct UniformBufferObject +{ + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; }; -class HelloTriangleApplication { -public: - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } - -private: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::SurfaceFormatKHR swapChainSurfaceFormat; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::RenderPass renderPass = nullptr; - vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - std::vector swapChainFramebuffers; - vk::raii::CommandPool commandPool = nullptr; - std::vector commandBuffers; - std::vector imageAvailableSemaphores; - std::vector renderFinishedSemaphores; - std::vector inFlightFences; - std::vector presentCompleteSemaphore; - uint32_t currentFrame = 0; - bool framebufferResized = false; - vk::raii::Buffer vertexBuffer = nullptr; - vk::raii::DeviceMemory vertexBufferMemory = nullptr; - vk::raii::Buffer indexBuffer = nullptr; - vk::raii::DeviceMemory indexBufferMemory = nullptr; - std::vector uniformBuffers; - std::vector uniformBuffersMemory; - std::vector uniformBuffersMapped; - vk::raii::DescriptorPool descriptorPool = nullptr; - std::vector descriptorSets; - vk::raii::Image textureImage = nullptr; - vk::raii::DeviceMemory textureImageMemory = nullptr; - vk::raii::ImageView textureImageView = nullptr; - vk::raii::Sampler textureSampler = nullptr; - vk::raii::Image depthImage = nullptr; - vk::raii::DeviceMemory depthImageMemory = nullptr; - vk::raii::ImageView depthImageView = nullptr; - std::vector vertices; - std::vector indices; - vk::SampleCountFlagBits msaaSamples = vk::SampleCountFlagBits::e1; - vk::raii::Image colorImage = nullptr; - vk::raii::DeviceMemory colorImageMemory = nullptr; - vk::raii::ImageView colorImageView = nullptr; - - // Application info to store profile support - AppInfo appInfo = {}; - - struct SwapChainSupportDetails { - vk::SurfaceCapabilitiesKHR capabilities; - std::vector formats; - std::vector presentModes; - }; - - const std::vector requiredDeviceExtension = { - VK_KHR_SWAPCHAIN_EXTENSION_NAME - }; - - void initWindow() { - glfwInit(); - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan Profiles Demo", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); - } - - static void framebufferResizeCallback(GLFWwindow* window, int, int) { - auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); - app->framebufferResized = true; - } - - void initVulkan() { - createInstance(); - setupDebugMessenger(); - createSurface(); - pickPhysicalDevice(); - checkFeatureSupport(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - - // Create render pass only if not using dynamic rendering - if (!appInfo.profileSupported) { - createRenderPass(); - } - - createDescriptorSetLayout(); - createGraphicsPipeline(); - - // Create framebuffers only if not using dynamic rendering - if (!appInfo.profileSupported) { - createFramebuffers(); - } - - createCommandPool(); - createColorResources(); - createDepthResources(); - createTextureImage(); - createTextureImageView(); - createTextureSampler(); - loadModel(); - createVertexBuffer(); - createIndexBuffer(); - createUniformBuffers(); - createDescriptorPool(); - createDescriptorSets(); - createCommandBuffers(); - createSyncObjects(); - } - - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - drawFrame(); - } - - device.waitIdle(); - } - - void cleanupSwapChain() { - colorImageView = nullptr; - colorImage = nullptr; - colorImageMemory = nullptr; - - depthImageView = nullptr; - depthImage = nullptr; - depthImageMemory = nullptr; - - for (auto& framebuffer : swapChainFramebuffers) { - framebuffer = nullptr; - } - - for (auto& imageView : swapChainImageViews) { - imageView = nullptr; - } - - swapChain = nullptr; - } - - void cleanup() { - glfwDestroyWindow(window); - glfwTerminate(); - } - - void recreateSwapChain() { - int width = 0, height = 0; - glfwGetFramebufferSize(window, &width, &height); - while (width == 0 || height == 0) { - glfwGetFramebufferSize(window, &width, &height); - glfwWaitEvents(); - } - - device.waitIdle(); - - cleanupSwapChain(); - - createSwapChain(); - createImageViews(); - - // Recreate traditional render pass and framebuffers if not using profiles - if (!appInfo.profileSupported) { - createRenderPass(); - createFramebuffers(); - } - - createColorResources(); - createDepthResources(); - } - - void createInstance() { - - constexpr vk::ApplicationInfo appInfo{ - .pApplicationName = "Vulkan Profiles Demo", - .applicationVersion = VK_MAKE_VERSION(1, 0, 0), - .pEngineName = "No Engine", - .engineVersion = VK_MAKE_VERSION(1, 0, 0), - .apiVersion = vk::ApiVersion14 - }; - - auto extensions = getRequiredExtensions(); - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledExtensionCount = static_cast(extensions.size()), - .ppEnabledExtensionNames = extensions.data() - }; - - instance = vk::raii::Instance(context, createInfo); - - } - - void setupDebugMessenger() { - // Always set up the debug messenger - // It will only be used if validation layers are enabled via vulkanconfig - - vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( - vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | - vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | - vk::DebugUtilsMessageSeverityFlagBitsEXT::eError - ); - - vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( - vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | - vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | - vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation - ); - - vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; - - try { - debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); - } catch (vk::SystemError& err) { - // If the debug utils extension is not available, this will fail - // That's okay, it just means validation layers aren't enabled - std::cout << "Debug messenger not available. Validation layers may not be enabled." << std::endl; - } - } - - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&](auto const & device) - { - // Check if any of the queue families support graphics operations - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of(queueFamilies, [](auto const & qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); - - // Check if all required device extensions are available - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of(requiredDeviceExtension, - [&availableDeviceExtensions](auto const & requiredDeviceExtension) - { - return std::ranges::any_of(availableDeviceExtensions, - [requiredDeviceExtension](auto const & availableDeviceExtension) - { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); - }); - - return supportsGraphics && supportsAllRequiredExtensions; - }); - - if (devIter != devices.end()) { - physicalDevice = *devIter; - msaaSamples = getMaxUsableSampleCount(); - - // Print device information - vk::PhysicalDeviceProperties deviceProperties = physicalDevice.getProperties(); - std::cout << "Selected GPU: " << deviceProperties.deviceName << std::endl; - std::cout << "API Version: " << VK_VERSION_MAJOR(deviceProperties.apiVersion) << "." - << VK_VERSION_MINOR(deviceProperties.apiVersion) << "." - << VK_VERSION_PATCH(deviceProperties.apiVersion) << std::endl; - } else { - throw std::runtime_error("failed to find a suitable GPU!"); - } - } - - void checkFeatureSupport() { - // Define the KHR roadmap 2022 profile - more widely supported than 2024 - appInfo.profile = { - VP_KHR_ROADMAP_2022_NAME, - VP_KHR_ROADMAP_2022_SPEC_VERSION - }; - - // Check if the profile is supported - VkBool32 supported = VK_FALSE; - VkResult result = vpGetPhysicalDeviceProfileSupport( +class HelloTriangleApplication +{ + public: + void run() + { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + + private: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::SurfaceFormatKHR swapChainSurfaceFormat; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::RenderPass renderPass = nullptr; + vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + std::vector swapChainFramebuffers; + vk::raii::CommandPool commandPool = nullptr; + std::vector commandBuffers; + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + std::vector inFlightFences; + std::vector presentCompleteSemaphore; + uint32_t currentFrame = 0; + bool framebufferResized = false; + vk::raii::Buffer vertexBuffer = nullptr; + vk::raii::DeviceMemory vertexBufferMemory = nullptr; + vk::raii::Buffer indexBuffer = nullptr; + vk::raii::DeviceMemory indexBufferMemory = nullptr; + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + std::vector uniformBuffersMapped; + vk::raii::DescriptorPool descriptorPool = nullptr; + std::vector descriptorSets; + vk::raii::Image textureImage = nullptr; + vk::raii::DeviceMemory textureImageMemory = nullptr; + vk::raii::ImageView textureImageView = nullptr; + vk::raii::Sampler textureSampler = nullptr; + vk::raii::Image depthImage = nullptr; + vk::raii::DeviceMemory depthImageMemory = nullptr; + vk::raii::ImageView depthImageView = nullptr; + std::vector vertices; + std::vector indices; + vk::SampleCountFlagBits msaaSamples = vk::SampleCountFlagBits::e1; + vk::raii::Image colorImage = nullptr; + vk::raii::DeviceMemory colorImageMemory = nullptr; + vk::raii::ImageView colorImageView = nullptr; + + // Application info to store profile support + AppInfo appInfo = {}; + + struct SwapChainSupportDetails + { + vk::SurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; + }; + + const std::vector requiredDeviceExtension = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME}; + + void initWindow() + { + glfwInit(); + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan Profiles Demo", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow *window, int, int) + { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + checkFeatureSupport(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + + // Create render pass only if not using dynamic rendering + if (!appInfo.profileSupported) + { + createRenderPass(); + } + + createDescriptorSetLayout(); + createGraphicsPipeline(); + + // Create framebuffers only if not using dynamic rendering + if (!appInfo.profileSupported) + { + createFramebuffers(); + } + + createCommandPool(); + createColorResources(); + createDepthResources(); + createTextureImage(); + createTextureImageView(); + createTextureSampler(); + loadModel(); + createVertexBuffer(); + createIndexBuffer(); + createUniformBuffers(); + createDescriptorPool(); + createDescriptorSets(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() + { + while (!glfwWindowShouldClose(window)) + { + glfwPollEvents(); + drawFrame(); + } + + device.waitIdle(); + } + + void cleanupSwapChain() + { + colorImageView = nullptr; + colorImage = nullptr; + colorImageMemory = nullptr; + + depthImageView = nullptr; + depthImage = nullptr; + depthImageMemory = nullptr; + + for (auto &framebuffer : swapChainFramebuffers) + { + framebuffer = nullptr; + } + + for (auto &imageView : swapChainImageViews) + { + imageView = nullptr; + } + + swapChain = nullptr; + } + + void cleanup() + { + glfwDestroyWindow(window); + glfwTerminate(); + } + + void recreateSwapChain() + { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) + { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + device.waitIdle(); + + cleanupSwapChain(); + + createSwapChain(); + createImageViews(); + + // Recreate traditional render pass and framebuffers if not using profiles + if (!appInfo.profileSupported) + { + createRenderPass(); + createFramebuffers(); + } + + createColorResources(); + createDepthResources(); + } + + void createInstance() + { + constexpr vk::ApplicationInfo appInfo{ + .pApplicationName = "Vulkan Profiles Demo", + .applicationVersion = VK_MAKE_VERSION(1, 0, 0), + .pEngineName = "No Engine", + .engineVersion = VK_MAKE_VERSION(1, 0, 0), + .apiVersion = vk::ApiVersion14}; + + auto extensions = getRequiredExtensions(); + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledExtensionCount = static_cast(extensions.size()), + .ppEnabledExtensionNames = extensions.data()}; + + instance = vk::raii::Instance(context, createInfo); + } + + void setupDebugMessenger() + { + // Always set up the debug messenger + // It will only be used if validation layers are enabled via vulkanconfig + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( + vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | + vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | + vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( + vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | + vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | + vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + + try + { + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + } + catch (vk::SystemError &err) + { + // If the debug utils extension is not available, this will fail + // That's okay, it just means validation layers aren't enabled + std::cout << "Debug messenger not available. Validation layers may not be enabled." << std::endl; + } + } + + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + // Check if all required device extensions are available + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + return supportsGraphics && supportsAllRequiredExtensions; + }); + + if (devIter != devices.end()) + { + physicalDevice = *devIter; + msaaSamples = getMaxUsableSampleCount(); + + // Print device information + vk::PhysicalDeviceProperties deviceProperties = physicalDevice.getProperties(); + std::cout << "Selected GPU: " << deviceProperties.deviceName << std::endl; + std::cout << "API Version: " << VK_VERSION_MAJOR(deviceProperties.apiVersion) << "." + << VK_VERSION_MINOR(deviceProperties.apiVersion) << "." + << VK_VERSION_PATCH(deviceProperties.apiVersion) << std::endl; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void checkFeatureSupport() + { + // Define the KHR roadmap 2022 profile - more widely supported than 2024 + appInfo.profile = { + VP_KHR_ROADMAP_2022_NAME, + VP_KHR_ROADMAP_2022_SPEC_VERSION}; + + // Check if the profile is supported + VkBool32 supported = VK_FALSE; + VkResult result = vpGetPhysicalDeviceProfileSupport( *instance, *physicalDevice, &appInfo.profile, - &supported - ); - - if (result == VK_SUCCESS && supported == VK_TRUE) { - appInfo.profileSupported = true; - std::cout << "Using KHR roadmap 2022 profile" << std::endl; - } else { - appInfo.profileSupported = false; - std::cout << "Falling back to traditional rendering (profile not supported)" << std::endl; - - // If we wanted to implement fallback, we would call detectFeatureSupport() here - // But for this example, we'll just use traditional rendering if the profile isn't supported - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - float queuePriority = 1.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - - if (appInfo.profileSupported) { - // Create device with Best Practices profile - - // Enable required features - vk::PhysicalDeviceFeatures2 features2; - vk::PhysicalDeviceFeatures deviceFeatures{}; - deviceFeatures.samplerAnisotropy = VK_TRUE; - deviceFeatures.sampleRateShading = VK_TRUE; - features2.features = deviceFeatures; - - // Enable dynamic rendering - vk::PhysicalDeviceDynamicRenderingFeatures dynamicRenderingFeatures; - dynamicRenderingFeatures.dynamicRendering = VK_TRUE; - features2.pNext = &dynamicRenderingFeatures; - - // Create a vk::DeviceCreateInfo with the required features - vk::DeviceCreateInfo vkDeviceCreateInfo{ - .pNext = &features2, - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() - }; - - // Create the device with the vk::DeviceCreateInfo - device = vk::raii::Device(physicalDevice, vkDeviceCreateInfo); - - std::cout << "Created logical device using KHR roadmap 2022 profile" << std::endl; - } else { - // Fallback to manual device creation - vk::PhysicalDeviceFeatures deviceFeatures{}; - deviceFeatures.samplerAnisotropy = VK_TRUE; - deviceFeatures.sampleRateShading = VK_TRUE; - - vk::DeviceCreateInfo createInfo{ - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data(), - .pEnabledFeatures = &deviceFeatures - }; - - device = vk::raii::Device(physicalDevice, createInfo); - - std::cout << "Created logical device using manual feature selection" << std::endl; - } - - queue = device.getQueue(queueIndex, 0); - } - - void createSwapChain() { - SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); - swapChainExtent = chooseSwapExtent( swapChainSupport.capabilities ); - swapChainSurfaceFormat = chooseSwapSurfaceFormat( swapChainSupport.formats ); - vk::SwapchainCreateInfoKHR swapChainCreateInfo{ .surface = *surface, - .minImageCount = chooseSwapMinImageCount( swapChainSupport.capabilities ), - .imageFormat = swapChainSurfaceFormat.format, - .imageColorSpace = swapChainSurfaceFormat.colorSpace, - .imageExtent = swapChainExtent, - .imageArrayLayers = 1, - .imageUsage = vk::ImageUsageFlagBits::eColorAttachment, - .imageSharingMode = vk::SharingMode::eExclusive, - .preTransform = swapChainSupport.capabilities.currentTransform, - .compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque, - .presentMode = chooseSwapPresentMode( swapChainSupport.presentModes ), - .clipped = true }; - - swapChain = device.createSwapchainKHR(swapChainCreateInfo); - swapChainImages = swapChain.getImages(); - } - - void createImageViews() { - swapChainImageViews.reserve(swapChainImages.size()); - - for (const auto& image : swapChainImages) { - swapChainImageViews.push_back(createImageView(image, swapChainSurfaceFormat.format, vk::ImageAspectFlagBits::eColor, 1)); - } - } - - void createRenderPass() { - // This is only called if the Best Practices profile is not supported - // or if dynamic rendering is not available - vk::AttachmentDescription colorAttachment{ - .format = swapChainSurfaceFormat.format, - .samples = msaaSamples, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .stencilLoadOp = vk::AttachmentLoadOp::eDontCare, - .stencilStoreOp = vk::AttachmentStoreOp::eDontCare, - .initialLayout = vk::ImageLayout::eUndefined, - .finalLayout = vk::ImageLayout::eColorAttachmentOptimal - }; - - vk::AttachmentDescription depthAttachment{ - .format = findDepthFormat(), - .samples = msaaSamples, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eDontCare, - .stencilLoadOp = vk::AttachmentLoadOp::eDontCare, - .stencilStoreOp = vk::AttachmentStoreOp::eDontCare, - .initialLayout = vk::ImageLayout::eUndefined, - .finalLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal - }; - - vk::AttachmentDescription colorAttachmentResolve{ - .format = swapChainSurfaceFormat.format, - .samples = vk::SampleCountFlagBits::e1, - .loadOp = vk::AttachmentLoadOp::eDontCare, - .storeOp = vk::AttachmentStoreOp::eStore, - .stencilLoadOp = vk::AttachmentLoadOp::eDontCare, - .stencilStoreOp = vk::AttachmentStoreOp::eDontCare, - .initialLayout = vk::ImageLayout::eUndefined, - .finalLayout = vk::ImageLayout::ePresentSrcKHR - }; - - vk::AttachmentReference colorAttachmentRef{ - .attachment = 0, - .layout = vk::ImageLayout::eColorAttachmentOptimal - }; - - vk::AttachmentReference depthAttachmentRef{ - .attachment = 1, - .layout = vk::ImageLayout::eDepthStencilAttachmentOptimal - }; - - vk::AttachmentReference colorAttachmentResolveRef{ - .attachment = 2, - .layout = vk::ImageLayout::eColorAttachmentOptimal - }; - - vk::SubpassDescription subpass{ - .pipelineBindPoint = vk::PipelineBindPoint::eGraphics, - .colorAttachmentCount = 1, - .pColorAttachments = &colorAttachmentRef, - .pResolveAttachments = &colorAttachmentResolveRef, - .pDepthStencilAttachment = &depthAttachmentRef - }; - - vk::SubpassDependency dependency{ - .srcSubpass = VK_SUBPASS_EXTERNAL, - .dstSubpass = 0, - .srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eEarlyFragmentTests, - .dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eEarlyFragmentTests, - .srcAccessMask = vk::AccessFlagBits::eNone, - .dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite | vk::AccessFlagBits::eDepthStencilAttachmentWrite - }; - - std::array attachments = {colorAttachment, depthAttachment, colorAttachmentResolve}; - vk::RenderPassCreateInfo renderPassInfo{ - .attachmentCount = static_cast(attachments.size()), - .pAttachments = attachments.data(), - .subpassCount = 1, - .pSubpasses = &subpass, - .dependencyCount = 1, - .pDependencies = &dependency - }; - - renderPass = device.createRenderPass(renderPassInfo); - } - - void createDescriptorSetLayout() { - vk::DescriptorSetLayoutBinding uboLayoutBinding{ - .binding = 0, - .descriptorType = vk::DescriptorType::eUniformBuffer, - .descriptorCount = 1, - .stageFlags = vk::ShaderStageFlagBits::eVertex - }; - - vk::DescriptorSetLayoutBinding samplerLayoutBinding{ - .binding = 1, - .descriptorType = vk::DescriptorType::eCombinedImageSampler, - .descriptorCount = 1, - .stageFlags = vk::ShaderStageFlagBits::eFragment - }; - - std::array bindings = {uboLayoutBinding, samplerLayoutBinding}; - vk::DescriptorSetLayoutCreateInfo layoutInfo{ - .bindingCount = static_cast(bindings.size()), - .pBindings = bindings.data() - }; - - descriptorSetLayout = device.createDescriptorSetLayout(layoutInfo); - } - - void createGraphicsPipeline() { - auto vertShaderCode = readFile("shaders/vert.spv"); - auto fragShaderCode = readFile("shaders/frag.spv"); - - vk::raii::ShaderModule vertShaderModule = createShaderModule(vertShaderCode); - vk::raii::ShaderModule fragShaderModule = createShaderModule(fragShaderCode); - - vk::PipelineShaderStageCreateInfo vertShaderStageInfo{ - .stage = vk::ShaderStageFlagBits::eVertex, - .module = *vertShaderModule, - .pName = "main" - }; - - vk::PipelineShaderStageCreateInfo fragShaderStageInfo{ - .stage = vk::ShaderStageFlagBits::eFragment, - .module = *fragShaderModule, - .pName = "main" - }; - - vk::PipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; - - auto bindingDescription = Vertex::getBindingDescription(); - auto attributeDescriptions = Vertex::getAttributeDescriptions(); - - vk::PipelineVertexInputStateCreateInfo vertexInputInfo{ - .vertexBindingDescriptionCount = 1, - .pVertexBindingDescriptions = &bindingDescription, - .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), - .pVertexAttributeDescriptions = attributeDescriptions.data() - }; - - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ - .topology = vk::PrimitiveTopology::eTriangleList, - .primitiveRestartEnable = VK_FALSE - }; - - vk::PipelineViewportStateCreateInfo viewportState{ - .viewportCount = 1, - .scissorCount = 1 - }; - - vk::PipelineRasterizationStateCreateInfo rasterizer{ - .depthClampEnable = VK_FALSE, - .rasterizerDiscardEnable = VK_FALSE, - .polygonMode = vk::PolygonMode::eFill, - .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eCounterClockwise, - .depthBiasEnable = VK_FALSE, - .lineWidth = 1.0f - }; - - vk::PipelineMultisampleStateCreateInfo multisampling{ - .rasterizationSamples = msaaSamples, - .sampleShadingEnable = VK_TRUE, - .minSampleShading = 0.2f - }; - - vk::PipelineDepthStencilStateCreateInfo depthStencil{ - .depthTestEnable = VK_TRUE, - .depthWriteEnable = VK_TRUE, - .depthCompareOp = vk::CompareOp::eLess, - .depthBoundsTestEnable = VK_FALSE, - .stencilTestEnable = VK_FALSE - }; - - vk::PipelineColorBlendAttachmentState colorBlendAttachment{ - .blendEnable = VK_FALSE, - .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA - }; - - vk::PipelineColorBlendStateCreateInfo colorBlending{ - .logicOpEnable = VK_FALSE, - .logicOp = vk::LogicOp::eCopy, - .attachmentCount = 1, - .pAttachments = &colorBlendAttachment - }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - - vk::PipelineDynamicStateCreateInfo dynamicState{ - .dynamicStateCount = static_cast(dynamicStates.size()), - .pDynamicStates = dynamicStates.data() - }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ - .setLayoutCount = 1, - .pSetLayouts = &*descriptorSetLayout - }; - - pipelineLayout = device.createPipelineLayout(pipelineLayoutInfo); - - vk::GraphicsPipelineCreateInfo pipelineInfo{ - .stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pDepthStencilState = &depthStencil, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = *pipelineLayout - }; - - // Configure pipeline based on whether we're using the KHR roadmap 2022 profile - if (appInfo.profileSupported) { - // With the KHR roadmap 2022 profile, we can use dynamic rendering - vk::Format colorFormat = swapChainSurfaceFormat.format; - vk::Format depthFormat = findDepthFormat(); - - vk::PipelineRenderingCreateInfo renderingInfo{ - .colorAttachmentCount = 1, - .pColorAttachmentFormats = &colorFormat, - .depthAttachmentFormat = depthFormat - }; - - pipelineInfo.pNext = &renderingInfo; - pipelineInfo.renderPass = nullptr; - - std::cout << "Creating pipeline with dynamic rendering (KHR roadmap 2022 profile)" << std::endl; - } else { - // Without the profile, use traditional render pass if dynamic rendering is not available - pipelineInfo.pNext = nullptr; - pipelineInfo.renderPass = *renderPass; - pipelineInfo.subpass = 0; - - std::cout << "Creating pipeline with traditional render pass (fallback)" << std::endl; - } - - graphicsPipeline = device.createGraphicsPipeline(nullptr, pipelineInfo); - } - - void createFramebuffers() { - // This is only called if the Best Practices profile is not supported - // or if dynamic rendering is not available - swapChainFramebuffers.reserve(swapChainImageViews.size()); - - for (size_t i = 0; i < swapChainImageViews.size(); i++) { - std::array attachments = { - *colorImageView, - *depthImageView, - *swapChainImageViews[i] - }; - - vk::FramebufferCreateInfo framebufferInfo{ - .renderPass = *renderPass, - .attachmentCount = static_cast(attachments.size()), - .pAttachments = attachments.data(), - .width = swapChainExtent.width, - .height = swapChainExtent.height, - .layers = 1 - }; - - swapChainFramebuffers.push_back(device.createFramebuffer(framebufferInfo)); - } - } - - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{ - .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex - }; - - commandPool = device.createCommandPool(poolInfo); - } - - void createColorResources() { - vk::Format colorFormat = swapChainSurfaceFormat.format; - - createImage(swapChainExtent.width, swapChainExtent.height, 1, msaaSamples, colorFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransientAttachment | vk::ImageUsageFlagBits::eColorAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, colorImage, colorImageMemory); - colorImageView = createImageView(*colorImage, colorFormat, vk::ImageAspectFlagBits::eColor, 1); - } - - void createDepthResources() { - vk::Format depthFormat = findDepthFormat(); - - createImage(swapChainExtent.width, swapChainExtent.height, 1, msaaSamples, depthFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eDepthStencilAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, depthImage, depthImageMemory); - depthImageView = createImageView(*depthImage, depthFormat, vk::ImageAspectFlagBits::eDepth, 1); - } - - vk::Format findSupportedFormat(const std::vector& candidates, vk::ImageTiling tiling, vk::FormatFeatureFlags features) { - for (vk::Format format : candidates) { - vk::FormatProperties props = physicalDevice.getFormatProperties(format); - - if (tiling == vk::ImageTiling::eLinear && (props.linearTilingFeatures & features) == features) { - return format; - } else if (tiling == vk::ImageTiling::eOptimal && (props.optimalTilingFeatures & features) == features) { - return format; - } - } - - throw std::runtime_error("failed to find supported format!"); - } - - vk::Format findDepthFormat() { - return findSupportedFormat( - {vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, vk::Format::eD24UnormS8Uint}, - vk::ImageTiling::eOptimal, - vk::FormatFeatureFlagBits::eDepthStencilAttachment - ); - } - - bool hasStencilComponent(vk::Format format) { - return format == vk::Format::eD32SfloatS8Uint || format == vk::Format::eD24UnormS8Uint; - } - - void createTextureImage() { - int texWidth, texHeight, texChannels; - stbi_uc* pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); - vk::DeviceSize imageSize = texWidth * texHeight * 4; - uint32_t mipLevels = static_cast(std::floor(std::log2(std::max(texWidth, texHeight)))) + 1; - - if (!pixels) { - throw std::runtime_error("failed to load texture image!"); - } - - vk::raii::Buffer stagingBuffer = nullptr; - vk::raii::DeviceMemory stagingBufferMemory = nullptr; - - createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, imageSize); - memcpy(data, pixels, static_cast(imageSize)); - stagingBufferMemory.unmapMemory(); - - stbi_image_free(pixels); - - createImage(texWidth, texHeight, mipLevels, vk::SampleCountFlagBits::e1, vk::Format::eR8G8B8A8Srgb, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory); - - transitionImageLayout(*textureImage, vk::Format::eR8G8B8A8Srgb, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, mipLevels); - copyBufferToImage(*stagingBuffer, *textureImage, static_cast(texWidth), static_cast(texHeight)); - - generateMipmaps(*textureImage, vk::Format::eR8G8B8A8Srgb, texWidth, texHeight, mipLevels); - } - - void generateMipmaps(vk::Image image, vk::Format imageFormat, int32_t texWidth, int32_t texHeight, uint32_t mipLevels) { - vk::FormatProperties formatProperties = physicalDevice.getFormatProperties(imageFormat); - - if (!(formatProperties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eSampledImageFilterLinear)) { - throw std::runtime_error("texture image format does not support linear blitting!"); - } - - vk::raii::CommandBuffer commandBuffer = beginSingleTimeCommands(); - - vk::ImageMemoryBarrier barrier{ - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = image, - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1, - } - }; - - int32_t mipWidth = texWidth; - int32_t mipHeight = texHeight; - - for (uint32_t i = 1; i < mipLevels; i++) { - barrier.subresourceRange.baseMipLevel = i - 1; - barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal; - barrier.newLayout = vk::ImageLayout::eTransferSrcOptimal; - barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; - barrier.dstAccessMask = vk::AccessFlagBits::eTransferRead; - - commandBuffer.pipelineBarrier( - vk::PipelineStageFlagBits::eTransfer, - vk::PipelineStageFlagBits::eTransfer, - {}, - std::array{}, - std::array{}, - std::array{barrier}); - - vk::ImageBlit blit{ - .srcSubresource = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .mipLevel = i - 1, - .baseArrayLayer = 0, - .layerCount = 1 - }, - .srcOffsets = std::array{ - vk::Offset3D{0, 0, 0}, - vk::Offset3D{mipWidth, mipHeight, 1} - }, - .dstSubresource = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .mipLevel = i, - .baseArrayLayer = 0, - .layerCount = 1 - }, - .dstOffsets = std::array{ - vk::Offset3D{0, 0, 0}, - vk::Offset3D{mipWidth > 1 ? mipWidth / 2 : 1, mipHeight > 1 ? mipHeight / 2 : 1, 1} - } - }; - - commandBuffer.blitImage( - image, vk::ImageLayout::eTransferSrcOptimal, - image, vk::ImageLayout::eTransferDstOptimal, - std::array{blit}, - vk::Filter::eLinear); - - barrier.oldLayout = vk::ImageLayout::eTransferSrcOptimal; - barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal; - barrier.srcAccessMask = vk::AccessFlagBits::eTransferRead; - barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - - commandBuffer.pipelineBarrier( - vk::PipelineStageFlagBits::eTransfer, - vk::PipelineStageFlagBits::eFragmentShader, - {}, - std::array{}, - std::array{}, - std::array{barrier}); - - if (mipWidth > 1) mipWidth /= 2; - if (mipHeight > 1) mipHeight /= 2; - } - - barrier.subresourceRange.baseMipLevel = mipLevels - 1; - barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal; - barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal; - barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; - barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - - commandBuffer.pipelineBarrier( - vk::PipelineStageFlagBits::eTransfer, - vk::PipelineStageFlagBits::eFragmentShader, - {}, - std::array{}, - std::array{}, - std::array{barrier}); - - endSingleTimeCommands(commandBuffer); - } - - vk::raii::ImageView createImageView(vk::Image image, vk::Format format, vk::ImageAspectFlags aspectFlags, uint32_t mipLevels) { - vk::ImageViewCreateInfo viewInfo{ - .image = image, - .viewType = vk::ImageViewType::e2D, - .format = format, - .subresourceRange = { - .aspectMask = aspectFlags, - .baseMipLevel = 0, - .levelCount = mipLevels, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - - return device.createImageView(viewInfo); - } - - void createTextureImageView() { - textureImageView = createImageView(*textureImage, vk::Format::eR8G8B8A8Srgb, vk::ImageAspectFlagBits::eColor, 1); - } - - void createTextureSampler() { - vk::PhysicalDeviceProperties properties = physicalDevice.getProperties(); - - vk::SamplerCreateInfo samplerInfo{ - .magFilter = vk::Filter::eLinear, - .minFilter = vk::Filter::eLinear, - .mipmapMode = vk::SamplerMipmapMode::eLinear, - .addressModeU = vk::SamplerAddressMode::eRepeat, - .addressModeV = vk::SamplerAddressMode::eRepeat, - .addressModeW = vk::SamplerAddressMode::eRepeat, - .mipLodBias = 0.0f, - .anisotropyEnable = VK_TRUE, - .maxAnisotropy = properties.limits.maxSamplerAnisotropy, - .compareEnable = VK_FALSE, - .compareOp = vk::CompareOp::eAlways, - .minLod = 0.0f, - .maxLod = 0.0f, - .borderColor = vk::BorderColor::eIntOpaqueBlack, - .unnormalizedCoordinates = VK_FALSE - }; - - textureSampler = device.createSampler(samplerInfo); - } - - void createVertexBuffer() { - vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); - - vk::raii::Buffer stagingBuffer = nullptr; - vk::raii::DeviceMemory stagingBufferMemory = nullptr; - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(data, vertices.data(), (size_t) bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); - - copyBuffer(*stagingBuffer, *vertexBuffer, bufferSize); - } - - void createIndexBuffer() { - vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); - - vk::raii::Buffer stagingBuffer = nullptr; - vk::raii::DeviceMemory stagingBufferMemory = nullptr; - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(data, indices.data(), (size_t) bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); - - copyBuffer(*stagingBuffer, *indexBuffer, bufferSize); - } - - void createUniformBuffers() { - vk::DeviceSize bufferSize = sizeof(UniformBufferObject); - - // Reserve space but don't resize, as RAII objects can't be default-constructed - uniformBuffers.reserve(MAX_FRAMES_IN_FLIGHT); - uniformBuffersMemory.reserve(MAX_FRAMES_IN_FLIGHT); - uniformBuffersMapped.resize(MAX_FRAMES_IN_FLIGHT); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::raii::Buffer buffer = nullptr; - vk::raii::DeviceMemory bufferMemory = nullptr; - createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMemory); - - uniformBuffers.push_back(std::move(buffer)); - uniformBuffersMemory.push_back(std::move(bufferMemory)); - uniformBuffersMapped[i] = uniformBuffersMemory[i].mapMemory(0, bufferSize); - } - } - - void createDescriptorPool() { - std::array poolSizes{}; - poolSizes[0].type = vk::DescriptorType::eUniformBuffer; - poolSizes[0].descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT); - poolSizes[1].type = vk::DescriptorType::eCombinedImageSampler; - poolSizes[1].descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT); - - vk::DescriptorPoolCreateInfo poolInfo{ - .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, - .maxSets = static_cast(MAX_FRAMES_IN_FLIGHT), - .poolSizeCount = static_cast(poolSizes.size()), - .pPoolSizes = poolSizes.data() - }; - - descriptorPool = device.createDescriptorPool(poolInfo); - } - - void createDescriptorSets() { - std::vector layouts(MAX_FRAMES_IN_FLIGHT, *descriptorSetLayout); - vk::DescriptorSetAllocateInfo allocInfo{ - .descriptorPool = *descriptorPool, - .descriptorSetCount = static_cast(MAX_FRAMES_IN_FLIGHT), - .pSetLayouts = layouts.data() - }; - - descriptorSets = device.allocateDescriptorSets(allocInfo); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DescriptorBufferInfo bufferInfo{ - .buffer = *uniformBuffers[i], - .offset = 0, - .range = sizeof(UniformBufferObject) - }; - - vk::DescriptorImageInfo imageInfo{ - .sampler = *textureSampler, - .imageView = *textureImageView, - .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal - }; - - std::array descriptorWrites{}; - - descriptorWrites[0].dstSet = *descriptorSets[i]; - descriptorWrites[0].dstBinding = 0; - descriptorWrites[0].dstArrayElement = 0; - descriptorWrites[0].descriptorType = vk::DescriptorType::eUniformBuffer; - descriptorWrites[0].descriptorCount = 1; - descriptorWrites[0].pBufferInfo = &bufferInfo; - - descriptorWrites[1].dstSet = *descriptorSets[i]; - descriptorWrites[1].dstBinding = 1; - descriptorWrites[1].dstArrayElement = 0; - descriptorWrites[1].descriptorType = vk::DescriptorType::eCombinedImageSampler; - descriptorWrites[1].descriptorCount = 1; - descriptorWrites[1].pImageInfo = &imageInfo; - - device.updateDescriptorSets(descriptorWrites, nullptr); - } - } - - void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer& buffer, vk::raii::DeviceMemory& bufferMemory) { - vk::BufferCreateInfo bufferInfo{ - .size = size, - .usage = usage, - .sharingMode = vk::SharingMode::eExclusive - }; - - buffer = device.createBuffer(bufferInfo); - - vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); - - vk::MemoryAllocateInfo allocInfo{ - .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) - }; - - bufferMemory = device.allocateMemory(allocInfo); - buffer.bindMemory(*bufferMemory, 0); - } - - void copyBuffer(vk::Buffer srcBuffer, vk::Buffer dstBuffer, vk::DeviceSize size) { - vk::raii::CommandBuffer commandBuffer = beginSingleTimeCommands(); - - vk::BufferCopy copyRegion{ - .size = size - }; - commandBuffer.copyBuffer(srcBuffer, dstBuffer, copyRegion); - - endSingleTimeCommands(commandBuffer); - } - - void copyBufferToImage(vk::Buffer buffer, vk::Image image, uint32_t width, uint32_t height) { - vk::raii::CommandBuffer commandBuffer = beginSingleTimeCommands(); - - vk::BufferImageCopy region{ - .bufferOffset = 0, - .bufferRowLength = 0, - .bufferImageHeight = 0, - .imageSubresource = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .mipLevel = 0, - .baseArrayLayer = 0, - .layerCount = 1 - }, - .imageOffset = {0, 0, 0}, - .imageExtent = { - width, - height, - 1 - } - }; - - commandBuffer.copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, region); - - endSingleTimeCommands(commandBuffer); - } - - void createImage(uint32_t width, uint32_t height, uint32_t mipLevels, vk::SampleCountFlagBits numSamples, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image& image, vk::raii::DeviceMemory& imageMemory) { - vk::ImageCreateInfo imageInfo{ - .imageType = vk::ImageType::e2D, - .format = format, - .extent = { - .width = width, - .height = height, - .depth = 1 - }, - .mipLevels = mipLevels, - .arrayLayers = 1, - .samples = numSamples, - .tiling = tiling, - .usage = usage, - .sharingMode = vk::SharingMode::eExclusive, - .initialLayout = vk::ImageLayout::eUndefined - }; - - image = device.createImage(imageInfo); - - vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); - - vk::MemoryAllocateInfo allocInfo{ - .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) - }; - - imageMemory = device.allocateMemory(allocInfo); - image.bindMemory(*imageMemory, 0); - } - - void transitionImageLayout(vk::Image image, vk::Format format, vk::ImageLayout oldLayout, vk::ImageLayout newLayout, uint32_t mipLevels) { - vk::raii::CommandBuffer commandBuffer = beginSingleTimeCommands(); - - vk::ImageMemoryBarrier barrier{ - .oldLayout = oldLayout, - .newLayout = newLayout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = image, - .subresourceRange = { - .baseMipLevel = 0, - .levelCount = mipLevels, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - - if (newLayout == vk::ImageLayout::eDepthStencilAttachmentOptimal) { - barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eDepth; - - if (hasStencilComponent(format)) { - barrier.subresourceRange.aspectMask |= vk::ImageAspectFlagBits::eStencil; - } - } else { - barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; - } - - vk::PipelineStageFlags sourceStage; - vk::PipelineStageFlags destinationStage; - - if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) { - barrier.srcAccessMask = vk::AccessFlagBits::eNone; - barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; - - sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; - destinationStage = vk::PipelineStageFlagBits::eTransfer; - } else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) { - barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; - barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - - sourceStage = vk::PipelineStageFlagBits::eTransfer; - destinationStage = vk::PipelineStageFlagBits::eFragmentShader; - } else if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eDepthStencilAttachmentOptimal) { - barrier.srcAccessMask = vk::AccessFlagBits::eNone; - barrier.dstAccessMask = vk::AccessFlagBits::eDepthStencilAttachmentRead | vk::AccessFlagBits::eDepthStencilAttachmentWrite; - - sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; - destinationStage = vk::PipelineStageFlagBits::eEarlyFragmentTests; - } else { - throw std::invalid_argument("unsupported layout transition!"); - } - - commandBuffer.pipelineBarrier( - sourceStage, - destinationStage, - {}, - std::array{}, - std::array{}, - std::array{barrier} - ); - - endSingleTimeCommands(commandBuffer); - } - - vk::raii::CommandBuffer beginSingleTimeCommands() { - vk::CommandBufferAllocateInfo allocInfo{ - .commandPool = *commandPool, - .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = 1 - }; - - vk::raii::CommandBuffer commandBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); - - vk::CommandBufferBeginInfo beginInfo{ - .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit - }; - - commandBuffer.begin(beginInfo); - - return commandBuffer; - } - - void endSingleTimeCommands(vk::raii::CommandBuffer& commandBuffer) { - commandBuffer.end(); - - vk::SubmitInfo submitInfo{ - .commandBufferCount = 1, - .pCommandBuffers = &*commandBuffer - }; - - queue.submit(submitInfo, nullptr); - queue.waitIdle(); - } - - void loadModel() { - tinyobj::attrib_t attrib; - std::vector shapes; - std::vector materials; - std::string warn, err; - - if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, MODEL_PATH.c_str())) { - throw std::runtime_error(warn + err); - } - - std::unordered_map uniqueVertices{}; - - for (const auto& shape : shapes) { - for (const auto& index : shape.mesh.indices) { - Vertex vertex{}; - - vertex.pos = { - attrib.vertices[3 * index.vertex_index + 0], - attrib.vertices[3 * index.vertex_index + 1], - attrib.vertices[3 * index.vertex_index + 2] - }; - - vertex.texCoord = { - attrib.texcoords[2 * index.texcoord_index + 0], - 1.0f - attrib.texcoords[2 * index.texcoord_index + 1] - }; - - vertex.color = {1.0f, 1.0f, 1.0f}; - - if (uniqueVertices.count(vertex) == 0) { - uniqueVertices[vertex] = static_cast(vertices.size()); - vertices.push_back(vertex); - } - - indices.push_back(uniqueVertices[vertex]); - } - } - } - - void createCommandBuffers() { - commandBuffers.reserve(MAX_FRAMES_IN_FLIGHT); - - vk::CommandBufferAllocateInfo allocInfo{ - .commandPool = *commandPool, - .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = static_cast(MAX_FRAMES_IN_FLIGHT) - }; - - commandBuffers = device.allocateCommandBuffers(allocInfo); - } - - void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin({}); - - // Transition the swapchain image to the correct layout for rendering - vk::ImageMemoryBarrier imageBarrier{ - .srcAccessMask = vk::AccessFlagBits::eNone, - .dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite, - .oldLayout = vk::ImageLayout::eUndefined, - .newLayout = vk::ImageLayout::eColorAttachmentOptimal, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - - commandBuffers[currentFrame].pipelineBarrier( - vk::PipelineStageFlagBits::eTopOfPipe, - vk::PipelineStageFlagBits::eColorAttachmentOutput, - vk::DependencyFlagBits::eByRegion, - std::array{}, - std::array{}, - std::array{imageBarrier} - ); - - // Clear values for color and depth - vk::ClearValue clearColor{}; - clearColor.color = vk::ClearColorValue(std::array{0.0f, 0.0f, 0.0f, 1.0f}); - - vk::ClearValue clearDepth{}; - clearDepth.depthStencil = vk::ClearDepthStencilValue{1.0f, 0}; - - std::array clearValues = {clearColor, clearDepth}; - - // Use different rendering approach based on profile support - if (appInfo.profileSupported) { - // Use dynamic rendering with the KHR roadmap 2022 profile - vk::RenderingAttachmentInfo colorAttachment{ - .imageView = *colorImageView, - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .resolveMode = vk::ResolveModeFlagBits::eAverage, - .resolveImageView = *swapChainImageViews[imageIndex], - .resolveImageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor - }; - - vk::RenderingAttachmentInfo depthAttachment{ - .imageView = *depthImageView, - .imageLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eDontCare, - .clearValue = clearDepth - }; - - vk::RenderingInfo renderingInfo{ - .renderArea = {{0, 0}, swapChainExtent}, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &colorAttachment, - .pDepthAttachment = &depthAttachment - }; - - commandBuffers[currentFrame].beginRendering(renderingInfo); - - } else { - // Use traditional render pass if not using the KHR roadmap 2022 profile - vk::RenderPassBeginInfo renderPassInfo{ - .renderPass = *renderPass, - .framebuffer = *swapChainFramebuffers[imageIndex], - .renderArea = {{0, 0}, swapChainExtent}, - .clearValueCount = static_cast(clearValues.size()), - .pClearValues = clearValues.data() - }; - - commandBuffers[currentFrame].beginRenderPass(renderPassInfo, vk::SubpassContents::eInline); - - } - - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - - vk::Viewport viewport{ - .x = 0.0f, - .y = 0.0f, - .width = static_cast(swapChainExtent.width), - .height = static_cast(swapChainExtent.height), - .minDepth = 0.0f, - .maxDepth = 1.0f - }; - commandBuffers[currentFrame].setViewport(0, viewport); - - vk::Rect2D scissor{ - .offset = {0, 0}, - .extent = swapChainExtent - }; - commandBuffers[currentFrame].setScissor(0, scissor); - - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint32); - commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, *pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); - commandBuffers[currentFrame].drawIndexed(static_cast(indices.size()), 1, 0, 0, 0); - - if (appInfo.profileSupported) { - commandBuffers[currentFrame].endRendering(); - - // Transition the swapchain image to the correct layout for presentation - vk::ImageMemoryBarrier barrier{ - .srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite, - .dstAccessMask = vk::AccessFlagBits::eNone, - .oldLayout = vk::ImageLayout::eColorAttachmentOptimal, - .newLayout = vk::ImageLayout::ePresentSrcKHR, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - - commandBuffers[currentFrame].pipelineBarrier( - vk::PipelineStageFlagBits::eColorAttachmentOutput, - vk::PipelineStageFlagBits::eBottomOfPipe, - vk::DependencyFlagBits::eByRegion, - std::array{}, - std::array{}, - std::array{barrier} - ); - } else { - commandBuffers[currentFrame].endRenderPass(); - // Traditional render pass already transitions the image to the correct layout - } - - commandBuffers[currentFrame].end(); - } - - void createSyncObjects() { - imageAvailableSemaphores.reserve(MAX_FRAMES_IN_FLIGHT); - renderFinishedSemaphores.reserve(MAX_FRAMES_IN_FLIGHT); - inFlightFences.reserve(MAX_FRAMES_IN_FLIGHT); - presentCompleteSemaphore.reserve(swapChainImages.size()); - - vk::SemaphoreCreateInfo semaphoreInfo{}; - vk::FenceCreateInfo fenceInfo{ - .flags = vk::FenceCreateFlagBits::eSignaled - }; - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - imageAvailableSemaphores.push_back(device.createSemaphore(semaphoreInfo)); - renderFinishedSemaphores.push_back(device.createSemaphore(semaphoreInfo)); - inFlightFences.push_back(device.createFence(fenceInfo)); - } - - for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.push_back(device.createSemaphore(semaphoreInfo)); - } - } - - void updateUniformBuffer(uint32_t currentImage) { - static auto startTime = std::chrono::high_resolution_clock::now(); - - auto currentTime = std::chrono::high_resolution_clock::now(); - float time = std::chrono::duration(currentTime - startTime).count(); - - UniformBufferObject ubo{}; - ubo.model = glm::rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.proj = glm::perspective(glm::radians(45.0f), swapChainExtent.width / (float) swapChainExtent.height, 0.1f, 10.0f); - ubo.proj[1][1] *= -1; - - memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); - } - - void drawFrame() { - static_cast(device.waitForFences({*inFlightFences[currentFrame]}, VK_TRUE, FenceTimeout)); - - uint32_t imageIndex; - try { - auto [result, idx] = swapChain.acquireNextImage(FenceTimeout, *imageAvailableSemaphores[currentFrame]); - imageIndex = idx; - } catch (vk::OutOfDateKHRError&) { - recreateSwapChain(); - return; - } - - updateUniformBuffer(currentFrame); - - device.resetFences({*inFlightFences[currentFrame]}); - - commandBuffers[currentFrame].reset(); - recordCommandBuffer(imageIndex); - - vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); - const vk::SubmitInfo submitInfo{ - .waitSemaphoreCount = 1, - .pWaitSemaphores = &*imageAvailableSemaphores[currentFrame], - .pWaitDstStageMask = &waitDestinationStageMask, - .commandBufferCount = 1, - .pCommandBuffers = &*commandBuffers[currentFrame], - .signalSemaphoreCount = 1, - .pSignalSemaphores = &*presentCompleteSemaphore[imageIndex] - }; - queue.submit(submitInfo, *inFlightFences[currentFrame]); - - const vk::PresentInfoKHR presentInfoKHR{ - .waitSemaphoreCount = 1, - .pWaitSemaphores = &*presentCompleteSemaphore[imageIndex], - .swapchainCount = 1, - .pSwapchains = &*swapChain, - .pImageIndices = &imageIndex - }; - - vk::Result result; - try { - result = queue.presentKHR(presentInfoKHR); - } catch (vk::OutOfDateKHRError&) { - result = vk::Result::eErrorOutOfDateKHR; - } - - if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { - framebufferResized = false; - recreateSwapChain(); - } else if (result != vk::Result::eSuccess) { - throw std::runtime_error("failed to present swap chain image!"); - } - - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; - } - - vk::SampleCountFlagBits getMaxUsableSampleCount() { - vk::PhysicalDeviceProperties physicalDeviceProperties = physicalDevice.getProperties(); - - vk::SampleCountFlags counts = physicalDeviceProperties.limits.framebufferColorSampleCounts & physicalDeviceProperties.limits.framebufferDepthSampleCounts; - if (counts & vk::SampleCountFlagBits::e64) { return vk::SampleCountFlagBits::e64; } - if (counts & vk::SampleCountFlagBits::e32) { return vk::SampleCountFlagBits::e32; } - if (counts & vk::SampleCountFlagBits::e16) { return vk::SampleCountFlagBits::e16; } - if (counts & vk::SampleCountFlagBits::e8) { return vk::SampleCountFlagBits::e8; } - if (counts & vk::SampleCountFlagBits::e4) { return vk::SampleCountFlagBits::e4; } - if (counts & vk::SampleCountFlagBits::e2) { return vk::SampleCountFlagBits::e2; } - - return vk::SampleCountFlagBits::e1; - } - - uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) { - vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); - - for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { - if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { - return i; - } - } - - throw std::runtime_error("failed to find suitable memory type!"); - } - - static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const & surfaceCapabilities) { - auto minImageCount = std::max( 3u, surfaceCapabilities.minImageCount ); - if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) { - minImageCount = surfaceCapabilities.maxImageCount; - } - return minImageCount; - } - - static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { - assert(!availableFormats.empty()); - const auto formatIt = std::ranges::find_if( - availableFormats, - []( const auto & format ) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; } ); - return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; - } - - vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - assert(std::ranges::any_of(availablePresentModes, [](auto presentMode){ return presentMode == vk::PresentModeKHR::eFifo; })); - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eFifo; - } - - vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) { - if (capabilities.currentExtent.width != 0xFFFFFFFF) { - 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) - }; - } - - std::vector getRequiredExtensions() { - // Get the required extensions from GLFW - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - - // Check if the debug utils extension is available - std::vector props = context.enumerateInstanceExtensionProperties(); - bool debugUtilsAvailable = std::ranges::any_of(props, - [](vk::ExtensionProperties const & ep) { - return strcmp(ep.extensionName, vk::EXTDebugUtilsExtensionName) == 0; - }); - - // Always include the debug utils extension if available - // This allows validation layers to be enabled via vulkanconfig - if (debugUtilsAvailable) { - extensions.push_back(vk::EXTDebugUtilsExtensionName); - } else { - std::cout << "VK_EXT_debug_utils extension not available. Validation layers may not work." << std::endl; - } - - return extensions; - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - vk::raii::ShaderModule createShaderModule(const std::vector& code) { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size(), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - 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; - } - - SwapChainSupportDetails querySwapChainSupport(vk::raii::PhysicalDevice device) { - SwapChainSupportDetails details; - details.capabilities = device.getSurfaceCapabilitiesKHR(*surface); - details.formats = device.getSurfaceFormatsKHR(*surface); - details.presentModes = device.getSurfacePresentModesKHR(*surface); - - return details; - } + &supported); + + if (result == VK_SUCCESS && supported == VK_TRUE) + { + appInfo.profileSupported = true; + std::cout << "Using KHR roadmap 2022 profile" << std::endl; + } + else + { + appInfo.profileSupported = false; + std::cout << "Falling back to traditional rendering (profile not supported)" << std::endl; + + // If we wanted to implement fallback, we would call detectFeatureSupport() here + // But for this example, we'll just use traditional rendering if the profile isn't supported + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + float queuePriority = 1.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + + if (appInfo.profileSupported) + { + // Create device with Best Practices profile + + // Enable required features + vk::PhysicalDeviceFeatures2 features2; + vk::PhysicalDeviceFeatures deviceFeatures{}; + deviceFeatures.samplerAnisotropy = VK_TRUE; + deviceFeatures.sampleRateShading = VK_TRUE; + features2.features = deviceFeatures; + + // Enable dynamic rendering + vk::PhysicalDeviceDynamicRenderingFeatures dynamicRenderingFeatures; + dynamicRenderingFeatures.dynamicRendering = VK_TRUE; + features2.pNext = &dynamicRenderingFeatures; + + // Create a vk::DeviceCreateInfo with the required features + vk::DeviceCreateInfo vkDeviceCreateInfo{ + .pNext = &features2, + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + // Create the device with the vk::DeviceCreateInfo + device = vk::raii::Device(physicalDevice, vkDeviceCreateInfo); + + std::cout << "Created logical device using KHR roadmap 2022 profile" << std::endl; + } + else + { + // Fallback to manual device creation + vk::PhysicalDeviceFeatures deviceFeatures{}; + deviceFeatures.samplerAnisotropy = VK_TRUE; + deviceFeatures.sampleRateShading = VK_TRUE; + + vk::DeviceCreateInfo createInfo{ + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data(), + .pEnabledFeatures = &deviceFeatures}; + + device = vk::raii::Device(physicalDevice, createInfo); + + std::cout << "Created logical device using manual feature selection" << std::endl; + } + + queue = device.getQueue(queueIndex, 0); + } + + void createSwapChain() + { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + swapChainExtent = chooseSwapExtent(swapChainSupport.capabilities); + swapChainSurfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + vk::SwapchainCreateInfoKHR swapChainCreateInfo{.surface = *surface, + .minImageCount = chooseSwapMinImageCount(swapChainSupport.capabilities), + .imageFormat = swapChainSurfaceFormat.format, + .imageColorSpace = swapChainSurfaceFormat.colorSpace, + .imageExtent = swapChainExtent, + .imageArrayLayers = 1, + .imageUsage = vk::ImageUsageFlagBits::eColorAttachment, + .imageSharingMode = vk::SharingMode::eExclusive, + .preTransform = swapChainSupport.capabilities.currentTransform, + .compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque, + .presentMode = chooseSwapPresentMode(swapChainSupport.presentModes), + .clipped = true}; + + swapChain = device.createSwapchainKHR(swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + void createImageViews() + { + swapChainImageViews.reserve(swapChainImages.size()); + + for (const auto &image : swapChainImages) + { + swapChainImageViews.push_back(createImageView(image, swapChainSurfaceFormat.format, vk::ImageAspectFlagBits::eColor, 1)); + } + } + + void createRenderPass() + { + // This is only called if the Best Practices profile is not supported + // or if dynamic rendering is not available + vk::AttachmentDescription colorAttachment{ + .format = swapChainSurfaceFormat.format, + .samples = msaaSamples, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .stencilLoadOp = vk::AttachmentLoadOp::eDontCare, + .stencilStoreOp = vk::AttachmentStoreOp::eDontCare, + .initialLayout = vk::ImageLayout::eUndefined, + .finalLayout = vk::ImageLayout::eColorAttachmentOptimal}; + + vk::AttachmentDescription depthAttachment{ + .format = findDepthFormat(), + .samples = msaaSamples, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eDontCare, + .stencilLoadOp = vk::AttachmentLoadOp::eDontCare, + .stencilStoreOp = vk::AttachmentStoreOp::eDontCare, + .initialLayout = vk::ImageLayout::eUndefined, + .finalLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal}; + + vk::AttachmentDescription colorAttachmentResolve{ + .format = swapChainSurfaceFormat.format, + .samples = vk::SampleCountFlagBits::e1, + .loadOp = vk::AttachmentLoadOp::eDontCare, + .storeOp = vk::AttachmentStoreOp::eStore, + .stencilLoadOp = vk::AttachmentLoadOp::eDontCare, + .stencilStoreOp = vk::AttachmentStoreOp::eDontCare, + .initialLayout = vk::ImageLayout::eUndefined, + .finalLayout = vk::ImageLayout::ePresentSrcKHR}; + + vk::AttachmentReference colorAttachmentRef{ + .attachment = 0, + .layout = vk::ImageLayout::eColorAttachmentOptimal}; + + vk::AttachmentReference depthAttachmentRef{ + .attachment = 1, + .layout = vk::ImageLayout::eDepthStencilAttachmentOptimal}; + + vk::AttachmentReference colorAttachmentResolveRef{ + .attachment = 2, + .layout = vk::ImageLayout::eColorAttachmentOptimal}; + + vk::SubpassDescription subpass{ + .pipelineBindPoint = vk::PipelineBindPoint::eGraphics, + .colorAttachmentCount = 1, + .pColorAttachments = &colorAttachmentRef, + .pResolveAttachments = &colorAttachmentResolveRef, + .pDepthStencilAttachment = &depthAttachmentRef}; + + vk::SubpassDependency dependency{ + .srcSubpass = VK_SUBPASS_EXTERNAL, + .dstSubpass = 0, + .srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eEarlyFragmentTests, + .dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eEarlyFragmentTests, + .srcAccessMask = vk::AccessFlagBits::eNone, + .dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite | vk::AccessFlagBits::eDepthStencilAttachmentWrite}; + + std::array attachments = {colorAttachment, depthAttachment, colorAttachmentResolve}; + vk::RenderPassCreateInfo renderPassInfo{ + .attachmentCount = static_cast(attachments.size()), + .pAttachments = attachments.data(), + .subpassCount = 1, + .pSubpasses = &subpass, + .dependencyCount = 1, + .pDependencies = &dependency}; + + renderPass = device.createRenderPass(renderPassInfo); + } + + void createDescriptorSetLayout() + { + vk::DescriptorSetLayoutBinding uboLayoutBinding{ + .binding = 0, + .descriptorType = vk::DescriptorType::eUniformBuffer, + .descriptorCount = 1, + .stageFlags = vk::ShaderStageFlagBits::eVertex}; + + vk::DescriptorSetLayoutBinding samplerLayoutBinding{ + .binding = 1, + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .descriptorCount = 1, + .stageFlags = vk::ShaderStageFlagBits::eFragment}; + + std::array bindings = {uboLayoutBinding, samplerLayoutBinding}; + vk::DescriptorSetLayoutCreateInfo layoutInfo{ + .bindingCount = static_cast(bindings.size()), + .pBindings = bindings.data()}; + + descriptorSetLayout = device.createDescriptorSetLayout(layoutInfo); + } + + void createGraphicsPipeline() + { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); + + vk::raii::ShaderModule vertShaderModule = createShaderModule(vertShaderCode); + vk::raii::ShaderModule fragShaderModule = createShaderModule(fragShaderCode); + + vk::PipelineShaderStageCreateInfo vertShaderStageInfo{ + .stage = vk::ShaderStageFlagBits::eVertex, + .module = *vertShaderModule, + .pName = "main"}; + + vk::PipelineShaderStageCreateInfo fragShaderStageInfo{ + .stage = vk::ShaderStageFlagBits::eFragment, + .module = *fragShaderModule, + .pName = "main"}; + + vk::PipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; + + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{ + .vertexBindingDescriptionCount = 1, + .pVertexBindingDescriptions = &bindingDescription, + .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), + .pVertexAttributeDescriptions = attributeDescriptions.data()}; + + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ + .topology = vk::PrimitiveTopology::eTriangleList, + .primitiveRestartEnable = VK_FALSE}; + + vk::PipelineViewportStateCreateInfo viewportState{ + .viewportCount = 1, + .scissorCount = 1}; + + vk::PipelineRasterizationStateCreateInfo rasterizer{ + .depthClampEnable = VK_FALSE, + .rasterizerDiscardEnable = VK_FALSE, + .polygonMode = vk::PolygonMode::eFill, + .cullMode = vk::CullModeFlagBits::eBack, + .frontFace = vk::FrontFace::eCounterClockwise, + .depthBiasEnable = VK_FALSE, + .lineWidth = 1.0f}; + + vk::PipelineMultisampleStateCreateInfo multisampling{ + .rasterizationSamples = msaaSamples, + .sampleShadingEnable = VK_TRUE, + .minSampleShading = 0.2f}; + + vk::PipelineDepthStencilStateCreateInfo depthStencil{ + .depthTestEnable = VK_TRUE, + .depthWriteEnable = VK_TRUE, + .depthCompareOp = vk::CompareOp::eLess, + .depthBoundsTestEnable = VK_FALSE, + .stencilTestEnable = VK_FALSE}; + + vk::PipelineColorBlendAttachmentState colorBlendAttachment{ + .blendEnable = VK_FALSE, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA}; + + vk::PipelineColorBlendStateCreateInfo colorBlending{ + .logicOpEnable = VK_FALSE, + .logicOp = vk::LogicOp::eCopy, + .attachmentCount = 1, + .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + + vk::PipelineDynamicStateCreateInfo dynamicState{ + .dynamicStateCount = static_cast(dynamicStates.size()), + .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ + .setLayoutCount = 1, + .pSetLayouts = &*descriptorSetLayout}; + + pipelineLayout = device.createPipelineLayout(pipelineLayoutInfo); + + vk::GraphicsPipelineCreateInfo pipelineInfo{ + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pDepthStencilState = &depthStencil, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = *pipelineLayout}; + + // Configure pipeline based on whether we're using the KHR roadmap 2022 profile + if (appInfo.profileSupported) + { + // With the KHR roadmap 2022 profile, we can use dynamic rendering + vk::Format colorFormat = swapChainSurfaceFormat.format; + vk::Format depthFormat = findDepthFormat(); + + vk::PipelineRenderingCreateInfo renderingInfo{ + .colorAttachmentCount = 1, + .pColorAttachmentFormats = &colorFormat, + .depthAttachmentFormat = depthFormat}; + + pipelineInfo.pNext = &renderingInfo; + pipelineInfo.renderPass = nullptr; + + std::cout << "Creating pipeline with dynamic rendering (KHR roadmap 2022 profile)" << std::endl; + } + else + { + // Without the profile, use traditional render pass if dynamic rendering is not available + pipelineInfo.pNext = nullptr; + pipelineInfo.renderPass = *renderPass; + pipelineInfo.subpass = 0; + + std::cout << "Creating pipeline with traditional render pass (fallback)" << std::endl; + } + + graphicsPipeline = device.createGraphicsPipeline(nullptr, pipelineInfo); + } + + void createFramebuffers() + { + // This is only called if the Best Practices profile is not supported + // or if dynamic rendering is not available + swapChainFramebuffers.reserve(swapChainImageViews.size()); + + for (size_t i = 0; i < swapChainImageViews.size(); i++) + { + std::array attachments = { + *colorImageView, + *depthImageView, + *swapChainImageViews[i]}; + + vk::FramebufferCreateInfo framebufferInfo{ + .renderPass = *renderPass, + .attachmentCount = static_cast(attachments.size()), + .pAttachments = attachments.data(), + .width = swapChainExtent.width, + .height = swapChainExtent.height, + .layers = 1}; + + swapChainFramebuffers.push_back(device.createFramebuffer(framebufferInfo)); + } + } + + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{ + .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex}; + + commandPool = device.createCommandPool(poolInfo); + } + + void createColorResources() + { + vk::Format colorFormat = swapChainSurfaceFormat.format; + + createImage(swapChainExtent.width, swapChainExtent.height, 1, msaaSamples, colorFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransientAttachment | vk::ImageUsageFlagBits::eColorAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, colorImage, colorImageMemory); + colorImageView = createImageView(*colorImage, colorFormat, vk::ImageAspectFlagBits::eColor, 1); + } + + void createDepthResources() + { + vk::Format depthFormat = findDepthFormat(); + + createImage(swapChainExtent.width, swapChainExtent.height, 1, msaaSamples, depthFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eDepthStencilAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, depthImage, depthImageMemory); + depthImageView = createImageView(*depthImage, depthFormat, vk::ImageAspectFlagBits::eDepth, 1); + } + + vk::Format findSupportedFormat(const std::vector &candidates, vk::ImageTiling tiling, vk::FormatFeatureFlags features) + { + for (vk::Format format : candidates) + { + vk::FormatProperties props = physicalDevice.getFormatProperties(format); + + if (tiling == vk::ImageTiling::eLinear && (props.linearTilingFeatures & features) == features) + { + return format; + } + else if (tiling == vk::ImageTiling::eOptimal && (props.optimalTilingFeatures & features) == features) + { + return format; + } + } + + throw std::runtime_error("failed to find supported format!"); + } + + vk::Format findDepthFormat() + { + return findSupportedFormat( + {vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, vk::Format::eD24UnormS8Uint}, + vk::ImageTiling::eOptimal, + vk::FormatFeatureFlagBits::eDepthStencilAttachment); + } + + bool hasStencilComponent(vk::Format format) + { + return format == vk::Format::eD32SfloatS8Uint || format == vk::Format::eD24UnormS8Uint; + } + + void createTextureImage() + { + int texWidth, texHeight, texChannels; + stbi_uc *pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + vk::DeviceSize imageSize = texWidth * texHeight * 4; + uint32_t mipLevels = static_cast(std::floor(std::log2(std::max(texWidth, texHeight)))) + 1; + + if (!pixels) + { + throw std::runtime_error("failed to load texture image!"); + } + + vk::raii::Buffer stagingBuffer = nullptr; + vk::raii::DeviceMemory stagingBufferMemory = nullptr; + + createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, imageSize); + memcpy(data, pixels, static_cast(imageSize)); + stagingBufferMemory.unmapMemory(); + + stbi_image_free(pixels); + + createImage(texWidth, texHeight, mipLevels, vk::SampleCountFlagBits::e1, vk::Format::eR8G8B8A8Srgb, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory); + + transitionImageLayout(*textureImage, vk::Format::eR8G8B8A8Srgb, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, mipLevels); + copyBufferToImage(*stagingBuffer, *textureImage, static_cast(texWidth), static_cast(texHeight)); + + generateMipmaps(*textureImage, vk::Format::eR8G8B8A8Srgb, texWidth, texHeight, mipLevels); + } + + void generateMipmaps(vk::Image image, vk::Format imageFormat, int32_t texWidth, int32_t texHeight, uint32_t mipLevels) + { + vk::FormatProperties formatProperties = physicalDevice.getFormatProperties(imageFormat); + + if (!(formatProperties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eSampledImageFilterLinear)) + { + throw std::runtime_error("texture image format does not support linear blitting!"); + } + + vk::raii::CommandBuffer commandBuffer = beginSingleTimeCommands(); + + vk::ImageMemoryBarrier barrier{ + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = image, + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }}; + + int32_t mipWidth = texWidth; + int32_t mipHeight = texHeight; + + for (uint32_t i = 1; i < mipLevels; i++) + { + barrier.subresourceRange.baseMipLevel = i - 1; + barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal; + barrier.newLayout = vk::ImageLayout::eTransferSrcOptimal; + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eTransferRead; + + commandBuffer.pipelineBarrier( + vk::PipelineStageFlagBits::eTransfer, + vk::PipelineStageFlagBits::eTransfer, + {}, + std::array{}, + std::array{}, + std::array{barrier}); + + vk::ImageBlit blit{ + .srcSubresource = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .mipLevel = i - 1, + .baseArrayLayer = 0, + .layerCount = 1}, + .srcOffsets = std::array{vk::Offset3D{0, 0, 0}, vk::Offset3D{mipWidth, mipHeight, 1}}, + .dstSubresource = {.aspectMask = vk::ImageAspectFlagBits::eColor, .mipLevel = i, .baseArrayLayer = 0, .layerCount = 1}, + .dstOffsets = std::array{vk::Offset3D{0, 0, 0}, vk::Offset3D{mipWidth > 1 ? mipWidth / 2 : 1, mipHeight > 1 ? mipHeight / 2 : 1, 1}}}; + + commandBuffer.blitImage( + image, vk::ImageLayout::eTransferSrcOptimal, + image, vk::ImageLayout::eTransferDstOptimal, + std::array{blit}, + vk::Filter::eLinear); + + barrier.oldLayout = vk::ImageLayout::eTransferSrcOptimal; + barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal; + barrier.srcAccessMask = vk::AccessFlagBits::eTransferRead; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + + commandBuffer.pipelineBarrier( + vk::PipelineStageFlagBits::eTransfer, + vk::PipelineStageFlagBits::eFragmentShader, + {}, + std::array{}, + std::array{}, + std::array{barrier}); + + if (mipWidth > 1) + mipWidth /= 2; + if (mipHeight > 1) + mipHeight /= 2; + } + + barrier.subresourceRange.baseMipLevel = mipLevels - 1; + barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal; + barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal; + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + + commandBuffer.pipelineBarrier( + vk::PipelineStageFlagBits::eTransfer, + vk::PipelineStageFlagBits::eFragmentShader, + {}, + std::array{}, + std::array{}, + std::array{barrier}); + + endSingleTimeCommands(commandBuffer); + } + + vk::raii::ImageView createImageView(vk::Image image, vk::Format format, vk::ImageAspectFlags aspectFlags, uint32_t mipLevels) + { + vk::ImageViewCreateInfo viewInfo{ + .image = image, + .viewType = vk::ImageViewType::e2D, + .format = format, + .subresourceRange = { + .aspectMask = aspectFlags, + .baseMipLevel = 0, + .levelCount = mipLevels, + .baseArrayLayer = 0, + .layerCount = 1}}; + + return device.createImageView(viewInfo); + } + + void createTextureImageView() + { + textureImageView = createImageView(*textureImage, vk::Format::eR8G8B8A8Srgb, vk::ImageAspectFlagBits::eColor, 1); + } + + void createTextureSampler() + { + vk::PhysicalDeviceProperties properties = physicalDevice.getProperties(); + + vk::SamplerCreateInfo samplerInfo{ + .magFilter = vk::Filter::eLinear, + .minFilter = vk::Filter::eLinear, + .mipmapMode = vk::SamplerMipmapMode::eLinear, + .addressModeU = vk::SamplerAddressMode::eRepeat, + .addressModeV = vk::SamplerAddressMode::eRepeat, + .addressModeW = vk::SamplerAddressMode::eRepeat, + .mipLodBias = 0.0f, + .anisotropyEnable = VK_TRUE, + .maxAnisotropy = properties.limits.maxSamplerAnisotropy, + .compareEnable = VK_FALSE, + .compareOp = vk::CompareOp::eAlways, + .minLod = 0.0f, + .maxLod = 0.0f, + .borderColor = vk::BorderColor::eIntOpaqueBlack, + .unnormalizedCoordinates = VK_FALSE}; + + textureSampler = device.createSampler(samplerInfo); + } + + void createVertexBuffer() + { + vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + + vk::raii::Buffer stagingBuffer = nullptr; + vk::raii::DeviceMemory stagingBufferMemory = nullptr; + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(data, vertices.data(), (size_t) bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); + + copyBuffer(*stagingBuffer, *vertexBuffer, bufferSize); + } + + void createIndexBuffer() + { + vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + + vk::raii::Buffer stagingBuffer = nullptr; + vk::raii::DeviceMemory stagingBufferMemory = nullptr; + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(data, indices.data(), (size_t) bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); + + copyBuffer(*stagingBuffer, *indexBuffer, bufferSize); + } + + void createUniformBuffers() + { + vk::DeviceSize bufferSize = sizeof(UniformBufferObject); + + // Reserve space but don't resize, as RAII objects can't be default-constructed + uniformBuffers.reserve(MAX_FRAMES_IN_FLIGHT); + uniformBuffersMemory.reserve(MAX_FRAMES_IN_FLIGHT); + uniformBuffersMapped.resize(MAX_FRAMES_IN_FLIGHT); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::raii::Buffer buffer = nullptr; + vk::raii::DeviceMemory bufferMemory = nullptr; + createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMemory); + + uniformBuffers.push_back(std::move(buffer)); + uniformBuffersMemory.push_back(std::move(bufferMemory)); + uniformBuffersMapped[i] = uniformBuffersMemory[i].mapMemory(0, bufferSize); + } + } + + void createDescriptorPool() + { + std::array poolSizes{}; + poolSizes[0].type = vk::DescriptorType::eUniformBuffer; + poolSizes[0].descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT); + poolSizes[1].type = vk::DescriptorType::eCombinedImageSampler; + poolSizes[1].descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT); + + vk::DescriptorPoolCreateInfo poolInfo{ + .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, + .maxSets = static_cast(MAX_FRAMES_IN_FLIGHT), + .poolSizeCount = static_cast(poolSizes.size()), + .pPoolSizes = poolSizes.data()}; + + descriptorPool = device.createDescriptorPool(poolInfo); + } + + void createDescriptorSets() + { + std::vector layouts(MAX_FRAMES_IN_FLIGHT, *descriptorSetLayout); + vk::DescriptorSetAllocateInfo allocInfo{ + .descriptorPool = *descriptorPool, + .descriptorSetCount = static_cast(MAX_FRAMES_IN_FLIGHT), + .pSetLayouts = layouts.data()}; + + descriptorSets = device.allocateDescriptorSets(allocInfo); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DescriptorBufferInfo bufferInfo{ + .buffer = *uniformBuffers[i], + .offset = 0, + .range = sizeof(UniformBufferObject)}; + + vk::DescriptorImageInfo imageInfo{ + .sampler = *textureSampler, + .imageView = *textureImageView, + .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal}; + + std::array descriptorWrites{}; + + descriptorWrites[0].dstSet = *descriptorSets[i]; + descriptorWrites[0].dstBinding = 0; + descriptorWrites[0].dstArrayElement = 0; + descriptorWrites[0].descriptorType = vk::DescriptorType::eUniformBuffer; + descriptorWrites[0].descriptorCount = 1; + descriptorWrites[0].pBufferInfo = &bufferInfo; + + descriptorWrites[1].dstSet = *descriptorSets[i]; + descriptorWrites[1].dstBinding = 1; + descriptorWrites[1].dstArrayElement = 0; + descriptorWrites[1].descriptorType = vk::DescriptorType::eCombinedImageSampler; + descriptorWrites[1].descriptorCount = 1; + descriptorWrites[1].pImageInfo = &imageInfo; + + device.updateDescriptorSets(descriptorWrites, nullptr); + } + } + + void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer &buffer, vk::raii::DeviceMemory &bufferMemory) + { + vk::BufferCreateInfo bufferInfo{ + .size = size, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive}; + + buffer = device.createBuffer(bufferInfo); + + vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); + + vk::MemoryAllocateInfo allocInfo{ + .allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + + bufferMemory = device.allocateMemory(allocInfo); + buffer.bindMemory(*bufferMemory, 0); + } + + void copyBuffer(vk::Buffer srcBuffer, vk::Buffer dstBuffer, vk::DeviceSize size) + { + vk::raii::CommandBuffer commandBuffer = beginSingleTimeCommands(); + + vk::BufferCopy copyRegion{ + .size = size}; + commandBuffer.copyBuffer(srcBuffer, dstBuffer, copyRegion); + + endSingleTimeCommands(commandBuffer); + } + + void copyBufferToImage(vk::Buffer buffer, vk::Image image, uint32_t width, uint32_t height) + { + vk::raii::CommandBuffer commandBuffer = beginSingleTimeCommands(); + + vk::BufferImageCopy region{ + .bufferOffset = 0, + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .mipLevel = 0, + .baseArrayLayer = 0, + .layerCount = 1}, + .imageOffset = {0, 0, 0}, + .imageExtent = {width, height, 1}}; + + commandBuffer.copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, region); + + endSingleTimeCommands(commandBuffer); + } + + void createImage(uint32_t width, uint32_t height, uint32_t mipLevels, vk::SampleCountFlagBits numSamples, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image &image, vk::raii::DeviceMemory &imageMemory) + { + vk::ImageCreateInfo imageInfo{ + .imageType = vk::ImageType::e2D, + .format = format, + .extent = { + .width = width, + .height = height, + .depth = 1}, + .mipLevels = mipLevels, + .arrayLayers = 1, + .samples = numSamples, + .tiling = tiling, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive, + .initialLayout = vk::ImageLayout::eUndefined}; + + image = device.createImage(imageInfo); + + vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); + + vk::MemoryAllocateInfo allocInfo{ + .allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + + imageMemory = device.allocateMemory(allocInfo); + image.bindMemory(*imageMemory, 0); + } + + void transitionImageLayout(vk::Image image, vk::Format format, vk::ImageLayout oldLayout, vk::ImageLayout newLayout, uint32_t mipLevels) + { + vk::raii::CommandBuffer commandBuffer = beginSingleTimeCommands(); + + vk::ImageMemoryBarrier barrier{ + .oldLayout = oldLayout, + .newLayout = newLayout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = image, + .subresourceRange = { + .baseMipLevel = 0, + .levelCount = mipLevels, + .baseArrayLayer = 0, + .layerCount = 1}}; + + if (newLayout == vk::ImageLayout::eDepthStencilAttachmentOptimal) + { + barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eDepth; + + if (hasStencilComponent(format)) + { + barrier.subresourceRange.aspectMask |= vk::ImageAspectFlagBits::eStencil; + } + } + else + { + barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; + } + + vk::PipelineStageFlags sourceStage; + vk::PipelineStageFlags destinationStage; + + if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) + { + barrier.srcAccessMask = vk::AccessFlagBits::eNone; + barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; + + sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; + destinationStage = vk::PipelineStageFlagBits::eTransfer; + } + else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) + { + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + + sourceStage = vk::PipelineStageFlagBits::eTransfer; + destinationStage = vk::PipelineStageFlagBits::eFragmentShader; + } + else if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eDepthStencilAttachmentOptimal) + { + barrier.srcAccessMask = vk::AccessFlagBits::eNone; + barrier.dstAccessMask = vk::AccessFlagBits::eDepthStencilAttachmentRead | vk::AccessFlagBits::eDepthStencilAttachmentWrite; + + sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; + destinationStage = vk::PipelineStageFlagBits::eEarlyFragmentTests; + } + else + { + throw std::invalid_argument("unsupported layout transition!"); + } + + commandBuffer.pipelineBarrier( + sourceStage, + destinationStage, + {}, + std::array{}, + std::array{}, + std::array{barrier}); + + endSingleTimeCommands(commandBuffer); + } + + vk::raii::CommandBuffer beginSingleTimeCommands() + { + vk::CommandBufferAllocateInfo allocInfo{ + .commandPool = *commandPool, + .level = vk::CommandBufferLevel::ePrimary, + .commandBufferCount = 1}; + + vk::raii::CommandBuffer commandBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); + + vk::CommandBufferBeginInfo beginInfo{ + .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; + + commandBuffer.begin(beginInfo); + + return commandBuffer; + } + + void endSingleTimeCommands(vk::raii::CommandBuffer &commandBuffer) + { + commandBuffer.end(); + + vk::SubmitInfo submitInfo{ + .commandBufferCount = 1, + .pCommandBuffers = &*commandBuffer}; + + queue.submit(submitInfo, nullptr); + queue.waitIdle(); + } + + void loadModel() + { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn, err; + + if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, MODEL_PATH.c_str())) + { + throw std::runtime_error(warn + err); + } + + std::unordered_map uniqueVertices{}; + + for (const auto &shape : shapes) + { + for (const auto &index : shape.mesh.indices) + { + Vertex vertex{}; + + vertex.pos = { + attrib.vertices[3 * index.vertex_index + 0], + attrib.vertices[3 * index.vertex_index + 1], + attrib.vertices[3 * index.vertex_index + 2]}; + + vertex.texCoord = { + attrib.texcoords[2 * index.texcoord_index + 0], + 1.0f - attrib.texcoords[2 * index.texcoord_index + 1]}; + + vertex.color = {1.0f, 1.0f, 1.0f}; + + if (uniqueVertices.count(vertex) == 0) + { + uniqueVertices[vertex] = static_cast(vertices.size()); + vertices.push_back(vertex); + } + + indices.push_back(uniqueVertices[vertex]); + } + } + } + + void createCommandBuffers() + { + commandBuffers.reserve(MAX_FRAMES_IN_FLIGHT); + + vk::CommandBufferAllocateInfo allocInfo{ + .commandPool = *commandPool, + .level = vk::CommandBufferLevel::ePrimary, + .commandBufferCount = static_cast(MAX_FRAMES_IN_FLIGHT)}; + + commandBuffers = device.allocateCommandBuffers(allocInfo); + } + + void recordCommandBuffer(uint32_t imageIndex) + { + commandBuffers[currentFrame].begin({}); + + // Transition the swapchain image to the correct layout for rendering + vk::ImageMemoryBarrier imageBarrier{ + .srcAccessMask = vk::AccessFlagBits::eNone, + .dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite, + .oldLayout = vk::ImageLayout::eUndefined, + .newLayout = vk::ImageLayout::eColorAttachmentOptimal, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + + commandBuffers[currentFrame].pipelineBarrier( + vk::PipelineStageFlagBits::eTopOfPipe, + vk::PipelineStageFlagBits::eColorAttachmentOutput, + vk::DependencyFlagBits::eByRegion, + std::array{}, + std::array{}, + std::array{imageBarrier}); + + // Clear values for color and depth + vk::ClearValue clearColor{}; + clearColor.color = vk::ClearColorValue(std::array{0.0f, 0.0f, 0.0f, 1.0f}); + + vk::ClearValue clearDepth{}; + clearDepth.depthStencil = vk::ClearDepthStencilValue{1.0f, 0}; + + std::array clearValues = {clearColor, clearDepth}; + + // Use different rendering approach based on profile support + if (appInfo.profileSupported) + { + // Use dynamic rendering with the KHR roadmap 2022 profile + vk::RenderingAttachmentInfo colorAttachment{ + .imageView = *colorImageView, + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .resolveMode = vk::ResolveModeFlagBits::eAverage, + .resolveImageView = *swapChainImageViews[imageIndex], + .resolveImageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor}; + + vk::RenderingAttachmentInfo depthAttachment{ + .imageView = *depthImageView, + .imageLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eDontCare, + .clearValue = clearDepth}; + + vk::RenderingInfo renderingInfo{ + .renderArea = {{0, 0}, swapChainExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &colorAttachment, + .pDepthAttachment = &depthAttachment}; + + commandBuffers[currentFrame].beginRendering(renderingInfo); + } + else + { + // Use traditional render pass if not using the KHR roadmap 2022 profile + vk::RenderPassBeginInfo renderPassInfo{ + .renderPass = *renderPass, + .framebuffer = *swapChainFramebuffers[imageIndex], + .renderArea = {{0, 0}, swapChainExtent}, + .clearValueCount = static_cast(clearValues.size()), + .pClearValues = clearValues.data()}; + + commandBuffers[currentFrame].beginRenderPass(renderPassInfo, vk::SubpassContents::eInline); + } + + commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + + vk::Viewport viewport{ + .x = 0.0f, + .y = 0.0f, + .width = static_cast(swapChainExtent.width), + .height = static_cast(swapChainExtent.height), + .minDepth = 0.0f, + .maxDepth = 1.0f}; + commandBuffers[currentFrame].setViewport(0, viewport); + + vk::Rect2D scissor{ + .offset = {0, 0}, + .extent = swapChainExtent}; + commandBuffers[currentFrame].setScissor(0, scissor); + + commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint32); + commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, *pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); + commandBuffers[currentFrame].drawIndexed(static_cast(indices.size()), 1, 0, 0, 0); + + if (appInfo.profileSupported) + { + commandBuffers[currentFrame].endRendering(); + + // Transition the swapchain image to the correct layout for presentation + vk::ImageMemoryBarrier barrier{ + .srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite, + .dstAccessMask = vk::AccessFlagBits::eNone, + .oldLayout = vk::ImageLayout::eColorAttachmentOptimal, + .newLayout = vk::ImageLayout::ePresentSrcKHR, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + + commandBuffers[currentFrame].pipelineBarrier( + vk::PipelineStageFlagBits::eColorAttachmentOutput, + vk::PipelineStageFlagBits::eBottomOfPipe, + vk::DependencyFlagBits::eByRegion, + std::array{}, + std::array{}, + std::array{barrier}); + } + else + { + commandBuffers[currentFrame].endRenderPass(); + // Traditional render pass already transitions the image to the correct layout + } + + commandBuffers[currentFrame].end(); + } + + void createSyncObjects() + { + imageAvailableSemaphores.reserve(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.reserve(MAX_FRAMES_IN_FLIGHT); + inFlightFences.reserve(MAX_FRAMES_IN_FLIGHT); + presentCompleteSemaphore.reserve(swapChainImages.size()); + + vk::SemaphoreCreateInfo semaphoreInfo{}; + vk::FenceCreateInfo fenceInfo{ + .flags = vk::FenceCreateFlagBits::eSignaled}; + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + imageAvailableSemaphores.push_back(device.createSemaphore(semaphoreInfo)); + renderFinishedSemaphores.push_back(device.createSemaphore(semaphoreInfo)); + inFlightFences.push_back(device.createFence(fenceInfo)); + } + + for (size_t i = 0; i < swapChainImages.size(); i++) + { + presentCompleteSemaphore.push_back(device.createSemaphore(semaphoreInfo)); + } + } + + void updateUniformBuffer(uint32_t currentImage) + { + static auto startTime = std::chrono::high_resolution_clock::now(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + UniformBufferObject ubo{}; + ubo.model = glm::rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.proj = glm::perspective(glm::radians(45.0f), swapChainExtent.width / (float) swapChainExtent.height, 0.1f, 10.0f); + ubo.proj[1][1] *= -1; + + memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); + } + + void drawFrame() + { + static_cast(device.waitForFences({*inFlightFences[currentFrame]}, VK_TRUE, FenceTimeout)); + + uint32_t imageIndex; + try + { + auto [result, idx] = swapChain.acquireNextImage(FenceTimeout, *imageAvailableSemaphores[currentFrame]); + imageIndex = idx; + } + catch (vk::OutOfDateKHRError &) + { + recreateSwapChain(); + return; + } + + updateUniformBuffer(currentFrame); + + device.resetFences({*inFlightFences[currentFrame]}); + + commandBuffers[currentFrame].reset(); + recordCommandBuffer(imageIndex); + + vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); + const vk::SubmitInfo submitInfo{ + .waitSemaphoreCount = 1, + .pWaitSemaphores = &*imageAvailableSemaphores[currentFrame], + .pWaitDstStageMask = &waitDestinationStageMask, + .commandBufferCount = 1, + .pCommandBuffers = &*commandBuffers[currentFrame], + .signalSemaphoreCount = 1, + .pSignalSemaphores = &*presentCompleteSemaphore[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[currentFrame]); + + const vk::PresentInfoKHR presentInfoKHR{ + .waitSemaphoreCount = 1, + .pWaitSemaphores = &*presentCompleteSemaphore[imageIndex], + .swapchainCount = 1, + .pSwapchains = &*swapChain, + .pImageIndices = &imageIndex}; + + vk::Result result; + try + { + result = queue.presentKHR(presentInfoKHR); + } + catch (vk::OutOfDateKHRError &) + { + result = vk::Result::eErrorOutOfDateKHR; + } + + if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) + { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != vk::Result::eSuccess) + { + throw std::runtime_error("failed to present swap chain image!"); + } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + vk::SampleCountFlagBits getMaxUsableSampleCount() + { + vk::PhysicalDeviceProperties physicalDeviceProperties = physicalDevice.getProperties(); + + vk::SampleCountFlags counts = physicalDeviceProperties.limits.framebufferColorSampleCounts & physicalDeviceProperties.limits.framebufferDepthSampleCounts; + if (counts & vk::SampleCountFlagBits::e64) + { + return vk::SampleCountFlagBits::e64; + } + if (counts & vk::SampleCountFlagBits::e32) + { + return vk::SampleCountFlagBits::e32; + } + if (counts & vk::SampleCountFlagBits::e16) + { + return vk::SampleCountFlagBits::e16; + } + if (counts & vk::SampleCountFlagBits::e8) + { + return vk::SampleCountFlagBits::e8; + } + if (counts & vk::SampleCountFlagBits::e4) + { + return vk::SampleCountFlagBits::e4; + } + if (counts & vk::SampleCountFlagBits::e2) + { + return vk::SampleCountFlagBits::e2; + } + + return vk::SampleCountFlagBits::e1; + } + + uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) + { + vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) + { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) + { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const &surfaceCapabilities) + { + auto minImageCount = std::max(3u, surfaceCapabilities.minImageCount); + if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) + { + minImageCount = surfaceCapabilities.maxImageCount; + } + return minImageCount; + } + + static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + assert(!availableFormats.empty()); + const auto formatIt = std::ranges::find_if( + availableFormats, + [](const auto &format) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; }); + return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; + } + + vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + assert(std::ranges::any_of(availablePresentModes, [](auto presentMode) { return presentMode == vk::PresentModeKHR::eFifo; })); + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + vk::PresentModeKHR::eFifo; + } + + vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR &capabilities) + { + if (capabilities.currentExtent.width != 0xFFFFFFFF) + { + 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)}; + } + + std::vector getRequiredExtensions() + { + // Get the required extensions from GLFW + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + // Check if the debug utils extension is available + std::vector props = context.enumerateInstanceExtensionProperties(); + bool debugUtilsAvailable = std::ranges::any_of(props, + [](vk::ExtensionProperties const &ep) { + return strcmp(ep.extensionName, vk::EXTDebugUtilsExtensionName) == 0; + }); + + // Always include the debug utils extension if available + // This allows validation layers to be enabled via vulkanconfig + if (debugUtilsAvailable) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + else + { + std::cout << "VK_EXT_debug_utils extension not available. Validation layers may not work." << std::endl; + } + + return extensions; + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + vk::raii::ShaderModule createShaderModule(const std::vector &code) + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size(), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + 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; + } + + SwapChainSupportDetails querySwapChainSupport(vk::raii::PhysicalDevice device) + { + SwapChainSupportDetails details; + details.capabilities = device.getSurfaceCapabilitiesKHR(*surface); + details.formats = device.getSurfaceFormatsKHR(*surface); + details.presentModes = device.getSurfacePresentModesKHR(*surface); + + return details; + } }; -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } diff --git a/attachments/34_android.cpp b/attachments/34_android.cpp index a4bf7396..eb12e393 100644 --- a/attachments/34_android.cpp +++ b/attachments/34_android.cpp @@ -13,25 +13,24 @@ #include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include #if defined(__ANDROID__) -#include -#include +# include +# include #endif #include // Platform detection #if defined(__ANDROID__) - #define PLATFORM_ANDROID 1 +# define PLATFORM_ANDROID 1 #else - #define PLATFORM_DESKTOP 1 +# define PLATFORM_DESKTOP 1 #endif - #define STB_IMAGE_IMPLEMENTATION #include @@ -40,40 +39,47 @@ import vulkan_hpp; // Platform-specific includes #if PLATFORM_ANDROID - // Android-specific includes - #include - #include - #include - #include - - // Declare and implement app_dummy function from native_app_glue - extern "C" void app_dummy() { - // This is a dummy function that does nothing - // It's used to prevent the linker from stripping out the native_app_glue code - } - - // Define AAssetManager type for Android - typedef AAssetManager AssetManagerType; - - // Define logging macros for Android - #define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "VulkanTutorial", __VA_ARGS__)) - #define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "VulkanTutorial", __VA_ARGS__)) - #define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "VulkanTutorial", __VA_ARGS__)) - #define LOG_INFO(msg) LOGI("%s", msg) - #define LOG_ERROR(msg) LOGE("%s", msg) +// Android-specific includes +# include +# include +# include +# include + +// Declare and implement app_dummy function from native_app_glue +extern "C" void app_dummy() +{ + // This is a dummy function that does nothing + // It's used to prevent the linker from stripping out the native_app_glue code +} + +// Define AAssetManager type for Android +typedef AAssetManager AssetManagerType; + +// Define logging macros for Android +# define LOGI(...) ((void) __android_log_print(ANDROID_LOG_INFO, "VulkanTutorial", __VA_ARGS__)) +# define LOGW(...) ((void) __android_log_print(ANDROID_LOG_WARN, "VulkanTutorial", __VA_ARGS__)) +# define LOGE(...) ((void) __android_log_print(ANDROID_LOG_ERROR, "VulkanTutorial", __VA_ARGS__)) +# define LOG_INFO(msg) LOGI("%s", msg) +# define LOG_ERROR(msg) LOGE("%s", msg) #else - // Define AAssetManager type for non-Android platforms - typedef void AssetManagerType; - // Desktop-specific includes - #define GLFW_INCLUDE_VULKAN - #include - - // Define logging macros for Desktop - #define LOGI(...) printf(__VA_ARGS__); printf("\n") - #define LOGW(...) printf(__VA_ARGS__); printf("\n") - #define LOGE(...) fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n") - #define LOG_INFO(msg) std::cout << msg << std::endl - #define LOG_ERROR(msg) std::cerr << msg << std::endl +// Define AAssetManager type for non-Android platforms +typedef void AssetManagerType; +// Desktop-specific includes +# define GLFW_INCLUDE_VULKAN +# include + +// Define logging macros for Desktop +# define LOGI(...) \ + printf(__VA_ARGS__); \ + printf("\n") +# define LOGW(...) \ + printf(__VA_ARGS__); \ + printf("\n") +# define LOGE(...) \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "\n") +# define LOG_INFO(msg) std::cout << msg << std::endl +# define LOG_ERROR(msg) std::cerr << msg << std::endl #endif #define GLM_FORCE_RADIANS @@ -84,1627 +90,1667 @@ import vulkan_hpp; #include #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -constexpr uint64_t FenceTimeout = 100000000; -const std::string MODEL_PATH = "models/viking_room.obj"; -const std::string TEXTURE_PATH = "textures/viking_room.png"; -constexpr int MAX_FRAMES_IN_FLIGHT = 2; +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; +constexpr uint64_t FenceTimeout = 100000000; +const std::string MODEL_PATH = "models/viking_room.obj"; +const std::string TEXTURE_PATH = "textures/viking_room.png"; +constexpr int MAX_FRAMES_IN_FLIGHT = 2; #if PLATFORM_ANDROID // Define VpProfileProperties structure if not already defined -#ifndef VP_PROFILE_PROPERTIES_DEFINED -#define VP_PROFILE_PROPERTIES_DEFINED -struct VpProfileProperties { - char name[256]; - uint32_t specVersion; +# ifndef VP_PROFILE_PROPERTIES_DEFINED +# define VP_PROFILE_PROPERTIES_DEFINED +struct VpProfileProperties +{ + char name[256]; + uint32_t specVersion; }; -#endif +# endif // Define Vulkan Profile constants -#ifndef VP_KHR_ROADMAP_2022_NAME -#define VP_KHR_ROADMAP_2022_NAME "VP_KHR_roadmap_2022" -#endif +# ifndef VP_KHR_ROADMAP_2022_NAME +# define VP_KHR_ROADMAP_2022_NAME "VP_KHR_roadmap_2022" +# endif -#ifndef VP_KHR_ROADMAP_2022_SPEC_VERSION -#define VP_KHR_ROADMAP_2022_SPEC_VERSION 1 -#endif +# ifndef VP_KHR_ROADMAP_2022_SPEC_VERSION +# define VP_KHR_ROADMAP_2022_SPEC_VERSION 1 +# endif #endif // Application info structure to store profile support flags -struct AppInfo { - bool profileSupported = false; - VpProfileProperties profile; +struct AppInfo +{ + bool profileSupported = false; + VpProfileProperties profile; }; -struct Vertex { - glm::vec3 pos; - glm::vec3 color; - glm::vec2 texCoord; - - static vk::VertexInputBindingDescription getBindingDescription() { - return { 0, sizeof(Vertex), vk::VertexInputRate::eVertex }; - } - - static std::array getAttributeDescriptions() { - return { - vk::VertexInputAttributeDescription( 0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, pos) ), - vk::VertexInputAttributeDescription( 1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color) ), - vk::VertexInputAttributeDescription( 2, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, texCoord) ) - }; - } - - bool operator==(const Vertex& other) const { - return pos == other.pos && color == other.color && texCoord == other.texCoord; - } +struct Vertex +{ + glm::vec3 pos; + glm::vec3 color; + glm::vec2 texCoord; + + static vk::VertexInputBindingDescription getBindingDescription() + { + return {0, sizeof(Vertex), vk::VertexInputRate::eVertex}; + } + + static std::array getAttributeDescriptions() + { + return { + vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, pos)), + vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color)), + vk::VertexInputAttributeDescription(2, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, texCoord))}; + } + + bool operator==(const Vertex &other) const + { + return pos == other.pos && color == other.color && texCoord == other.texCoord; + } }; -template<> struct std::hash { - size_t operator()(Vertex const& vertex) const noexcept { - return ((hash()(vertex.pos) ^ (hash()(vertex.color) << 1)) >> 1) ^ (hash()(vertex.texCoord) << 1); - } +template <> +struct std::hash +{ + size_t operator()(Vertex const &vertex) const noexcept + { + return ((hash()(vertex.pos) ^ (hash()(vertex.color) << 1)) >> 1) ^ (hash()(vertex.texCoord) << 1); + } }; -struct UniformBufferObject { - alignas(16) glm::mat4 model; - alignas(16) glm::mat4 view; - alignas(16) glm::mat4 proj; +struct UniformBufferObject +{ + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; }; // Cross-platform file reading function -std::vector readFile(const std::string& filename, std::optional assetManager = std::nullopt) { +std::vector readFile(const std::string &filename, std::optional assetManager = std::nullopt) +{ #if PLATFORM_ANDROID - // On Android, use asset manager if provided - if (assetManager.has_value() && *assetManager != nullptr) { - // Open the asset - AAsset* asset = AAssetManager_open(*assetManager, filename.c_str(), AASSET_MODE_BUFFER); - if (!asset) { - LOGE("Failed to open asset: %s", filename.c_str()); - throw std::runtime_error("Failed to open file: " + filename); - } - - // Get the file size - off_t fileSize = AAsset_getLength(asset); - std::vector buffer(fileSize); - - // Read the file data - AAsset_read(asset, buffer.data(), fileSize); - - // Close the asset - AAsset_close(asset); - - return buffer; - } + // On Android, use asset manager if provided + if (assetManager.has_value() && *assetManager != nullptr) + { + // Open the asset + AAsset *asset = AAssetManager_open(*assetManager, filename.c_str(), AASSET_MODE_BUFFER); + if (!asset) + { + LOGE("Failed to open asset: %s", filename.c_str()); + throw std::runtime_error("Failed to open file: " + filename); + } + + // Get the file size + off_t fileSize = AAsset_getLength(asset); + std::vector buffer(fileSize); + + // Read the file data + AAsset_read(asset, buffer.data(), fileSize); + + // Close the asset + AAsset_close(asset); + + return buffer; + } #endif - // Desktop version or Android fallback to filesystem - std::ifstream file(filename, std::ios::ate | std::ios::binary); + // Desktop version or Android fallback to filesystem + std::ifstream file(filename, std::ios::ate | std::ios::binary); - if (!file.is_open()) { - throw std::runtime_error("Failed to open file: " + filename); - } + if (!file.is_open()) + { + throw std::runtime_error("Failed to open file: " + filename); + } - size_t fileSize = static_cast(file.tellg()); - std::vector buffer(fileSize); + size_t fileSize = static_cast(file.tellg()); + std::vector buffer(fileSize); - file.seekg(0); - file.read(buffer.data(), fileSize); - file.close(); + file.seekg(0); + file.read(buffer.data(), fileSize); + file.close(); - return buffer; + return buffer; } // Cross-platform application class -class HelloTriangleApplication { -public: +class HelloTriangleApplication +{ + public: #if PLATFORM_DESKTOP - // Desktop constructor - HelloTriangleApplication() { - // No Android-specific initialization needed - } + // Desktop constructor + HelloTriangleApplication() + { + // No Android-specific initialization needed + } #else - // Android constructor - HelloTriangleApplication(android_app* app) : androidApp(app) { - androidApp->userData = this; - androidApp->onAppCmd = handleAppCommand; - // Note: onInputEvent is no longer a member of android_app in the current NDK version - // Input events are now handled differently - - // Get the asset manager - assetManager = androidApp->activity->assetManager; - } + // Android constructor + HelloTriangleApplication(android_app *app) : + androidApp(app) + { + androidApp->userData = this; + androidApp->onAppCmd = handleAppCommand; + // Note: onInputEvent is no longer a member of android_app in the current NDK version + // Input events are now handled differently + + // Get the asset manager + assetManager = androidApp->activity->assetManager; + } #endif - void run() { + void run() + { #if PLATFORM_DESKTOP - // Desktop main loop - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); + // Desktop main loop + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); #else - // Android main loop is handled by Android - while (!initialized) { - // Wait for app to initialize - int events; - android_poll_source* source; - if (ALooper_pollOnce(0, nullptr, &events, (void**)&source) >= 0) { - if (source != nullptr) { - source->process(androidApp, source); - } - } - } + // Android main loop is handled by Android + while (!initialized) + { + // Wait for app to initialize + int events; + android_poll_source *source; + if (ALooper_pollOnce(0, nullptr, &events, (void **) &source) >= 0) + { + if (source != nullptr) + { + source->process(androidApp, source); + } + } + } #endif - } + } #if PLATFORM_DESKTOP - // Initialize window (Desktop only) - void initWindow() { - glfwInit(); - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan Cross-Platform", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); - - LOG_INFO("Desktop window created"); - } - - // Desktop main loop - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - drawFrame(); - } - - device.waitIdle(); - } - - // Desktop framebuffer resize callback - static void framebufferResizeCallback(GLFWwindow* window, int, int) { - auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); - app->framebufferResized = true; - } + // Initialize window (Desktop only) + void initWindow() + { + glfwInit(); + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan Cross-Platform", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + + LOG_INFO("Desktop window created"); + } + + // Desktop main loop + void mainLoop() + { + while (!glfwWindowShouldClose(window)) + { + glfwPollEvents(); + drawFrame(); + } + + device.waitIdle(); + } + + // Desktop framebuffer resize callback + static void framebufferResizeCallback(GLFWwindow *window, int, int) + { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } #endif - void cleanup() { - if (initialized) { - // Wait for device to finish operations - if (*device) { - device.waitIdle(); - } + void cleanup() + { + if (initialized) + { + // Wait for device to finish operations + if (*device) + { + device.waitIdle(); + } - // Cleanup resources - cleanupSwapChain(); + // Cleanup resources + cleanupSwapChain(); - initialized = false; - } - } + initialized = false; + } + } -private: + private: #if PLATFORM_ANDROID - // Android-specific members - android_app* androidApp = nullptr; - AssetManagerType* assetManager = nullptr; + // Android-specific members + android_app *androidApp = nullptr; + AssetManagerType *assetManager = nullptr; #else - // Desktop-specific members - GLFWwindow* window = nullptr; + // Desktop-specific members + GLFWwindow *window = nullptr; #endif - bool initialized = false; - bool framebufferResized = false; - - // Vulkan objects - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::SurfaceFormatKHR swapChainSurfaceFormat; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::RenderPass renderPass = nullptr; - vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - std::vector swapChainFramebuffers; - vk::raii::CommandPool commandPool = nullptr; - std::vector commandBuffers; - vk::raii::Buffer vertexBuffer = nullptr; - vk::raii::DeviceMemory vertexBufferMemory = nullptr; - vk::raii::Buffer indexBuffer = nullptr; - vk::raii::DeviceMemory indexBufferMemory = nullptr; - vk::raii::Image textureImage = nullptr; - vk::raii::DeviceMemory textureImageMemory = nullptr; - vk::raii::ImageView textureImageView = nullptr; - vk::raii::Sampler textureSampler = nullptr; - std::vector uniformBuffers; - std::vector uniformBuffersMemory; - vk::raii::DescriptorPool descriptorPool = nullptr; - std::vector descriptorSets; - std::vector imageAvailableSemaphores; - std::vector renderFinishedSemaphores; - std::vector inFlightFences; - uint32_t currentFrame = 0; - - // Application info - AppInfo appInfo; - - // Model data - std::vector vertices; - std::vector indices; - - // Swap chain support details - struct SwapChainSupportDetails { - vk::SurfaceCapabilitiesKHR capabilities; - std::vector formats; - std::vector presentModes; - }; - - // Required device extensions - const std::vector deviceExtensions = { - VK_KHR_SWAPCHAIN_EXTENSION_NAME - }; - - // Initialize Vulkan - void initVulkan() { - createInstance(); - createSurface(); - pickPhysicalDevice(); - checkFeatureSupport(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createRenderPass(); - createDescriptorSetLayout(); - createGraphicsPipeline(); - createFramebuffers(); - createCommandPool(); - createTextureImage(); - createTextureImageView(); - createTextureSampler(); - loadModel(); - createVertexBuffer(); - createIndexBuffer(); - createUniformBuffers(); - createDescriptorPool(); - createDescriptorSets(); - createCommandBuffers(); - createSyncObjects(); - - initialized = true; - } - - // Create Vulkan instance - void createInstance() { - // Application info - vk::ApplicationInfo appInfo{ - .pApplicationName = "Vulkan Android", - .applicationVersion = VK_MAKE_VERSION(1, 0, 0), - .pEngineName = "No Engine", - .engineVersion = VK_MAKE_VERSION(1, 0, 0), - .apiVersion = VK_API_VERSION_1_3 - }; - - // Get required extensions - std::vector extensions = getRequiredExtensions(); - - // Create instance - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledExtensionCount = static_cast(extensions.size()), - .ppEnabledExtensionNames = extensions.data() - }; - - instance = vk::raii::Instance(context, createInfo); - LOGI("Vulkan instance created"); - } - - // Create platform-specific surface - void createSurface() { - VkSurfaceKHR _surface; + bool initialized = false; + bool framebufferResized = false; + + // Vulkan objects + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::SurfaceFormatKHR swapChainSurfaceFormat; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::RenderPass renderPass = nullptr; + vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + std::vector swapChainFramebuffers; + vk::raii::CommandPool commandPool = nullptr; + std::vector commandBuffers; + vk::raii::Buffer vertexBuffer = nullptr; + vk::raii::DeviceMemory vertexBufferMemory = nullptr; + vk::raii::Buffer indexBuffer = nullptr; + vk::raii::DeviceMemory indexBufferMemory = nullptr; + vk::raii::Image textureImage = nullptr; + vk::raii::DeviceMemory textureImageMemory = nullptr; + vk::raii::ImageView textureImageView = nullptr; + vk::raii::Sampler textureSampler = nullptr; + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + vk::raii::DescriptorPool descriptorPool = nullptr; + std::vector descriptorSets; + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + std::vector inFlightFences; + uint32_t currentFrame = 0; + + // Application info + AppInfo appInfo; + + // Model data + std::vector vertices; + std::vector indices; + + // Swap chain support details + struct SwapChainSupportDetails + { + vk::SurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; + }; + + // Required device extensions + const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME}; + + // Initialize Vulkan + void initVulkan() + { + createInstance(); + createSurface(); + pickPhysicalDevice(); + checkFeatureSupport(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createDescriptorSetLayout(); + createGraphicsPipeline(); + createFramebuffers(); + createCommandPool(); + createTextureImage(); + createTextureImageView(); + createTextureSampler(); + loadModel(); + createVertexBuffer(); + createIndexBuffer(); + createUniformBuffers(); + createDescriptorPool(); + createDescriptorSets(); + createCommandBuffers(); + createSyncObjects(); + + initialized = true; + } + + // Create Vulkan instance + void createInstance() + { + // Application info + vk::ApplicationInfo appInfo{ + .pApplicationName = "Vulkan Android", + .applicationVersion = VK_MAKE_VERSION(1, 0, 0), + .pEngineName = "No Engine", + .engineVersion = VK_MAKE_VERSION(1, 0, 0), + .apiVersion = VK_API_VERSION_1_3}; + + // Get required extensions + std::vector extensions = getRequiredExtensions(); + + // Create instance + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledExtensionCount = static_cast(extensions.size()), + .ppEnabledExtensionNames = extensions.data()}; + + instance = vk::raii::Instance(context, createInfo); + LOGI("Vulkan instance created"); + } + + // Create platform-specific surface + void createSurface() + { + VkSurfaceKHR _surface; #if PLATFORM_ANDROID - // Create Android surface - VkAndroidSurfaceCreateInfoKHR createInfo = { - .sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR, - .pNext = nullptr, - .flags = 0, - .window = androidApp->window - }; - - VkResult result = vkCreateAndroidSurfaceKHR( - *instance, - &createInfo, - nullptr, - &_surface - ); - - if (result != VK_SUCCESS) { - throw std::runtime_error("Failed to create Android surface"); - } - - LOG_INFO("Android surface created"); + // Create Android surface + VkAndroidSurfaceCreateInfoKHR createInfo = { + .sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR, + .pNext = nullptr, + .flags = 0, + .window = androidApp->window}; + + VkResult result = vkCreateAndroidSurfaceKHR( + *instance, + &createInfo, + nullptr, + &_surface); + + if (result != VK_SUCCESS) + { + throw std::runtime_error("Failed to create Android surface"); + } + + LOG_INFO("Android surface created"); #else - // Create desktop surface using GLFW - if (glfwCreateWindowSurface(*instance, window, nullptr, &_surface) != 0) { - throw std::runtime_error("Failed to create window surface"); - } + // Create desktop surface using GLFW + if (glfwCreateWindowSurface(*instance, window, nullptr, &_surface) != 0) + { + throw std::runtime_error("Failed to create window surface"); + } - LOG_INFO("Desktop surface created"); + LOG_INFO("Desktop surface created"); #endif - surface = vk::raii::SurfaceKHR(instance, _surface); - } + surface = vk::raii::SurfaceKHR(instance, _surface); + } - // Pick physical device - void pickPhysicalDevice() { - std::vector devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( + // Pick physical device + void pickPhysicalDevice() + { + std::vector devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( devices, - [&](auto const& device) { + [&](auto const &device) { // Check if any of the queue families support graphics operations auto queueFamilies = device.getQueueFamilyProperties(); bool supportsGraphics = - std::ranges::any_of(queueFamilies, [](auto const& qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); // Check if all required device extensions are available auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); bool supportsAllRequiredExtensions = std::ranges::all_of(deviceExtensions, - [&availableDeviceExtensions](auto const& requiredDeviceExtension) { - return std::ranges::any_of(availableDeviceExtensions, - [requiredDeviceExtension](auto const& availableDeviceExtension) { - return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; - }); - }); + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { + return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; + }); + }); return supportsGraphics && supportsAllRequiredExtensions; }); - if (devIter != devices.end()) { - physicalDevice = *devIter; - - // Print device information - vk::PhysicalDeviceProperties deviceProperties = physicalDevice.getProperties(); - LOGI("Selected GPU: %s", deviceProperties.deviceName.data()); - } else { - throw std::runtime_error("Failed to find a suitable GPU"); - } - } - - // Check feature support - void checkFeatureSupport() { - // Define the KHR roadmap 2022 profile - appInfo.profile = { - VP_KHR_ROADMAP_2022_NAME, - VP_KHR_ROADMAP_2022_SPEC_VERSION - }; - - // Check if the profile is supported - VkBool32 supported = VK_FALSE; + if (devIter != devices.end()) + { + physicalDevice = *devIter; + + // Print device information + vk::PhysicalDeviceProperties deviceProperties = physicalDevice.getProperties(); + LOGI("Selected GPU: %s", deviceProperties.deviceName.data()); + } + else + { + throw std::runtime_error("Failed to find a suitable GPU"); + } + } + + // Check feature support + void checkFeatureSupport() + { + // Define the KHR roadmap 2022 profile + appInfo.profile = { + VP_KHR_ROADMAP_2022_NAME, + VP_KHR_ROADMAP_2022_SPEC_VERSION}; + + // Check if the profile is supported + VkBool32 supported = VK_FALSE; #ifdef PLATFORM_ANDROID - // Create a vp::ProfileDesc from our VpProfileProperties - vp::ProfileDesc profileDesc = { - appInfo.profile.name, - appInfo.profile.specVersion - }; - - // Use vp::GetProfileSupport instead of vpGetPhysicalDeviceProfileSupport - bool result = vp::GetProfileSupport( - *physicalDevice, // Pass the physical device directly - &profileDesc, // Pass the profile description - &supported // Output parameter for support status - ); + // Create a vp::ProfileDesc from our VpProfileProperties + vp::ProfileDesc profileDesc = { + appInfo.profile.name, + appInfo.profile.specVersion}; + + // Use vp::GetProfileSupport instead of vpGetPhysicalDeviceProfileSupport + bool result = vp::GetProfileSupport( + *physicalDevice, // Pass the physical device directly + &profileDesc, // Pass the profile description + &supported // Output parameter for support status + ); #else - VkResult vk_result = vpGetPhysicalDeviceProfileSupport( - *instance, - *physicalDevice, - &appInfo.profile, - &supported - ); - bool result = vk_result == VK_SUCCESS; + VkResult vk_result = vpGetPhysicalDeviceProfileSupport( + *instance, + *physicalDevice, + &appInfo.profile, + &supported); + bool result = vk_result == VK_SUCCESS; #endif - if (result && supported == VK_TRUE) { - appInfo.profileSupported = true; - LOGI("Using KHR roadmap 2022 profile"); - } else { - appInfo.profileSupported = false; - LOGI("Falling back to traditional rendering (profile not supported)"); - } - } - - // Create logical device - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - float queuePriority = 1.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - - if (appInfo.profileSupported) { - // Enable required features - vk::PhysicalDeviceFeatures2 features2; - vk::PhysicalDeviceFeatures deviceFeatures{}; - deviceFeatures.samplerAnisotropy = VK_TRUE; - deviceFeatures.sampleRateShading = VK_TRUE; - features2.features = deviceFeatures; - - // Enable dynamic rendering - vk::PhysicalDeviceDynamicRenderingFeatures dynamicRenderingFeatures; - dynamicRenderingFeatures.dynamicRendering = VK_TRUE; - features2.pNext = &dynamicRenderingFeatures; - - // Create a vk::DeviceCreateInfo with the required features - vk::DeviceCreateInfo vkDeviceCreateInfo{ - .pNext = &features2, - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(deviceExtensions.size()), - .ppEnabledExtensionNames = deviceExtensions.data() - }; - - // Create the device with the vk::DeviceCreateInfo - device = vk::raii::Device(physicalDevice, vkDeviceCreateInfo); - } else { - // Fallback to manual device creation - vk::PhysicalDeviceFeatures deviceFeatures{}; - deviceFeatures.samplerAnisotropy = VK_TRUE; - deviceFeatures.sampleRateShading = VK_TRUE; - - vk::DeviceCreateInfo createInfo{ - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(deviceExtensions.size()), - .ppEnabledExtensionNames = deviceExtensions.data(), - .pEnabledFeatures = &deviceFeatures - }; - - device = vk::raii::Device(physicalDevice, createInfo); - } - - queue = device.getQueue(queueIndex, 0); - } - - // Create swap chain - void createSwapChain() { - SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); - swapChainExtent = chooseSwapExtent( swapChainSupport.capabilities ); - swapChainSurfaceFormat = chooseSwapSurfaceFormat( swapChainSupport.formats ); - vk::SwapchainCreateInfoKHR swapChainCreateInfo{ .surface = *surface, - .minImageCount = chooseSwapMinImageCount( swapChainSupport.capabilities ), - .imageFormat = swapChainSurfaceFormat.format, - .imageColorSpace = swapChainSurfaceFormat.colorSpace, - .imageExtent = swapChainExtent, - .imageArrayLayers = 1, - .imageUsage = vk::ImageUsageFlagBits::eColorAttachment, - .imageSharingMode = vk::SharingMode::eExclusive, - .preTransform = swapChainSupport.capabilities.currentTransform, - .compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque, - .presentMode = chooseSwapPresentMode( swapChainSupport.presentModes ), - .clipped = true }; - - swapChain = device.createSwapchainKHR(swapChainCreateInfo); - swapChainImages = swapChain.getImages(); - } - - // Create image views - void createImageViews() { - swapChainImageViews.reserve(swapChainImages.size()); - - for (const auto& image : swapChainImages) { - vk::ImageViewCreateInfo createInfo{ - .image = image, - .viewType = vk::ImageViewType::e2D, - .format = swapChainSurfaceFormat.format, - .components = { - .r = vk::ComponentSwizzle::eIdentity, - .g = vk::ComponentSwizzle::eIdentity, - .b = vk::ComponentSwizzle::eIdentity, - .a = vk::ComponentSwizzle::eIdentity - }, - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - - swapChainImageViews.push_back(device.createImageView(createInfo)); - } - } - - // Create render pass - void createRenderPass() { - vk::AttachmentDescription colorAttachment{ - .format = swapChainSurfaceFormat.format, - .samples = vk::SampleCountFlagBits::e1, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .stencilLoadOp = vk::AttachmentLoadOp::eDontCare, - .stencilStoreOp = vk::AttachmentStoreOp::eDontCare, - .initialLayout = vk::ImageLayout::eUndefined, - .finalLayout = vk::ImageLayout::ePresentSrcKHR - }; - - vk::AttachmentReference colorAttachmentRef{ - .attachment = 0, - .layout = vk::ImageLayout::eColorAttachmentOptimal - }; - - vk::SubpassDescription subpass{ - .pipelineBindPoint = vk::PipelineBindPoint::eGraphics, - .colorAttachmentCount = 1, - .pColorAttachments = &colorAttachmentRef - }; - - vk::SubpassDependency dependency{ - .srcSubpass = VK_SUBPASS_EXTERNAL, - .dstSubpass = 0, - .srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput, - .dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput, - .srcAccessMask = vk::AccessFlagBits::eNone, - .dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite - }; - - vk::RenderPassCreateInfo renderPassInfo{ - .attachmentCount = 1, - .pAttachments = &colorAttachment, - .subpassCount = 1, - .pSubpasses = &subpass, - .dependencyCount = 1, - .pDependencies = &dependency - }; - - renderPass = device.createRenderPass(renderPassInfo); - } - - // Create descriptor set layout - void createDescriptorSetLayout() { - vk::DescriptorSetLayoutBinding uboLayoutBinding{ - .binding = 0, - .descriptorType = vk::DescriptorType::eUniformBuffer, - .descriptorCount = 1, - .stageFlags = vk::ShaderStageFlagBits::eVertex - }; - - vk::DescriptorSetLayoutBinding samplerLayoutBinding{ - .binding = 1, - .descriptorType = vk::DescriptorType::eCombinedImageSampler, - .descriptorCount = 1, - .stageFlags = vk::ShaderStageFlagBits::eFragment - }; - - std::array bindings = {uboLayoutBinding, samplerLayoutBinding}; - - vk::DescriptorSetLayoutCreateInfo layoutInfo{ - .bindingCount = static_cast(bindings.size()), - .pBindings = bindings.data() - }; - - descriptorSetLayout = device.createDescriptorSetLayout(layoutInfo); - } - - // Create graphics pipeline - void createGraphicsPipeline() { - // Load shader code from asset files - LOGI("Loading shaders from assets"); - - // Load shader files using cross-platform function + if (result && supported == VK_TRUE) + { + appInfo.profileSupported = true; + LOGI("Using KHR roadmap 2022 profile"); + } + else + { + appInfo.profileSupported = false; + LOGI("Falling back to traditional rendering (profile not supported)"); + } + } + + // Create logical device + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + float queuePriority = 1.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + + if (appInfo.profileSupported) + { + // Enable required features + vk::PhysicalDeviceFeatures2 features2; + vk::PhysicalDeviceFeatures deviceFeatures{}; + deviceFeatures.samplerAnisotropy = VK_TRUE; + deviceFeatures.sampleRateShading = VK_TRUE; + features2.features = deviceFeatures; + + // Enable dynamic rendering + vk::PhysicalDeviceDynamicRenderingFeatures dynamicRenderingFeatures; + dynamicRenderingFeatures.dynamicRendering = VK_TRUE; + features2.pNext = &dynamicRenderingFeatures; + + // Create a vk::DeviceCreateInfo with the required features + vk::DeviceCreateInfo vkDeviceCreateInfo{ + .pNext = &features2, + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(deviceExtensions.size()), + .ppEnabledExtensionNames = deviceExtensions.data()}; + + // Create the device with the vk::DeviceCreateInfo + device = vk::raii::Device(physicalDevice, vkDeviceCreateInfo); + } + else + { + // Fallback to manual device creation + vk::PhysicalDeviceFeatures deviceFeatures{}; + deviceFeatures.samplerAnisotropy = VK_TRUE; + deviceFeatures.sampleRateShading = VK_TRUE; + + vk::DeviceCreateInfo createInfo{ + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(deviceExtensions.size()), + .ppEnabledExtensionNames = deviceExtensions.data(), + .pEnabledFeatures = &deviceFeatures}; + + device = vk::raii::Device(physicalDevice, createInfo); + } + + queue = device.getQueue(queueIndex, 0); + } + + // Create swap chain + void createSwapChain() + { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + swapChainExtent = chooseSwapExtent(swapChainSupport.capabilities); + swapChainSurfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + vk::SwapchainCreateInfoKHR swapChainCreateInfo{.surface = *surface, + .minImageCount = chooseSwapMinImageCount(swapChainSupport.capabilities), + .imageFormat = swapChainSurfaceFormat.format, + .imageColorSpace = swapChainSurfaceFormat.colorSpace, + .imageExtent = swapChainExtent, + .imageArrayLayers = 1, + .imageUsage = vk::ImageUsageFlagBits::eColorAttachment, + .imageSharingMode = vk::SharingMode::eExclusive, + .preTransform = swapChainSupport.capabilities.currentTransform, + .compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque, + .presentMode = chooseSwapPresentMode(swapChainSupport.presentModes), + .clipped = true}; + + swapChain = device.createSwapchainKHR(swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + // Create image views + void createImageViews() + { + swapChainImageViews.reserve(swapChainImages.size()); + + for (const auto &image : swapChainImages) + { + vk::ImageViewCreateInfo createInfo{ + .image = image, + .viewType = vk::ImageViewType::e2D, + .format = swapChainSurfaceFormat.format, + .components = { + .r = vk::ComponentSwizzle::eIdentity, + .g = vk::ComponentSwizzle::eIdentity, + .b = vk::ComponentSwizzle::eIdentity, + .a = vk::ComponentSwizzle::eIdentity}, + .subresourceRange = {.aspectMask = vk::ImageAspectFlagBits::eColor, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1}}; + + swapChainImageViews.push_back(device.createImageView(createInfo)); + } + } + + // Create render pass + void createRenderPass() + { + vk::AttachmentDescription colorAttachment{ + .format = swapChainSurfaceFormat.format, + .samples = vk::SampleCountFlagBits::e1, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .stencilLoadOp = vk::AttachmentLoadOp::eDontCare, + .stencilStoreOp = vk::AttachmentStoreOp::eDontCare, + .initialLayout = vk::ImageLayout::eUndefined, + .finalLayout = vk::ImageLayout::ePresentSrcKHR}; + + vk::AttachmentReference colorAttachmentRef{ + .attachment = 0, + .layout = vk::ImageLayout::eColorAttachmentOptimal}; + + vk::SubpassDescription subpass{ + .pipelineBindPoint = vk::PipelineBindPoint::eGraphics, + .colorAttachmentCount = 1, + .pColorAttachments = &colorAttachmentRef}; + + vk::SubpassDependency dependency{ + .srcSubpass = VK_SUBPASS_EXTERNAL, + .dstSubpass = 0, + .srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput, + .dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput, + .srcAccessMask = vk::AccessFlagBits::eNone, + .dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite}; + + vk::RenderPassCreateInfo renderPassInfo{ + .attachmentCount = 1, + .pAttachments = &colorAttachment, + .subpassCount = 1, + .pSubpasses = &subpass, + .dependencyCount = 1, + .pDependencies = &dependency}; + + renderPass = device.createRenderPass(renderPassInfo); + } + + // Create descriptor set layout + void createDescriptorSetLayout() + { + vk::DescriptorSetLayoutBinding uboLayoutBinding{ + .binding = 0, + .descriptorType = vk::DescriptorType::eUniformBuffer, + .descriptorCount = 1, + .stageFlags = vk::ShaderStageFlagBits::eVertex}; + + vk::DescriptorSetLayoutBinding samplerLayoutBinding{ + .binding = 1, + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .descriptorCount = 1, + .stageFlags = vk::ShaderStageFlagBits::eFragment}; + + std::array bindings = {uboLayoutBinding, samplerLayoutBinding}; + + vk::DescriptorSetLayoutCreateInfo layoutInfo{ + .bindingCount = static_cast(bindings.size()), + .pBindings = bindings.data()}; + + descriptorSetLayout = device.createDescriptorSetLayout(layoutInfo); + } + + // Create graphics pipeline + void createGraphicsPipeline() + { + // Load shader code from asset files + LOGI("Loading shaders from assets"); + + // Load shader files using cross-platform function #if PLATFORM_ANDROID - std::optional optionalAssetManager = assetManager; + std::optional optionalAssetManager = assetManager; #else - std::optional optionalAssetManager = std::nullopt; + std::optional optionalAssetManager = std::nullopt; #endif - std::vector vertShaderCode = readFile("shaders/vert.spv", optionalAssetManager); - std::vector fragShaderCode = readFile("shaders/frag.spv", optionalAssetManager); - - LOGI("Shaders loaded successfully"); - - // Create shader modules - vk::ShaderModuleCreateInfo vertShaderModuleInfo{ - .codeSize = vertShaderCode.size(), - .pCode = reinterpret_cast(vertShaderCode.data()) - }; - vk::raii::ShaderModule vertShaderModule = device.createShaderModule(vertShaderModuleInfo); - - vk::ShaderModuleCreateInfo fragShaderModuleInfo{ - .codeSize = fragShaderCode.size(), - .pCode = reinterpret_cast(fragShaderCode.data()) - }; - vk::raii::ShaderModule fragShaderModule = device.createShaderModule(fragShaderModuleInfo); - - // Create shader stages - vk::PipelineShaderStageCreateInfo shaderStages[] = { - { - .stage = vk::ShaderStageFlagBits::eVertex, - .module = *vertShaderModule, - .pName = "main" - }, - { - .stage = vk::ShaderStageFlagBits::eFragment, - .module = *fragShaderModule, - .pName = "main" - } - }; - - // Vertex input - auto bindingDescription = Vertex::getBindingDescription(); - auto attributeDescriptions = Vertex::getAttributeDescriptions(); - - vk::PipelineVertexInputStateCreateInfo vertexInputInfo{ - .vertexBindingDescriptionCount = 1, - .pVertexBindingDescriptions = &bindingDescription, - .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), - .pVertexAttributeDescriptions = attributeDescriptions.data() - }; - - // Input assembly - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ - .topology = vk::PrimitiveTopology::eTriangleList, - .primitiveRestartEnable = VK_FALSE - }; - - // Viewport and scissor - vk::PipelineViewportStateCreateInfo viewportState{ - .viewportCount = 1, - .scissorCount = 1 - }; - - // Rasterization - vk::PipelineRasterizationStateCreateInfo rasterizer{ - .depthClampEnable = VK_FALSE, - .rasterizerDiscardEnable = VK_FALSE, - .polygonMode = vk::PolygonMode::eFill, - .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eCounterClockwise, - .depthBiasEnable = VK_FALSE, - .lineWidth = 1.0f - }; - - // Multisampling - vk::PipelineMultisampleStateCreateInfo multisampling{ - .rasterizationSamples = vk::SampleCountFlagBits::e1, - .sampleShadingEnable = VK_FALSE - }; - - // Color blending - vk::PipelineColorBlendAttachmentState colorBlendAttachment{ - .blendEnable = VK_FALSE, - .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA - }; - - vk::PipelineColorBlendStateCreateInfo colorBlending{ - .logicOpEnable = VK_FALSE, - .logicOp = vk::LogicOp::eCopy, - .attachmentCount = 1, - .pAttachments = &colorBlendAttachment - }; - - // Dynamic states - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - - vk::PipelineDynamicStateCreateInfo dynamicState{ - .dynamicStateCount = static_cast(dynamicStates.size()), - .pDynamicStates = dynamicStates.data() - }; - - // Pipeline layout - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ - .setLayoutCount = 1, - .pSetLayouts = &*descriptorSetLayout - }; - - pipelineLayout = device.createPipelineLayout(pipelineLayoutInfo); - - // Create the graphics pipeline - vk::GraphicsPipelineCreateInfo pipelineInfo{ - .stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pDepthStencilState = nullptr, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = *pipelineLayout, - .renderPass = *renderPass, - .subpass = 0 - }; - - // Create the pipeline - graphicsPipeline = device.createGraphicsPipeline(nullptr, pipelineInfo); - } - - // Create framebuffers - void createFramebuffers() { - swapChainFramebuffers.reserve(swapChainImageViews.size()); - - for (size_t i = 0; i < swapChainImageViews.size(); i++) { - vk::ImageView attachments[] = { - *swapChainImageViews[i] - }; - - vk::FramebufferCreateInfo framebufferInfo{ - .renderPass = *renderPass, - .attachmentCount = 1, - .pAttachments = attachments, - .width = swapChainExtent.width, - .height = swapChainExtent.height, - .layers = 1 - }; - - swapChainFramebuffers.push_back(device.createFramebuffer(framebufferInfo)); - } - } - - // Create command pool - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{ - .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex - }; - - commandPool = device.createCommandPool(poolInfo); - } - - // Create texture image - void createTextureImage() { - // Load texture image - int texWidth, texHeight, texChannels; - stbi_uc* pixels = nullptr; + std::vector vertShaderCode = readFile("shaders/vert.spv", optionalAssetManager); + std::vector fragShaderCode = readFile("shaders/frag.spv", optionalAssetManager); + + LOGI("Shaders loaded successfully"); + + // Create shader modules + vk::ShaderModuleCreateInfo vertShaderModuleInfo{ + .codeSize = vertShaderCode.size(), + .pCode = reinterpret_cast(vertShaderCode.data())}; + vk::raii::ShaderModule vertShaderModule = device.createShaderModule(vertShaderModuleInfo); + + vk::ShaderModuleCreateInfo fragShaderModuleInfo{ + .codeSize = fragShaderCode.size(), + .pCode = reinterpret_cast(fragShaderCode.data())}; + vk::raii::ShaderModule fragShaderModule = device.createShaderModule(fragShaderModuleInfo); + + // Create shader stages + vk::PipelineShaderStageCreateInfo shaderStages[] = { + {.stage = vk::ShaderStageFlagBits::eVertex, + .module = *vertShaderModule, + .pName = "main"}, + {.stage = vk::ShaderStageFlagBits::eFragment, + .module = *fragShaderModule, + .pName = "main"}}; + + // Vertex input + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{ + .vertexBindingDescriptionCount = 1, + .pVertexBindingDescriptions = &bindingDescription, + .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), + .pVertexAttributeDescriptions = attributeDescriptions.data()}; + + // Input assembly + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ + .topology = vk::PrimitiveTopology::eTriangleList, + .primitiveRestartEnable = VK_FALSE}; + + // Viewport and scissor + vk::PipelineViewportStateCreateInfo viewportState{ + .viewportCount = 1, + .scissorCount = 1}; + + // Rasterization + vk::PipelineRasterizationStateCreateInfo rasterizer{ + .depthClampEnable = VK_FALSE, + .rasterizerDiscardEnable = VK_FALSE, + .polygonMode = vk::PolygonMode::eFill, + .cullMode = vk::CullModeFlagBits::eBack, + .frontFace = vk::FrontFace::eCounterClockwise, + .depthBiasEnable = VK_FALSE, + .lineWidth = 1.0f}; + + // Multisampling + vk::PipelineMultisampleStateCreateInfo multisampling{ + .rasterizationSamples = vk::SampleCountFlagBits::e1, + .sampleShadingEnable = VK_FALSE}; + + // Color blending + vk::PipelineColorBlendAttachmentState colorBlendAttachment{ + .blendEnable = VK_FALSE, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA}; + + vk::PipelineColorBlendStateCreateInfo colorBlending{ + .logicOpEnable = VK_FALSE, + .logicOp = vk::LogicOp::eCopy, + .attachmentCount = 1, + .pAttachments = &colorBlendAttachment}; + + // Dynamic states + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + + vk::PipelineDynamicStateCreateInfo dynamicState{ + .dynamicStateCount = static_cast(dynamicStates.size()), + .pDynamicStates = dynamicStates.data()}; + + // Pipeline layout + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ + .setLayoutCount = 1, + .pSetLayouts = &*descriptorSetLayout}; + + pipelineLayout = device.createPipelineLayout(pipelineLayoutInfo); + + // Create the graphics pipeline + vk::GraphicsPipelineCreateInfo pipelineInfo{ + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pDepthStencilState = nullptr, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = *pipelineLayout, + .renderPass = *renderPass, + .subpass = 0}; + + // Create the pipeline + graphicsPipeline = device.createGraphicsPipeline(nullptr, pipelineInfo); + } + + // Create framebuffers + void createFramebuffers() + { + swapChainFramebuffers.reserve(swapChainImageViews.size()); + + for (size_t i = 0; i < swapChainImageViews.size(); i++) + { + vk::ImageView attachments[] = { + *swapChainImageViews[i]}; + + vk::FramebufferCreateInfo framebufferInfo{ + .renderPass = *renderPass, + .attachmentCount = 1, + .pAttachments = attachments, + .width = swapChainExtent.width, + .height = swapChainExtent.height, + .layers = 1}; + + swapChainFramebuffers.push_back(device.createFramebuffer(framebufferInfo)); + } + } + + // Create command pool + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{ + .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex}; + + commandPool = device.createCommandPool(poolInfo); + } + + // Create texture image + void createTextureImage() + { + // Load texture image + int texWidth, texHeight, texChannels; + stbi_uc *pixels = nullptr; #if PLATFORM_ANDROID - // Load image from Android assets - std::optional optionalAssetManager = assetManager; - std::vector imageData = readFile(TEXTURE_PATH, optionalAssetManager); - pixels = stbi_load_from_memory( - reinterpret_cast(imageData.data()), + // Load image from Android assets + std::optional optionalAssetManager = assetManager; + std::vector imageData = readFile(TEXTURE_PATH, optionalAssetManager); + pixels = stbi_load_from_memory( + reinterpret_cast(imageData.data()), static_cast(imageData.size()), - &texWidth, &texHeight, &texChannels, STBI_rgb_alpha - ); + &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); #else - // Load image from filesystem - pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + // Load image from filesystem + pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); #endif - if (!pixels) { - throw std::runtime_error("Failed to load texture image: " + TEXTURE_PATH); - } - - LOG_INFO("Texture loaded successfully"); - - vk::DeviceSize imageSize = texWidth * texHeight * 4; - - // Create staging buffer - vk::raii::Buffer stagingBuffer = nullptr; - vk::raii::DeviceMemory stagingBufferMemory = nullptr; - - createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - // Copy pixel data to staging buffer - void* data; - data = stagingBufferMemory.mapMemory(0, imageSize); - memcpy(data, pixels, static_cast(imageSize)); - stagingBufferMemory.unmapMemory(); - - // Free the pixel data - if (pixels != nullptr) { - stbi_image_free(pixels); - } - - // Create image - vk::ImageCreateInfo imageInfo{ - .imageType = vk::ImageType::e2D, - .format = vk::Format::eR8G8B8A8Srgb, - .extent = { - .width = static_cast(texWidth), - .height = static_cast(texHeight), - .depth = 1 - }, - .mipLevels = 1, - .arrayLayers = 1, - .samples = vk::SampleCountFlagBits::e1, - .tiling = vk::ImageTiling::eOptimal, - .usage = vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, - .sharingMode = vk::SharingMode::eExclusive, - .initialLayout = vk::ImageLayout::eUndefined - }; - - textureImage = device.createImage(imageInfo); - - // Allocate memory for the image - vk::MemoryRequirements memRequirements = textureImage.getMemoryRequirements(); - - vk::MemoryAllocateInfo allocInfo{ - .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, vk::MemoryPropertyFlagBits::eDeviceLocal) - }; - - textureImageMemory = device.allocateMemory(allocInfo); - textureImage.bindMemory(*textureImageMemory, 0); - - // Transition image layout and copy buffer to image - transitionImageLayout(textureImage, vk::Format::eR8G8B8A8Srgb, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal); - copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); - transitionImageLayout(textureImage, vk::Format::eR8G8B8A8Srgb, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal); - } - - // Create texture image view - void createTextureImageView() { - textureImageView = createImageView(textureImage, vk::Format::eR8G8B8A8Srgb); - } - - // Create texture sampler - void createTextureSampler() { - vk::SamplerCreateInfo samplerInfo{ - .magFilter = vk::Filter::eLinear, - .minFilter = vk::Filter::eLinear, - .mipmapMode = vk::SamplerMipmapMode::eLinear, - .addressModeU = vk::SamplerAddressMode::eRepeat, - .addressModeV = vk::SamplerAddressMode::eRepeat, - .addressModeW = vk::SamplerAddressMode::eRepeat, - .anisotropyEnable = VK_TRUE, - .maxAnisotropy = 16.0f, - .compareEnable = VK_FALSE, - .compareOp = vk::CompareOp::eAlways, - .borderColor = vk::BorderColor::eIntOpaqueBlack, - .unnormalizedCoordinates = VK_FALSE - }; - - textureSampler = device.createSampler(samplerInfo); - } - - // Load model - void loadModel() { - tinyobj::attrib_t attrib; - std::vector shapes; - std::vector materials; - std::string warn, err; + if (!pixels) + { + throw std::runtime_error("Failed to load texture image: " + TEXTURE_PATH); + } + + LOG_INFO("Texture loaded successfully"); + + vk::DeviceSize imageSize = texWidth * texHeight * 4; + + // Create staging buffer + vk::raii::Buffer stagingBuffer = nullptr; + vk::raii::DeviceMemory stagingBufferMemory = nullptr; + + createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + // Copy pixel data to staging buffer + void *data; + data = stagingBufferMemory.mapMemory(0, imageSize); + memcpy(data, pixels, static_cast(imageSize)); + stagingBufferMemory.unmapMemory(); + + // Free the pixel data + if (pixels != nullptr) + { + stbi_image_free(pixels); + } + + // Create image + vk::ImageCreateInfo imageInfo{ + .imageType = vk::ImageType::e2D, + .format = vk::Format::eR8G8B8A8Srgb, + .extent = { + .width = static_cast(texWidth), + .height = static_cast(texHeight), + .depth = 1}, + .mipLevels = 1, + .arrayLayers = 1, + .samples = vk::SampleCountFlagBits::e1, + .tiling = vk::ImageTiling::eOptimal, + .usage = vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, + .sharingMode = vk::SharingMode::eExclusive, + .initialLayout = vk::ImageLayout::eUndefined}; + + textureImage = device.createImage(imageInfo); + + // Allocate memory for the image + vk::MemoryRequirements memRequirements = textureImage.getMemoryRequirements(); + + vk::MemoryAllocateInfo allocInfo{ + .allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, vk::MemoryPropertyFlagBits::eDeviceLocal)}; + + textureImageMemory = device.allocateMemory(allocInfo); + textureImage.bindMemory(*textureImageMemory, 0); + + // Transition image layout and copy buffer to image + transitionImageLayout(textureImage, vk::Format::eR8G8B8A8Srgb, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); + transitionImageLayout(textureImage, vk::Format::eR8G8B8A8Srgb, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal); + } + + // Create texture image view + void createTextureImageView() + { + textureImageView = createImageView(textureImage, vk::Format::eR8G8B8A8Srgb); + } + + // Create texture sampler + void createTextureSampler() + { + vk::SamplerCreateInfo samplerInfo{ + .magFilter = vk::Filter::eLinear, + .minFilter = vk::Filter::eLinear, + .mipmapMode = vk::SamplerMipmapMode::eLinear, + .addressModeU = vk::SamplerAddressMode::eRepeat, + .addressModeV = vk::SamplerAddressMode::eRepeat, + .addressModeW = vk::SamplerAddressMode::eRepeat, + .anisotropyEnable = VK_TRUE, + .maxAnisotropy = 16.0f, + .compareEnable = VK_FALSE, + .compareOp = vk::CompareOp::eAlways, + .borderColor = vk::BorderColor::eIntOpaqueBlack, + .unnormalizedCoordinates = VK_FALSE}; + + textureSampler = device.createSampler(samplerInfo); + } + + // Load model + void loadModel() + { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn, err; #if PLATFORM_ANDROID - // Load OBJ file from Android assets - std::optional optionalAssetManager = assetManager; - std::vector objData = readFile(MODEL_PATH, optionalAssetManager); - std::string objString(objData.begin(), objData.end()); - std::istringstream objStream(objString); - - if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, &objStream)) { - throw std::runtime_error("Failed to load model: " + MODEL_PATH + " - " + warn + err); - } + // Load OBJ file from Android assets + std::optional optionalAssetManager = assetManager; + std::vector objData = readFile(MODEL_PATH, optionalAssetManager); + std::string objString(objData.begin(), objData.end()); + std::istringstream objStream(objString); + + if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, &objStream)) + { + throw std::runtime_error("Failed to load model: " + MODEL_PATH + " - " + warn + err); + } #else - // Load OBJ file from filesystem - if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, MODEL_PATH.c_str())) { - throw std::runtime_error("Failed to load model: " + MODEL_PATH + " - " + warn + err); - } + // Load OBJ file from filesystem + if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, MODEL_PATH.c_str())) + { + throw std::runtime_error("Failed to load model: " + MODEL_PATH + " - " + warn + err); + } #endif - std::unordered_map uniqueVertices{}; - - for (const auto& shape : shapes) { - for (const auto& index : shape.mesh.indices) { - Vertex vertex{}; - - vertex.pos = { - attrib.vertices[3 * index.vertex_index + 0], - attrib.vertices[3 * index.vertex_index + 1], - attrib.vertices[3 * index.vertex_index + 2] - }; - - vertex.texCoord = { - attrib.texcoords[2 * index.texcoord_index + 0], - 1.0f - attrib.texcoords[2 * index.texcoord_index + 1] - }; - - vertex.color = {1.0f, 1.0f, 1.0f}; - - if (uniqueVertices.count(vertex) == 0) { - uniqueVertices[vertex] = static_cast(vertices.size()); - vertices.push_back(vertex); - } - - indices.push_back(uniqueVertices[vertex]); - } - } - - LOG_INFO("Model loaded successfully"); - } - - // Create vertex buffer - void createVertexBuffer() { - vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); - - vk::raii::Buffer stagingBuffer = nullptr; - vk::raii::DeviceMemory stagingBufferMemory = nullptr; - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data; - data = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(data, vertices.data(), (size_t) bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); - - copyBuffer(stagingBuffer, vertexBuffer, bufferSize); - } - - // Create index buffer - void createIndexBuffer() { - vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); - - vk::raii::Buffer stagingBuffer = nullptr; - vk::raii::DeviceMemory stagingBufferMemory = nullptr; - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data; - data = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(data, indices.data(), (size_t) bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); - - copyBuffer(stagingBuffer, indexBuffer, bufferSize); - } - - // Create uniform buffers - void createUniformBuffers() { - vk::DeviceSize bufferSize = sizeof(UniformBufferObject); - - uniformBuffers.clear(); - uniformBuffersMemory.clear(); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - uniformBuffers.push_back(nullptr); - uniformBuffersMemory.push_back(nullptr); - } - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, uniformBuffers[i], uniformBuffersMemory[i]); - } - } - - // Create descriptor pool - void createDescriptorPool() { - std::array poolSizes = { - vk::DescriptorPoolSize{ - .type = vk::DescriptorType::eUniformBuffer, - .descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT) - }, - vk::DescriptorPoolSize{ - .type = vk::DescriptorType::eCombinedImageSampler, - .descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT) - } - }; - - vk::DescriptorPoolCreateInfo poolInfo{ - .maxSets = static_cast(MAX_FRAMES_IN_FLIGHT), - .poolSizeCount = static_cast(poolSizes.size()), - .pPoolSizes = poolSizes.data() - }; - - descriptorPool = device.createDescriptorPool(poolInfo); - } - - // Create descriptor sets - void createDescriptorSets() { - std::vector layouts(MAX_FRAMES_IN_FLIGHT, *descriptorSetLayout); - vk::DescriptorSetAllocateInfo allocInfo{ - .descriptorPool = *descriptorPool, - .descriptorSetCount = static_cast(MAX_FRAMES_IN_FLIGHT), - .pSetLayouts = layouts.data() - }; - - descriptorSets = device.allocateDescriptorSets(allocInfo); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DescriptorBufferInfo bufferInfo{ - .buffer = *uniformBuffers[i], - .offset = 0, - .range = sizeof(UniformBufferObject) - }; - - vk::DescriptorImageInfo imageInfo{ - .sampler = *textureSampler, - .imageView = *textureImageView, - .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal - }; - - std::array descriptorWrites = { - vk::WriteDescriptorSet{ - .dstSet = *descriptorSets[i], - .dstBinding = 0, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eUniformBuffer, - .pBufferInfo = &bufferInfo - }, - vk::WriteDescriptorSet{ - .dstSet = *descriptorSets[i], - .dstBinding = 1, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eCombinedImageSampler, - .pImageInfo = &imageInfo - } - }; - - device.updateDescriptorSets(descriptorWrites, nullptr); - } - } - - // Create command buffers - void createCommandBuffers() { - commandBuffers.reserve(MAX_FRAMES_IN_FLIGHT); - - vk::CommandBufferAllocateInfo allocInfo{ - .commandPool = *commandPool, - .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = static_cast(MAX_FRAMES_IN_FLIGHT) - }; - - commandBuffers = device.allocateCommandBuffers(allocInfo); - } - - // Create synchronization objects - void createSyncObjects() { - imageAvailableSemaphores.reserve(MAX_FRAMES_IN_FLIGHT); - renderFinishedSemaphores.reserve(MAX_FRAMES_IN_FLIGHT); - inFlightFences.reserve(MAX_FRAMES_IN_FLIGHT); - - vk::SemaphoreCreateInfo semaphoreInfo{}; - vk::FenceCreateInfo fenceInfo{ - .flags = vk::FenceCreateFlagBits::eSignaled - }; - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - imageAvailableSemaphores.push_back(device.createSemaphore(semaphoreInfo)); - renderFinishedSemaphores.push_back(device.createSemaphore(semaphoreInfo)); - inFlightFences.push_back(device.createFence(fenceInfo)); - } - } - - // Clean up swap chain - void cleanupSwapChain() { - for (auto& framebuffer : swapChainFramebuffers) { - framebuffer = nullptr; - } - - for (auto& imageView : swapChainImageViews) { - imageView = nullptr; - } - - swapChain = nullptr; - } - - // Record command buffer - void recordCommandBuffer(vk::raii::CommandBuffer& commandBuffer, uint32_t imageIndex) { - vk::CommandBufferBeginInfo beginInfo{}; - commandBuffer.begin(beginInfo); - - vk::RenderPassBeginInfo renderPassInfo{ - .renderPass = *renderPass, - .framebuffer = *swapChainFramebuffers[imageIndex], - .renderArea = { - .offset = {0, 0}, - .extent = swapChainExtent - } - }; - - vk::ClearValue clearColor; - clearColor.color.float32 = std::array{0.0f, 0.0f, 0.0f, 1.0f}; - renderPassInfo.clearValueCount = 1; - renderPassInfo.pClearValues = &clearColor; - - commandBuffer.beginRenderPass(renderPassInfo, vk::SubpassContents::eInline); - commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - - vk::Viewport viewport{ - .x = 0.0f, - .y = 0.0f, - .width = static_cast(swapChainExtent.width), - .height = static_cast(swapChainExtent.height), - .minDepth = 0.0f, - .maxDepth = 1.0f - }; - commandBuffer.setViewport(0, viewport); - - vk::Rect2D scissor{ - .offset = {0, 0}, - .extent = swapChainExtent - }; - commandBuffer.setScissor(0, scissor); - - commandBuffer.bindVertexBuffers(0, {*vertexBuffer}, {0}); - commandBuffer.bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint32); - commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, *pipelineLayout, 0, {*descriptorSets[currentFrame]}, nullptr); - commandBuffer.drawIndexed(static_cast(indices.size()), 1, 0, 0, 0); - - commandBuffer.endRenderPass(); - commandBuffer.end(); - } - - // Draw frame - void drawFrame() { - static_cast(device.waitForFences({*inFlightFences[currentFrame]}, VK_TRUE, FenceTimeout)); - - uint32_t imageIndex; - try { - auto [result, idx] = swapChain.acquireNextImage(FenceTimeout, *imageAvailableSemaphores[currentFrame]); - imageIndex = idx; - } catch (vk::OutOfDateKHRError&) { - recreateSwapChain(); - return; - } - - // Update uniform buffer with current transformation - updateUniformBuffer(currentFrame); - - device.resetFences({*inFlightFences[currentFrame]}); - - commandBuffers[currentFrame].reset(); - recordCommandBuffer(commandBuffers[currentFrame], imageIndex); - - vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); - const vk::SubmitInfo submitInfo{ - .waitSemaphoreCount = 1, - .pWaitSemaphores = &*imageAvailableSemaphores[currentFrame], - .pWaitDstStageMask = &waitDestinationStageMask, - .commandBufferCount = 1, - .pCommandBuffers = &*commandBuffers[currentFrame], - .signalSemaphoreCount = 1, - .pSignalSemaphores = &*renderFinishedSemaphores[currentFrame] - }; - queue.submit(submitInfo, *inFlightFences[currentFrame]); - - const vk::PresentInfoKHR presentInfoKHR{ - .waitSemaphoreCount = 1, - .pWaitSemaphores = &*renderFinishedSemaphores[currentFrame], - .swapchainCount = 1, - .pSwapchains = &*swapChain, - .pImageIndices = &imageIndex - }; - - vk::Result result; - try { - result = queue.presentKHR(presentInfoKHR); - } catch (vk::OutOfDateKHRError&) { - result = vk::Result::eErrorOutOfDateKHR; - } - - if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { - framebufferResized = false; - recreateSwapChain(); - } else if (result != vk::Result::eSuccess) { - throw std::runtime_error("Failed to present swap chain image"); - } - - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; - } - - // Recreate swap chain - void recreateSwapChain() { - // Wait for device to finish operations - device.waitIdle(); - - // Clean up old swap chain - cleanupSwapChain(); - - // Create new swap chain - createSwapChain(); - createImageViews(); - createFramebuffers(); - } - - // Get required extensions - std::vector getRequiredExtensions() { + std::unordered_map uniqueVertices{}; + + for (const auto &shape : shapes) + { + for (const auto &index : shape.mesh.indices) + { + Vertex vertex{}; + + vertex.pos = { + attrib.vertices[3 * index.vertex_index + 0], + attrib.vertices[3 * index.vertex_index + 1], + attrib.vertices[3 * index.vertex_index + 2]}; + + vertex.texCoord = { + attrib.texcoords[2 * index.texcoord_index + 0], + 1.0f - attrib.texcoords[2 * index.texcoord_index + 1]}; + + vertex.color = {1.0f, 1.0f, 1.0f}; + + if (uniqueVertices.count(vertex) == 0) + { + uniqueVertices[vertex] = static_cast(vertices.size()); + vertices.push_back(vertex); + } + + indices.push_back(uniqueVertices[vertex]); + } + } + + LOG_INFO("Model loaded successfully"); + } + + // Create vertex buffer + void createVertexBuffer() + { + vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + + vk::raii::Buffer stagingBuffer = nullptr; + vk::raii::DeviceMemory stagingBufferMemory = nullptr; + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data; + data = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(data, vertices.data(), (size_t) bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + } + + // Create index buffer + void createIndexBuffer() + { + vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + + vk::raii::Buffer stagingBuffer = nullptr; + vk::raii::DeviceMemory stagingBufferMemory = nullptr; + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data; + data = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(data, indices.data(), (size_t) bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); + + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + } + + // Create uniform buffers + void createUniformBuffers() + { + vk::DeviceSize bufferSize = sizeof(UniformBufferObject); + + uniformBuffers.clear(); + uniformBuffersMemory.clear(); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + uniformBuffers.push_back(nullptr); + uniformBuffersMemory.push_back(nullptr); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, uniformBuffers[i], uniformBuffersMemory[i]); + } + } + + // Create descriptor pool + void createDescriptorPool() + { + std::array poolSizes = { + vk::DescriptorPoolSize{ + .type = vk::DescriptorType::eUniformBuffer, + .descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT)}, + vk::DescriptorPoolSize{ + .type = vk::DescriptorType::eCombinedImageSampler, + .descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT)}}; + + vk::DescriptorPoolCreateInfo poolInfo{ + .maxSets = static_cast(MAX_FRAMES_IN_FLIGHT), + .poolSizeCount = static_cast(poolSizes.size()), + .pPoolSizes = poolSizes.data()}; + + descriptorPool = device.createDescriptorPool(poolInfo); + } + + // Create descriptor sets + void createDescriptorSets() + { + std::vector layouts(MAX_FRAMES_IN_FLIGHT, *descriptorSetLayout); + vk::DescriptorSetAllocateInfo allocInfo{ + .descriptorPool = *descriptorPool, + .descriptorSetCount = static_cast(MAX_FRAMES_IN_FLIGHT), + .pSetLayouts = layouts.data()}; + + descriptorSets = device.allocateDescriptorSets(allocInfo); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DescriptorBufferInfo bufferInfo{ + .buffer = *uniformBuffers[i], + .offset = 0, + .range = sizeof(UniformBufferObject)}; + + vk::DescriptorImageInfo imageInfo{ + .sampler = *textureSampler, + .imageView = *textureImageView, + .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal}; + + std::array descriptorWrites = { + vk::WriteDescriptorSet{ + .dstSet = *descriptorSets[i], + .dstBinding = 0, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eUniformBuffer, + .pBufferInfo = &bufferInfo}, + vk::WriteDescriptorSet{ + .dstSet = *descriptorSets[i], + .dstBinding = 1, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .pImageInfo = &imageInfo}}; + + device.updateDescriptorSets(descriptorWrites, nullptr); + } + } + + // Create command buffers + void createCommandBuffers() + { + commandBuffers.reserve(MAX_FRAMES_IN_FLIGHT); + + vk::CommandBufferAllocateInfo allocInfo{ + .commandPool = *commandPool, + .level = vk::CommandBufferLevel::ePrimary, + .commandBufferCount = static_cast(MAX_FRAMES_IN_FLIGHT)}; + + commandBuffers = device.allocateCommandBuffers(allocInfo); + } + + // Create synchronization objects + void createSyncObjects() + { + imageAvailableSemaphores.reserve(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.reserve(MAX_FRAMES_IN_FLIGHT); + inFlightFences.reserve(MAX_FRAMES_IN_FLIGHT); + + vk::SemaphoreCreateInfo semaphoreInfo{}; + vk::FenceCreateInfo fenceInfo{ + .flags = vk::FenceCreateFlagBits::eSignaled}; + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + imageAvailableSemaphores.push_back(device.createSemaphore(semaphoreInfo)); + renderFinishedSemaphores.push_back(device.createSemaphore(semaphoreInfo)); + inFlightFences.push_back(device.createFence(fenceInfo)); + } + } + + // Clean up swap chain + void cleanupSwapChain() + { + for (auto &framebuffer : swapChainFramebuffers) + { + framebuffer = nullptr; + } + + for (auto &imageView : swapChainImageViews) + { + imageView = nullptr; + } + + swapChain = nullptr; + } + + // Record command buffer + void recordCommandBuffer(vk::raii::CommandBuffer &commandBuffer, uint32_t imageIndex) + { + vk::CommandBufferBeginInfo beginInfo{}; + commandBuffer.begin(beginInfo); + + vk::RenderPassBeginInfo renderPassInfo{ + .renderPass = *renderPass, + .framebuffer = *swapChainFramebuffers[imageIndex], + .renderArea = { + .offset = {0, 0}, + .extent = swapChainExtent}}; + + vk::ClearValue clearColor; + clearColor.color.float32 = std::array{0.0f, 0.0f, 0.0f, 1.0f}; + renderPassInfo.clearValueCount = 1; + renderPassInfo.pClearValues = &clearColor; + + commandBuffer.beginRenderPass(renderPassInfo, vk::SubpassContents::eInline); + commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + + vk::Viewport viewport{ + .x = 0.0f, + .y = 0.0f, + .width = static_cast(swapChainExtent.width), + .height = static_cast(swapChainExtent.height), + .minDepth = 0.0f, + .maxDepth = 1.0f}; + commandBuffer.setViewport(0, viewport); + + vk::Rect2D scissor{ + .offset = {0, 0}, + .extent = swapChainExtent}; + commandBuffer.setScissor(0, scissor); + + commandBuffer.bindVertexBuffers(0, {*vertexBuffer}, {0}); + commandBuffer.bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint32); + commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, *pipelineLayout, 0, {*descriptorSets[currentFrame]}, nullptr); + commandBuffer.drawIndexed(static_cast(indices.size()), 1, 0, 0, 0); + + commandBuffer.endRenderPass(); + commandBuffer.end(); + } + + // Draw frame + void drawFrame() + { + static_cast(device.waitForFences({*inFlightFences[currentFrame]}, VK_TRUE, FenceTimeout)); + + uint32_t imageIndex; + try + { + auto [result, idx] = swapChain.acquireNextImage(FenceTimeout, *imageAvailableSemaphores[currentFrame]); + imageIndex = idx; + } + catch (vk::OutOfDateKHRError &) + { + recreateSwapChain(); + return; + } + + // Update uniform buffer with current transformation + updateUniformBuffer(currentFrame); + + device.resetFences({*inFlightFences[currentFrame]}); + + commandBuffers[currentFrame].reset(); + recordCommandBuffer(commandBuffers[currentFrame], imageIndex); + + vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); + const vk::SubmitInfo submitInfo{ + .waitSemaphoreCount = 1, + .pWaitSemaphores = &*imageAvailableSemaphores[currentFrame], + .pWaitDstStageMask = &waitDestinationStageMask, + .commandBufferCount = 1, + .pCommandBuffers = &*commandBuffers[currentFrame], + .signalSemaphoreCount = 1, + .pSignalSemaphores = &*renderFinishedSemaphores[currentFrame]}; + queue.submit(submitInfo, *inFlightFences[currentFrame]); + + const vk::PresentInfoKHR presentInfoKHR{ + .waitSemaphoreCount = 1, + .pWaitSemaphores = &*renderFinishedSemaphores[currentFrame], + .swapchainCount = 1, + .pSwapchains = &*swapChain, + .pImageIndices = &imageIndex}; + + vk::Result result; + try + { + result = queue.presentKHR(presentInfoKHR); + } + catch (vk::OutOfDateKHRError &) + { + result = vk::Result::eErrorOutOfDateKHR; + } + + if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) + { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != vk::Result::eSuccess) + { + throw std::runtime_error("Failed to present swap chain image"); + } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + // Recreate swap chain + void recreateSwapChain() + { + // Wait for device to finish operations + device.waitIdle(); + + // Clean up old swap chain + cleanupSwapChain(); + + // Create new swap chain + createSwapChain(); + createImageViews(); + createFramebuffers(); + } + + // Get required extensions + std::vector getRequiredExtensions() + { #if PLATFORM_ANDROID - // Android requires these extensions - std::vector extensions = { - VK_KHR_SURFACE_EXTENSION_NAME, - VK_KHR_ANDROID_SURFACE_EXTENSION_NAME - }; + // Android requires these extensions + std::vector extensions = { + VK_KHR_SURFACE_EXTENSION_NAME, + VK_KHR_ANDROID_SURFACE_EXTENSION_NAME}; #else - // Get the required extensions from GLFW - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + // Get the required extensions from GLFW + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); #endif - // Check if the debug utils extension is available - std::vector props = context.enumerateInstanceExtensionProperties(); - bool debugUtilsAvailable = std::ranges::any_of(props, - [](vk::ExtensionProperties const & ep) { - return strcmp(ep.extensionName, vk::EXTDebugUtilsExtensionName) == 0; - }); - - // Always include the debug utils extension if available - if (debugUtilsAvailable) { - extensions.push_back(vk::EXTDebugUtilsExtensionName); + // Check if the debug utils extension is available + std::vector props = context.enumerateInstanceExtensionProperties(); + bool debugUtilsAvailable = std::ranges::any_of(props, + [](vk::ExtensionProperties const &ep) { + return strcmp(ep.extensionName, vk::EXTDebugUtilsExtensionName) == 0; + }); + + // Always include the debug utils extension if available + if (debugUtilsAvailable) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); #if PLATFORM_DESKTOP - } else { - LOG_INFO("VK_EXT_debug_utils extension not available. Validation layers may not work."); + } + else + { + LOG_INFO("VK_EXT_debug_utils extension not available. Validation layers may not work."); #endif - } - - return extensions; - } - - static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const & surfaceCapabilities) { - auto minImageCount = std::max( 3u, surfaceCapabilities.minImageCount ); - if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) { - minImageCount = surfaceCapabilities.maxImageCount; - } - return minImageCount; - } - - // Choose swap surface format - vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { - assert(!availableFormats.empty()); - const auto formatIt = std::ranges::find_if( - availableFormats, - []( const auto & format ) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; } ); - return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; - } - - // Choose swap present mode - vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - assert(std::ranges::any_of(availablePresentModes, [](auto presentMode){ return presentMode == vk::PresentModeKHR::eFifo; })); - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eFifo; - } - - // Choose swap extent - vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) { - if (capabilities.currentExtent.width != 0xFFFFFFFF) { - return capabilities.currentExtent; - } else { + } + + return extensions; + } + + static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const &surfaceCapabilities) + { + auto minImageCount = std::max(3u, surfaceCapabilities.minImageCount); + if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) + { + minImageCount = surfaceCapabilities.maxImageCount; + } + return minImageCount; + } + + // Choose swap surface format + vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + assert(!availableFormats.empty()); + const auto formatIt = std::ranges::find_if( + availableFormats, + [](const auto &format) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; }); + return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; + } + + // Choose swap present mode + vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + assert(std::ranges::any_of(availablePresentModes, [](auto presentMode) { return presentMode == vk::PresentModeKHR::eFifo; })); + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + vk::PresentModeKHR::eFifo; + } + + // Choose swap extent + vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR &capabilities) + { + if (capabilities.currentExtent.width != 0xFFFFFFFF) + { + return capabilities.currentExtent; + } + else + { #if PLATFORM_ANDROID - // Get the window size from Android - int32_t width = ANativeWindow_getWidth(androidApp->window); - int32_t height = ANativeWindow_getHeight(androidApp->window); + // Get the window size from Android + int32_t width = ANativeWindow_getWidth(androidApp->window); + int32_t height = ANativeWindow_getHeight(androidApp->window); #else - // Get the window size from GLFW - int width, height; - glfwGetFramebufferSize(window, &width, &height); + // Get the window size from GLFW + int width, height; + glfwGetFramebufferSize(window, &width, &height); #endif - vk::Extent2D actualExtent = { - static_cast(width), - static_cast(height) - }; - - actualExtent.width = std::clamp(actualExtent.width, - capabilities.minImageExtent.width, - capabilities.maxImageExtent.width); - actualExtent.height = std::clamp(actualExtent.height, - capabilities.minImageExtent.height, - capabilities.maxImageExtent.height); - - return actualExtent; - } - } - - // Query swap chain support - SwapChainSupportDetails querySwapChainSupport(vk::raii::PhysicalDevice device) { - SwapChainSupportDetails details; - details.capabilities = device.getSurfaceCapabilitiesKHR(*surface); - details.formats = device.getSurfaceFormatsKHR(*surface); - details.presentModes = device.getSurfacePresentModesKHR(*surface); - return details; - } - - // Create buffer - void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer& buffer, vk::raii::DeviceMemory& bufferMemory) { - vk::BufferCreateInfo bufferInfo{ - .size = size, - .usage = usage, - .sharingMode = vk::SharingMode::eExclusive - }; - - buffer = device.createBuffer(bufferInfo); - - vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); - - vk::MemoryAllocateInfo allocInfo{ - .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) - }; - - bufferMemory = device.allocateMemory(allocInfo); - buffer.bindMemory(*bufferMemory, 0); - } - - // Copy buffer - void copyBuffer(vk::raii::Buffer& srcBuffer, vk::raii::Buffer& dstBuffer, vk::DeviceSize size) { - vk::CommandBufferAllocateInfo allocInfo{ - .commandPool = *commandPool, - .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = 1 - }; - - vk::raii::CommandBuffer commandBuffer = std::move(device.allocateCommandBuffers(allocInfo)[0]); - - vk::CommandBufferBeginInfo beginInfo{ - .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit - }; - commandBuffer.begin(beginInfo); - - vk::BufferCopy copyRegion{ - .srcOffset = 0, - .dstOffset = 0, - .size = size - }; - commandBuffer.copyBuffer(*srcBuffer, *dstBuffer, copyRegion); - - commandBuffer.end(); - - vk::SubmitInfo submitInfo{ - .commandBufferCount = 1, - .pCommandBuffers = &*commandBuffer - }; - - queue.submit(submitInfo, nullptr); - queue.waitIdle(); - } - - // Find memory type - uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) { - vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); - - for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { - if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { - return i; - } - } - - throw std::runtime_error("Failed to find suitable memory type"); - } - - // Create image view - vk::raii::ImageView createImageView(vk::raii::Image& image, vk::Format format) { - vk::ImageViewCreateInfo viewInfo{ - .image = *image, - .viewType = vk::ImageViewType::e2D, - .format = format, - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - - return device.createImageView(viewInfo); - } - - // Transition image layout - void transitionImageLayout(vk::raii::Image& image, vk::Format format, vk::ImageLayout oldLayout, vk::ImageLayout newLayout) { - vk::CommandBufferAllocateInfo allocInfo{ - .commandPool = *commandPool, - .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = 1 - }; - - vk::raii::CommandBuffer commandBuffer = std::move(device.allocateCommandBuffers(allocInfo)[0]); - - vk::CommandBufferBeginInfo beginInfo{ - .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit - }; - commandBuffer.begin(beginInfo); - - vk::ImageMemoryBarrier barrier{ - .oldLayout = oldLayout, - .newLayout = newLayout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = *image, - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - - vk::PipelineStageFlags sourceStage; - vk::PipelineStageFlags destinationStage; - - if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) { - barrier.srcAccessMask = vk::AccessFlagBits::eNone; - barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; - - sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; - destinationStage = vk::PipelineStageFlagBits::eTransfer; - } else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) { - barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; - barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - - sourceStage = vk::PipelineStageFlagBits::eTransfer; - destinationStage = vk::PipelineStageFlagBits::eFragmentShader; - } else { - throw std::invalid_argument("Unsupported layout transition"); - } - - commandBuffer.pipelineBarrier( - sourceStage, destinationStage, - vk::DependencyFlagBits::eByRegion, - nullptr, - nullptr, - barrier - ); - - commandBuffer.end(); - - vk::SubmitInfo submitInfo{ - .commandBufferCount = 1, - .pCommandBuffers = &*commandBuffer - }; - - queue.submit(submitInfo, nullptr); - queue.waitIdle(); - } - - // Copy buffer to image - void copyBufferToImage(vk::raii::Buffer& buffer, vk::raii::Image& image, uint32_t width, uint32_t height) { - vk::CommandBufferAllocateInfo allocInfo{ - .commandPool = *commandPool, - .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = 1 - }; - - vk::raii::CommandBuffer commandBuffer = std::move(device.allocateCommandBuffers(allocInfo)[0]); - - vk::CommandBufferBeginInfo beginInfo{ - .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit - }; - commandBuffer.begin(beginInfo); - - vk::BufferImageCopy region{ - .bufferOffset = 0, - .bufferRowLength = 0, - .bufferImageHeight = 0, - .imageSubresource = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .mipLevel = 0, - .baseArrayLayer = 0, - .layerCount = 1 - }, - .imageOffset = {0, 0, 0}, - .imageExtent = {width, height, 1} - }; - - commandBuffer.copyBufferToImage( - *buffer, - *image, - vk::ImageLayout::eTransferDstOptimal, - region - ); - - commandBuffer.end(); - - vk::SubmitInfo submitInfo{ - .commandBufferCount = 1, - .pCommandBuffers = &*commandBuffer - }; - - queue.submit(submitInfo, nullptr); - queue.waitIdle(); - } - - // Update uniform buffer - void updateUniformBuffer(uint32_t currentImage) { - static auto startTime = std::chrono::high_resolution_clock::now(); - - auto currentTime = std::chrono::high_resolution_clock::now(); - float time = std::chrono::duration(currentTime - startTime).count(); - - UniformBufferObject ubo{}; - ubo.model = glm::rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.proj = glm::perspective(glm::radians(45.0f), swapChainExtent.width / (float) swapChainExtent.height, 0.1f, 10.0f); - ubo.proj[1][1] *= -1; - - void* data; - data = uniformBuffersMemory[currentImage].mapMemory(0, sizeof(ubo)); - memcpy(data, &ubo, sizeof(ubo)); - uniformBuffersMemory[currentImage].unmapMemory(); - } + vk::Extent2D actualExtent = { + static_cast(width), + static_cast(height)}; + + actualExtent.width = std::clamp(actualExtent.width, + capabilities.minImageExtent.width, + capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, + capabilities.minImageExtent.height, + capabilities.maxImageExtent.height); + + return actualExtent; + } + } + + // Query swap chain support + SwapChainSupportDetails querySwapChainSupport(vk::raii::PhysicalDevice device) + { + SwapChainSupportDetails details; + details.capabilities = device.getSurfaceCapabilitiesKHR(*surface); + details.formats = device.getSurfaceFormatsKHR(*surface); + details.presentModes = device.getSurfacePresentModesKHR(*surface); + return details; + } + + // Create buffer + void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer &buffer, vk::raii::DeviceMemory &bufferMemory) + { + vk::BufferCreateInfo bufferInfo{ + .size = size, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive}; + + buffer = device.createBuffer(bufferInfo); + + vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); + + vk::MemoryAllocateInfo allocInfo{ + .allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + + bufferMemory = device.allocateMemory(allocInfo); + buffer.bindMemory(*bufferMemory, 0); + } + + // Copy buffer + void copyBuffer(vk::raii::Buffer &srcBuffer, vk::raii::Buffer &dstBuffer, vk::DeviceSize size) + { + vk::CommandBufferAllocateInfo allocInfo{ + .commandPool = *commandPool, + .level = vk::CommandBufferLevel::ePrimary, + .commandBufferCount = 1}; + + vk::raii::CommandBuffer commandBuffer = std::move(device.allocateCommandBuffers(allocInfo)[0]); + + vk::CommandBufferBeginInfo beginInfo{ + .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; + commandBuffer.begin(beginInfo); + + vk::BufferCopy copyRegion{ + .srcOffset = 0, + .dstOffset = 0, + .size = size}; + commandBuffer.copyBuffer(*srcBuffer, *dstBuffer, copyRegion); + + commandBuffer.end(); + + vk::SubmitInfo submitInfo{ + .commandBufferCount = 1, + .pCommandBuffers = &*commandBuffer}; + + queue.submit(submitInfo, nullptr); + queue.waitIdle(); + } + + // Find memory type + uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) + { + vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) + { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) + { + return i; + } + } + + throw std::runtime_error("Failed to find suitable memory type"); + } + + // Create image view + vk::raii::ImageView createImageView(vk::raii::Image &image, vk::Format format) + { + vk::ImageViewCreateInfo viewInfo{ + .image = *image, + .viewType = vk::ImageViewType::e2D, + .format = format, + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + + return device.createImageView(viewInfo); + } + + // Transition image layout + void transitionImageLayout(vk::raii::Image &image, vk::Format format, vk::ImageLayout oldLayout, vk::ImageLayout newLayout) + { + vk::CommandBufferAllocateInfo allocInfo{ + .commandPool = *commandPool, + .level = vk::CommandBufferLevel::ePrimary, + .commandBufferCount = 1}; + + vk::raii::CommandBuffer commandBuffer = std::move(device.allocateCommandBuffers(allocInfo)[0]); + + vk::CommandBufferBeginInfo beginInfo{ + .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; + commandBuffer.begin(beginInfo); + + vk::ImageMemoryBarrier barrier{ + .oldLayout = oldLayout, + .newLayout = newLayout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = *image, + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + + vk::PipelineStageFlags sourceStage; + vk::PipelineStageFlags destinationStage; + + if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) + { + barrier.srcAccessMask = vk::AccessFlagBits::eNone; + barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; + + sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; + destinationStage = vk::PipelineStageFlagBits::eTransfer; + } + else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) + { + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + + sourceStage = vk::PipelineStageFlagBits::eTransfer; + destinationStage = vk::PipelineStageFlagBits::eFragmentShader; + } + else + { + throw std::invalid_argument("Unsupported layout transition"); + } + + commandBuffer.pipelineBarrier( + sourceStage, destinationStage, + vk::DependencyFlagBits::eByRegion, + nullptr, + nullptr, + barrier); + + commandBuffer.end(); + + vk::SubmitInfo submitInfo{ + .commandBufferCount = 1, + .pCommandBuffers = &*commandBuffer}; + + queue.submit(submitInfo, nullptr); + queue.waitIdle(); + } + + // Copy buffer to image + void copyBufferToImage(vk::raii::Buffer &buffer, vk::raii::Image &image, uint32_t width, uint32_t height) + { + vk::CommandBufferAllocateInfo allocInfo{ + .commandPool = *commandPool, + .level = vk::CommandBufferLevel::ePrimary, + .commandBufferCount = 1}; + + vk::raii::CommandBuffer commandBuffer = std::move(device.allocateCommandBuffers(allocInfo)[0]); + + vk::CommandBufferBeginInfo beginInfo{ + .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; + commandBuffer.begin(beginInfo); + + vk::BufferImageCopy region{ + .bufferOffset = 0, + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .mipLevel = 0, + .baseArrayLayer = 0, + .layerCount = 1}, + .imageOffset = {0, 0, 0}, + .imageExtent = {width, height, 1}}; + + commandBuffer.copyBufferToImage( + *buffer, + *image, + vk::ImageLayout::eTransferDstOptimal, + region); + + commandBuffer.end(); + + vk::SubmitInfo submitInfo{ + .commandBufferCount = 1, + .pCommandBuffers = &*commandBuffer}; + + queue.submit(submitInfo, nullptr); + queue.waitIdle(); + } + + // Update uniform buffer + void updateUniformBuffer(uint32_t currentImage) + { + static auto startTime = std::chrono::high_resolution_clock::now(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + UniformBufferObject ubo{}; + ubo.model = glm::rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.proj = glm::perspective(glm::radians(45.0f), swapChainExtent.width / (float) swapChainExtent.height, 0.1f, 10.0f); + ubo.proj[1][1] *= -1; + + void *data; + data = uniformBuffersMemory[currentImage].mapMemory(0, sizeof(ubo)); + memcpy(data, &ubo, sizeof(ubo)); + uniformBuffersMemory[currentImage].unmapMemory(); + } #if PLATFORM_ANDROID - // Handle app commands - static void handleAppCommand(android_app* app, int32_t cmd) { - auto* vulkanApp = static_cast(app->userData); - switch (cmd) { - case APP_CMD_INIT_WINDOW: - // Window created, initialize Vulkan - if (app->window != nullptr) { - vulkanApp->initVulkan(); - } - break; - case APP_CMD_TERM_WINDOW: - // Window destroyed, clean up Vulkan - vulkanApp->cleanup(); - break; - default: - break; - } - } - - // Handle input events - static int32_t handleInputEvent(android_app* app, AInputEvent* event) { - auto* vulkanApp = static_cast(app->userData); - if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) { - // Handle touch events - float x = AMotionEvent_getX(event, 0); - float y = AMotionEvent_getY(event, 0); - - // Process touch coordinates - LOGI("Touch at: %f, %f", x, y); - - return 1; - } - return 0; - } + // Handle app commands + static void handleAppCommand(android_app *app, int32_t cmd) + { + auto *vulkanApp = static_cast(app->userData); + switch (cmd) + { + case APP_CMD_INIT_WINDOW: + // Window created, initialize Vulkan + if (app->window != nullptr) + { + vulkanApp->initVulkan(); + } + break; + case APP_CMD_TERM_WINDOW: + // Window destroyed, clean up Vulkan + vulkanApp->cleanup(); + break; + default: + break; + } + } + + // Handle input events + static int32_t handleInputEvent(android_app *app, AInputEvent *event) + { + auto *vulkanApp = static_cast(app->userData); + if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) + { + // Handle touch events + float x = AMotionEvent_getX(event, 0); + float y = AMotionEvent_getY(event, 0); + + // Process touch coordinates + LOGI("Touch at: %f, %f", x, y); + + return 1; + } + return 0; + } #endif }; // Platform-specific entry point #if PLATFORM_ANDROID // Android main entry point -void android_main(android_app* app) { - // Make sure glue isn't stripped - app_dummy(); - - try { - // Create and run the Vulkan application - HelloTriangleApplication vulkanApp(app); - vulkanApp.run(); - } catch (const std::exception& e) { - LOGE("Exception caught: %s", e.what()); - } +void android_main(android_app *app) +{ + // Make sure glue isn't stripped + app_dummy(); + + try + { + // Create and run the Vulkan application + HelloTriangleApplication vulkanApp(app); + vulkanApp.run(); + } + catch (const std::exception &e) + { + LOGE("Exception caught: %s", e.what()); + } } #else // Desktop main entry point -int main() { - try { - HelloTriangleApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + HelloTriangleApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } #endif diff --git a/attachments/35_gltf_ktx.cpp b/attachments/35_gltf_ktx.cpp index b8db78b4..e77de815 100644 --- a/attachments/35_gltf_ktx.cpp +++ b/attachments/35_gltf_ktx.cpp @@ -13,21 +13,21 @@ #include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include #if defined(__ANDROID__) -#include -#include +# include +# include #endif #include #if defined(__ANDROID__) - #define PLATFORM_ANDROID 1 +# define PLATFORM_ANDROID 1 #else - #define PLATFORM_DESKTOP 1 +# define PLATFORM_DESKTOP 1 #endif // Include tinygltf instead of tinyobjloader @@ -39,34 +39,41 @@ import vulkan_hpp; #include #if PLATFORM_ANDROID - #include - #include - #include - #include - - // Declare and implement app_dummy function from native_app_glue - extern "C" void app_dummy() { - // This is a dummy function that does nothing - // It's used to prevent the linker from stripping out the native_app_glue code - } - - // Define AAssetManager type for Android - typedef AAssetManager AssetManagerType; - - #define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "VulkanTutorial", __VA_ARGS__)) - #define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "VulkanTutorial", __VA_ARGS__)) - #define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "VulkanTutorial", __VA_ARGS__)) +# include +# include +# include +# include + +// Declare and implement app_dummy function from native_app_glue +extern "C" void app_dummy() +{ + // This is a dummy function that does nothing + // It's used to prevent the linker from stripping out the native_app_glue code +} + +// Define AAssetManager type for Android +typedef AAssetManager AssetManagerType; + +# define LOGI(...) ((void) __android_log_print(ANDROID_LOG_INFO, "VulkanTutorial", __VA_ARGS__)) +# define LOGW(...) ((void) __android_log_print(ANDROID_LOG_WARN, "VulkanTutorial", __VA_ARGS__)) +# define LOGE(...) ((void) __android_log_print(ANDROID_LOG_ERROR, "VulkanTutorial", __VA_ARGS__)) #else - // Define AAssetManager type for non-Android platforms - typedef void AssetManagerType; - // Desktop-specific includes - #define GLFW_INCLUDE_VULKAN - #include - - // Define logging macros for Desktop - #define LOGI(...) printf(__VA_ARGS__); printf("\n") - #define LOGW(...) printf(__VA_ARGS__); printf("\n") - #define LOGE(...) fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n") +// Define AAssetManager type for non-Android platforms +typedef void AssetManagerType; +// Desktop-specific includes +# define GLFW_INCLUDE_VULKAN +# include + +// Define logging macros for Desktop +# define LOGI(...) \ + printf(__VA_ARGS__); \ + printf("\n") +# define LOGW(...) \ + printf(__VA_ARGS__); \ + printf("\n") +# define LOGE(...) \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "\n") #endif #define GLM_FORCE_RADIANS @@ -77,46 +84,49 @@ import vulkan_hpp; #include #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; constexpr uint64_t FenceTimeout = 100000000; // Update paths to use glTF model and KTX2 texture -const std::string MODEL_PATH = "models/viking_room.glb"; -const std::string TEXTURE_PATH = "textures/viking_room.ktx2"; -constexpr int MAX_FRAMES_IN_FLIGHT = 2; +const std::string MODEL_PATH = "models/viking_room.glb"; +const std::string TEXTURE_PATH = "textures/viking_room.ktx2"; +constexpr int MAX_FRAMES_IN_FLIGHT = 2; // Define VpProfileProperties structure for Android only #if PLATFORM_ANDROID -#ifndef VP_PROFILE_PROPERTIES_DEFINED -#define VP_PROFILE_PROPERTIES_DEFINED -struct VpProfileProperties { - char name[256]; - uint32_t specVersion; +# ifndef VP_PROFILE_PROPERTIES_DEFINED +# define VP_PROFILE_PROPERTIES_DEFINED +struct VpProfileProperties +{ + char name[256]; + uint32_t specVersion; }; -#endif +# endif #endif // Define Vulkan Profile constants #ifndef VP_KHR_ROADMAP_2022_NAME -#define VP_KHR_ROADMAP_2022_NAME "VP_KHR_roadmap_2022" +# define VP_KHR_ROADMAP_2022_NAME "VP_KHR_roadmap_2022" #endif #ifndef VP_KHR_ROADMAP_2022_SPEC_VERSION -#define VP_KHR_ROADMAP_2022_SPEC_VERSION 1 +# define VP_KHR_ROADMAP_2022_SPEC_VERSION 1 #endif -struct AppInfo { - bool profileSupported = false; - VpProfileProperties profile; +struct AppInfo +{ + bool profileSupported = false; + VpProfileProperties profile; }; #if PLATFORM_ANDROID -void android_main(android_app* app); +void android_main(android_app *app); -struct AndroidAppState { - ANativeWindow* nativeWindow = nullptr; - bool initialized = false; - android_app* app = nullptr; +struct AndroidAppState +{ + ANativeWindow *nativeWindow = nullptr; + bool initialized = false; + android_app *app = nullptr; }; #endif @@ -126,1334 +136,1426 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -struct Vertex { - glm::vec3 pos; - glm::vec3 color; - glm::vec2 texCoord; - - static vk::VertexInputBindingDescription getBindingDescription() { - return { 0, sizeof(Vertex), vk::VertexInputRate::eVertex }; - } - - static std::array getAttributeDescriptions() { - return { - vk::VertexInputAttributeDescription( 0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, pos) ), - vk::VertexInputAttributeDescription( 1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color) ), - vk::VertexInputAttributeDescription( 2, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, texCoord) ) - }; - } - - bool operator==(const Vertex& other) const { - return pos == other.pos && color == other.color && texCoord == other.texCoord; - } +struct Vertex +{ + glm::vec3 pos; + glm::vec3 color; + glm::vec2 texCoord; + + static vk::VertexInputBindingDescription getBindingDescription() + { + return {0, sizeof(Vertex), vk::VertexInputRate::eVertex}; + } + + static std::array getAttributeDescriptions() + { + return { + vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, pos)), + vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color)), + vk::VertexInputAttributeDescription(2, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, texCoord))}; + } + + bool operator==(const Vertex &other) const + { + return pos == other.pos && color == other.color && texCoord == other.texCoord; + } }; -template<> struct std::hash { - size_t operator()(Vertex const& vertex) const noexcept { - return ((hash()(vertex.pos) ^ (hash()(vertex.color) << 1)) >> 1) ^ (hash()(vertex.texCoord) << 1); - } +template <> +struct std::hash +{ + size_t operator()(Vertex const &vertex) const noexcept + { + return ((hash()(vertex.pos) ^ (hash()(vertex.color) << 1)) >> 1) ^ (hash()(vertex.texCoord) << 1); + } }; -struct UniformBufferObject { - alignas(16) glm::mat4 model; - alignas(16) glm::mat4 view; - alignas(16) glm::mat4 proj; +struct UniformBufferObject +{ + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; }; -class VulkanApplication { -public: +class VulkanApplication +{ + public: #if PLATFORM_ANDROID - void run(android_app* app) { - androidAppState.nativeWindow = app->window; - androidAppState.app = app; - app->userData = &androidAppState; - app->onAppCmd = handleAppCommand; - // Note: onInputEvent is no longer a member of android_app in the current NDK version - // Input events are now handled differently - - int events; - android_poll_source* source; - - while (app->destroyRequested == 0) { - while (ALooper_pollOnce(androidAppState.initialized ? 0 : -1, nullptr, &events, (void**)&source) >= 0) { - if (source != nullptr) { - source->process(app, source); - } - } - - if (androidAppState.initialized && androidAppState.nativeWindow != nullptr) { - drawFrame(); - } - } - - if (androidAppState.initialized) { - device.waitIdle(); - } - } + void run(android_app *app) + { + androidAppState.nativeWindow = app->window; + androidAppState.app = app; + app->userData = &androidAppState; + app->onAppCmd = handleAppCommand; + // Note: onInputEvent is no longer a member of android_app in the current NDK version + // Input events are now handled differently + + int events; + android_poll_source *source; + + while (app->destroyRequested == 0) + { + while (ALooper_pollOnce(androidAppState.initialized ? 0 : -1, nullptr, &events, (void **) &source) >= 0) + { + if (source != nullptr) + { + source->process(app, source); + } + } + + if (androidAppState.initialized && androidAppState.nativeWindow != nullptr) + { + drawFrame(); + } + } + + if (androidAppState.initialized) + { + device.waitIdle(); + } + } #else - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } + void run() + { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } #endif -private: + private: #if PLATFORM_ANDROID - AndroidAppState androidAppState; - - static void handleAppCommand(android_app* app, int32_t cmd) { - auto* appState = static_cast(app->userData); - - switch (cmd) { - case APP_CMD_INIT_WINDOW: - if (app->window != nullptr) { - appState->nativeWindow = app->window; - // We can't cast AndroidAppState to VulkanApplication directly - // Instead, we need to access the VulkanApplication instance through a global variable - // or another mechanism. For now, we'll just set the initialized flag. - appState->initialized = true; - } - break; - case APP_CMD_TERM_WINDOW: - appState->nativeWindow = nullptr; - break; - default: - break; - } - } - - static int32_t handleInputEvent(android_app* app, AInputEvent* event) { - if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) { - float x = AMotionEvent_getX(event, 0); - float y = AMotionEvent_getY(event, 0); - - LOGI("Touch at: %f, %f", x, y); - - return 1; - } - return 0; - } + AndroidAppState androidAppState; + + static void handleAppCommand(android_app *app, int32_t cmd) + { + auto *appState = static_cast(app->userData); + + switch (cmd) + { + case APP_CMD_INIT_WINDOW: + if (app->window != nullptr) + { + appState->nativeWindow = app->window; + // We can't cast AndroidAppState to VulkanApplication directly + // Instead, we need to access the VulkanApplication instance through a global variable + // or another mechanism. For now, we'll just set the initialized flag. + appState->initialized = true; + } + break; + case APP_CMD_TERM_WINDOW: + appState->nativeWindow = nullptr; + break; + default: + break; + } + } + + static int32_t handleInputEvent(android_app *app, AInputEvent *event) + { + if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) + { + float x = AMotionEvent_getX(event, 0); + float y = AMotionEvent_getY(event, 0); + + LOGI("Touch at: %f, %f", x, y); + + return 1; + } + return 0; + } #else - GLFWwindow* window = nullptr; + GLFWwindow *window = nullptr; #endif - AppInfo appInfo; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::SurfaceFormatKHR swapChainSurfaceFormat; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - - vk::raii::Image depthImage = nullptr; - vk::raii::DeviceMemory depthImageMemory = nullptr; - vk::raii::ImageView depthImageView = nullptr; - - vk::raii::Image textureImage = nullptr; - vk::raii::DeviceMemory textureImageMemory = nullptr; - vk::raii::ImageView textureImageView = nullptr; - vk::raii::Sampler textureSampler = nullptr; - vk::Format textureImageFormat = vk::Format::eUndefined; - - std::vector vertices; - std::vector indices; - vk::raii::Buffer vertexBuffer = nullptr; - vk::raii::DeviceMemory vertexBufferMemory = nullptr; - vk::raii::Buffer indexBuffer = nullptr; - vk::raii::DeviceMemory indexBufferMemory = nullptr; - - std::vector uniformBuffers; - std::vector uniformBuffersMemory; - std::vector uniformBuffersMapped; - - vk::raii::DescriptorPool descriptorPool = nullptr; - std::vector descriptorSets; - - vk::raii::CommandPool commandPool = nullptr; - std::vector commandBuffers; - - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; - std::vector inFlightFences; - uint32_t currentFrame = 0; - - bool framebufferResized = false; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; + AppInfo appInfo; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::SurfaceFormatKHR swapChainSurfaceFormat; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + + vk::raii::Image depthImage = nullptr; + vk::raii::DeviceMemory depthImageMemory = nullptr; + vk::raii::ImageView depthImageView = nullptr; + + vk::raii::Image textureImage = nullptr; + vk::raii::DeviceMemory textureImageMemory = nullptr; + vk::raii::ImageView textureImageView = nullptr; + vk::raii::Sampler textureSampler = nullptr; + vk::Format textureImageFormat = vk::Format::eUndefined; + + std::vector vertices; + std::vector indices; + vk::raii::Buffer vertexBuffer = nullptr; + vk::raii::DeviceMemory vertexBufferMemory = nullptr; + vk::raii::Buffer indexBuffer = nullptr; + vk::raii::DeviceMemory indexBufferMemory = nullptr; + + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + std::vector uniformBuffersMapped; + + vk::raii::DescriptorPool descriptorPool = nullptr; + std::vector descriptorSets; + + vk::raii::CommandPool commandPool = nullptr; + std::vector commandBuffers; + + std::vector presentCompleteSemaphore; + std::vector renderFinishedSemaphore; + std::vector inFlightFences; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; #if PLATFORM_DESKTOP - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); - } - - static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { - auto app = static_cast(glfwGetWindowUserPointer(window)); - app->framebufferResized = true; - } + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow *window, int width, int height) + { + auto app = static_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } #endif -public: - void initVulkan() { - createInstance(); - setupDebugMessenger(); - createSurface(); - pickPhysicalDevice(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createDescriptorSetLayout(); - createGraphicsPipeline(); - createCommandPool(); - createDepthResources(); - createTextureImage(); - createTextureImageView(); - createTextureSampler(); - loadModel(); - createVertexBuffer(); - createIndexBuffer(); - createUniformBuffers(); - createDescriptorPool(); - createDescriptorSets(); - createCommandBuffers(); - createSyncObjects(); - } - -private: - + public: + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createDescriptorSetLayout(); + createGraphicsPipeline(); + createCommandPool(); + createDepthResources(); + createTextureImage(); + createTextureImageView(); + createTextureSampler(); + loadModel(); + createVertexBuffer(); + createIndexBuffer(); + createUniformBuffers(); + createDescriptorPool(); + createDescriptorSets(); + createCommandBuffers(); + createSyncObjects(); + } + + private: #if PLATFORM_DESKTOP - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - drawFrame(); - } - - device.waitIdle(); - } + void mainLoop() + { + while (!glfwWindowShouldClose(window)) + { + glfwPollEvents(); + drawFrame(); + } + + device.waitIdle(); + } #endif - void cleanupSwapChain() { - swapChainImageViews.clear(); - swapChain = nullptr; - } + void cleanupSwapChain() + { + swapChainImageViews.clear(); + swapChain = nullptr; + } #if PLATFORM_DESKTOP - void cleanup() const { - glfwDestroyWindow(window); - glfwTerminate(); - } + void cleanup() const + { + glfwDestroyWindow(window); + glfwTerminate(); + } #endif - void recreateSwapChain() { + void recreateSwapChain() + { #if PLATFORM_DESKTOP - int width = 0, height = 0; - glfwGetFramebufferSize(window, &width, &height); - while (width == 0 || height == 0) { - glfwGetFramebufferSize(window, &width, &height); - glfwWaitEvents(); - } + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) + { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } #endif - device.waitIdle(); - - cleanupSwapChain(); - createSwapChain(); - createImageViews(); - createDepthResources(); - } - - 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_API_VERSION_1_3 - }; - - auto extensions = getRequiredExtensions(); - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledExtensionCount = static_cast(extensions.size()), - .ppEnabledExtensionNames = extensions.data() - }; - - instance = vk::raii::Instance(context, createInfo); - LOGI("Vulkan instance created"); - } - - void setupDebugMessenger() { - // Debug messenger setup is disabled for now to avoid compatibility issues - // This is a simplified approach to get the code compiling - if (!enableValidationLayers) return; - - LOGI("Debug messenger setup skipped for compatibility"); - } - - void createSurface() { + device.waitIdle(); + + cleanupSwapChain(); + createSwapChain(); + createImageViews(); + createDepthResources(); + } + + 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_API_VERSION_1_3}; + + auto extensions = getRequiredExtensions(); + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledExtensionCount = static_cast(extensions.size()), + .ppEnabledExtensionNames = extensions.data()}; + + instance = vk::raii::Instance(context, createInfo); + LOGI("Vulkan instance created"); + } + + void setupDebugMessenger() + { + // Debug messenger setup is disabled for now to avoid compatibility issues + // This is a simplified approach to get the code compiling + if (!enableValidationLayers) + return; + + LOGI("Debug messenger setup skipped for compatibility"); + } + + void createSurface() + { #if PLATFORM_DESKTOP - VkSurfaceKHR _surface; - if (glfwCreateWindowSurface(*instance, window, nullptr, &_surface) != VK_SUCCESS) { - throw std::runtime_error("failed to create window surface!"); - } - surface = vk::raii::SurfaceKHR(instance, _surface); + VkSurfaceKHR _surface; + if (glfwCreateWindowSurface(*instance, window, nullptr, &_surface) != VK_SUCCESS) + { + throw std::runtime_error("failed to create window surface!"); + } + surface = vk::raii::SurfaceKHR(instance, _surface); #else - VkSurfaceKHR _surface; - VkAndroidSurfaceCreateInfoKHR createInfo{ - .sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR, - .window = androidAppState.nativeWindow - }; - if (vkCreateAndroidSurfaceKHR(*instance, &createInfo, nullptr, &_surface) != VK_SUCCESS) { - throw std::runtime_error("failed to create Android surface!"); - } - surface = vk::raii::SurfaceKHR(instance, _surface); + VkSurfaceKHR _surface; + VkAndroidSurfaceCreateInfoKHR createInfo{ + .sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR, + .window = androidAppState.nativeWindow}; + if (vkCreateAndroidSurfaceKHR(*instance, &createInfo, nullptr, &_surface) != VK_SUCCESS) + { + throw std::runtime_error("failed to create Android surface!"); + } + surface = vk::raii::SurfaceKHR(instance, _surface); #endif - } + } - void pickPhysicalDevice() { - std::vector devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( + void pickPhysicalDevice() + { + std::vector devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( devices, - [&](auto const& device) { + [&](auto const &device) { // Check if the device supports the Vulkan 1.3 API version bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; // Check if any of the queue families support graphics operations auto queueFamilies = device.getQueueFamilyProperties(); bool supportsGraphics = - std::ranges::any_of(queueFamilies, [](auto const& qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); // Check if all required device extensions are available auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); bool supportsAllRequiredExtensions = std::ranges::all_of(requiredDeviceExtension, - [&availableDeviceExtensions](auto const& requiredDeviceExtension) { - return std::ranges::any_of(availableDeviceExtensions, - [requiredDeviceExtension](auto const& availableDeviceExtension) { - return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; - }); - }); - - auto features = device.template getFeatures2(); + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { + return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; + }); + }); + + auto features = device.template getFeatures2(); bool supportsRequiredFeatures = features.template get().dynamicRendering && - features.template get().extendedDynamicState; + features.template get().extendedDynamicState; return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; }); - if (devIter != devices.end()) { - physicalDevice = *devIter; + if (devIter != devices.end()) + { + physicalDevice = *devIter; - // Check for Vulkan profile support - VpProfileProperties profileProperties; + // Check for Vulkan profile support + VpProfileProperties profileProperties; #if PLATFORM_ANDROID - strcpy(profileProperties.name, VP_KHR_ROADMAP_2022_NAME); + strcpy(profileProperties.name, VP_KHR_ROADMAP_2022_NAME); #else - strcpy(profileProperties.profileName, VP_KHR_ROADMAP_2022_NAME); + strcpy(profileProperties.profileName, VP_KHR_ROADMAP_2022_NAME); #endif - profileProperties.specVersion = VP_KHR_ROADMAP_2022_SPEC_VERSION; + profileProperties.specVersion = VP_KHR_ROADMAP_2022_SPEC_VERSION; - VkBool32 supported = VK_FALSE; - bool result = false; + VkBool32 supported = VK_FALSE; + bool result = false; #if PLATFORM_ANDROID - // Create a vp::ProfileDesc from our VpProfileProperties - vp::ProfileDesc profileDesc = { - profileProperties.name, - profileProperties.specVersion - }; - - // Use vp::GetProfileSupport for Android - result = vp::GetProfileSupport( - *physicalDevice, // Pass the physical device directly - &profileDesc, // Pass the profile description - &supported // Output parameter for support status - ); + // Create a vp::ProfileDesc from our VpProfileProperties + vp::ProfileDesc profileDesc = { + profileProperties.name, + profileProperties.specVersion}; + + // Use vp::GetProfileSupport for Android + result = vp::GetProfileSupport( + *physicalDevice, // Pass the physical device directly + &profileDesc, // Pass the profile description + &supported // Output parameter for support status + ); #else - // Use vpGetPhysicalDeviceProfileSupport for Desktop - VkResult vk_result = vpGetPhysicalDeviceProfileSupport( - *instance, - *physicalDevice, - &profileProperties, - &supported - ); - result = vk_result == static_cast(vk::Result::eSuccess); + // Use vpGetPhysicalDeviceProfileSupport for Desktop + VkResult vk_result = vpGetPhysicalDeviceProfileSupport( + *instance, + *physicalDevice, + &profileProperties, + &supported); + result = vk_result == static_cast(vk::Result::eSuccess); #endif - const char* name = nullptr; + const char *name = nullptr; #ifdef PLATFORM_ANDROID - name = profileProperties.name; + name = profileProperties.name; #else - name = profileProperties.profileName; + name = profileProperties.profileName; #endif - if (result && supported == VK_TRUE) { - appInfo.profileSupported = true; - appInfo.profile = profileProperties; - LOGI("Device supports Vulkan profile: %s", name); - } else { - LOGI("Device does not support Vulkan profile: %s", name); - } - } else { - throw std::runtime_error("failed to find a suitable GPU!"); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - auto features = physicalDevice.getFeatures2(); - vk::PhysicalDeviceVulkan13Features vulkan13Features; - vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT extendedDynamicStateFeatures; - vulkan13Features.dynamicRendering = vk::True; - vulkan13Features.synchronization2 = vk::True; - extendedDynamicStateFeatures.extendedDynamicState = vk::True; - vulkan13Features.pNext = &extendedDynamicStateFeatures; - features.pNext = &vulkan13Features; - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo { .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ - .pNext = &features, - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() - }; - - // Create the device with the appropriate features - device = vk::raii::Device(physicalDevice, deviceCreateInfo); - - queue = vk::raii::Queue(device, queueIndex, 0); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( *surface ); - swapChainExtent = chooseSwapExtent( surfaceCapabilities ); - swapChainSurfaceFormat = chooseSwapSurfaceFormat( physicalDevice.getSurfaceFormatsKHR( *surface ) ); - vk::SwapchainCreateInfoKHR swapChainCreateInfo{ .surface = *surface, - .minImageCount = chooseSwapMinImageCount( surfaceCapabilities ), - .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 }; - - swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); - swapChainImages = swapChain.getImages(); - } - - void createImageViews() { - vk::ImageViewCreateInfo imageViewCreateInfo{ - .viewType = vk::ImageViewType::e2D, - .format = swapChainSurfaceFormat.format, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } - }; - for ( auto image : swapChainImages ) - { - imageViewCreateInfo.image = image; - swapChainImageViews.emplace_back( device, imageViewCreateInfo ); - } - } - - void createDescriptorSetLayout() { - std::array bindings = { - vk::DescriptorSetLayoutBinding( 0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr), - vk::DescriptorSetLayoutBinding( 1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment, nullptr) - }; - - vk::DescriptorSetLayoutCreateInfo layoutInfo{ .bindingCount = static_cast(bindings.size()), .pBindings = bindings.data() }; - descriptorSetLayout = vk::raii::DescriptorSetLayout(device, layoutInfo); - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(this->readFile("shaders/slang.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}; - - auto bindingDescription = Vertex::getBindingDescription(); - auto attributeDescriptions = Vertex::getAttributeDescriptions(); - vk::PipelineVertexInputStateCreateInfo vertexInputInfo{ - .vertexBindingDescriptionCount = 1, - .pVertexBindingDescriptions = &bindingDescription, - .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), - .pVertexAttributeDescriptions = attributeDescriptions.data() - }; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ - .topology = vk::PrimitiveTopology::eTriangleList, - .primitiveRestartEnable = vk::False - }; - vk::PipelineViewportStateCreateInfo viewportState{ - .viewportCount = 1, - .scissorCount = 1 - }; - vk::PipelineRasterizationStateCreateInfo rasterizer{ - .depthClampEnable = vk::False, - .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, - .cullMode = vk::CullModeFlagBits::eBack, // Re-enabled culling for better performance - .frontFace = vk::FrontFace::eClockwise, // Keeping Clockwise for glTF - .depthBiasEnable = vk::False - }; - rasterizer.lineWidth = 1.0f; - vk::PipelineMultisampleStateCreateInfo multisampling{ - .rasterizationSamples = vk::SampleCountFlagBits::e1, - .sampleShadingEnable = vk::False - }; - vk::PipelineDepthStencilStateCreateInfo depthStencil{ - .depthTestEnable = vk::True, - .depthWriteEnable = vk::True, - .depthCompareOp = vk::CompareOp::eLess, - .depthBoundsTestEnable = vk::False, - .stencilTestEnable = vk::False - }; - vk::PipelineColorBlendAttachmentState colorBlendAttachment; - colorBlendAttachment.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA; - colorBlendAttachment.blendEnable = vk::False; - - vk::PipelineColorBlendStateCreateInfo colorBlending{ - .logicOpEnable = vk::False, - .logicOp = vk::LogicOp::eCopy, - .attachmentCount = 1, - .pAttachments = &colorBlendAttachment - }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0 }; - - pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); - - vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ .colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format }; - vk::GraphicsPipelineCreateInfo pipelineInfo{ .pNext = &pipelineRenderingCreateInfo, - .stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pDepthStencilState = &depthStencil, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = *pipelineLayout, - .renderPass = nullptr - }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{ - .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex - }; - commandPool = vk::raii::CommandPool(device, poolInfo); - } - - void createDepthResources() { - vk::Format depthFormat = findDepthFormat(); - - createImage(swapChainExtent.width, swapChainExtent.height, depthFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eDepthStencilAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, depthImage, depthImageMemory); - depthImageView = createImageView(depthImage, depthFormat, vk::ImageAspectFlagBits::eDepth); - } - - vk::Format findSupportedFormat(const std::vector& candidates, vk::ImageTiling tiling, vk::FormatFeatureFlags features) const { - for (const auto format : candidates) { - vk::FormatProperties props = physicalDevice.getFormatProperties(format); - - if (tiling == vk::ImageTiling::eLinear && (props.linearTilingFeatures & features) == features) { - return format; - } - if (tiling == vk::ImageTiling::eOptimal && (props.optimalTilingFeatures & features) == features) { - return format; - } - } - - throw std::runtime_error("failed to find supported format!"); - } - - [[nodiscard]] vk::Format findDepthFormat() const { - return findSupportedFormat( - {vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, vk::Format::eD24UnormS8Uint}, - vk::ImageTiling::eOptimal, - vk::FormatFeatureFlagBits::eDepthStencilAttachment - ); - } - - static bool hasStencilComponent(vk::Format format) { - return format == vk::Format::eD32SfloatS8Uint || format == vk::Format::eD24UnormS8Uint; - } - - void createTextureImage() { - // Load KTX2 texture instead of using stb_image - ktxTexture* kTexture; - KTX_error_code result = ktxTexture_CreateFromNamedFile( - TEXTURE_PATH.c_str(), - KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, - &kTexture); - - if (result != KTX_SUCCESS) { - throw std::runtime_error("failed to load ktx texture image!"); - } - - // Get texture dimensions and data - uint32_t texWidth = kTexture->baseWidth; - uint32_t texHeight = kTexture->baseHeight; - ktx_size_t imageSize = ktxTexture_GetImageSize(kTexture, 0); - ktx_uint8_t* ktxTextureData = ktxTexture_GetData(kTexture); - - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, imageSize); - memcpy(data, ktxTextureData, imageSize); - stagingBufferMemory.unmapMemory(); - - // Determine the Vulkan format from KTX format - vk::Format textureFormat; - - // Check if the KTX texture has a format - if (kTexture->classId == ktxTexture2_c) { - // For KTX2 files, we can get the format directly - auto* ktx2 = reinterpret_cast(kTexture); - textureFormat = static_cast(ktx2->vkFormat); - if (textureFormat == vk::Format::eUndefined) { - // If the format is undefined, fall back to a reasonable default - textureFormat = vk::Format::eR8G8B8A8Unorm; - } - } else { - // For KTX1 files or if we can't determine the format, use a reasonable default - textureFormat = vk::Format::eR8G8B8A8Unorm; - } - - textureImageFormat = textureFormat; - - createImage(texWidth, texHeight, textureFormat, vk::ImageTiling::eOptimal, - vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, - vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory); - - transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal); - copyBufferToImage(stagingBuffer, textureImage, texWidth, texHeight); - transitionImageLayout(textureImage, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal); - - ktxTexture_Destroy(kTexture); - } - - void createTextureImageView() { - textureImageView = createImageView(textureImage, textureImageFormat, vk::ImageAspectFlagBits::eColor); - } - - void createTextureSampler() { - vk::PhysicalDeviceProperties properties = physicalDevice.getProperties(); - vk::SamplerCreateInfo samplerInfo{ - .magFilter = vk::Filter::eLinear, - .minFilter = vk::Filter::eLinear, - .mipmapMode = vk::SamplerMipmapMode::eLinear, - .addressModeU = vk::SamplerAddressMode::eRepeat, - .addressModeV = vk::SamplerAddressMode::eRepeat, - .addressModeW = vk::SamplerAddressMode::eRepeat, - .mipLodBias = 0.0f, - .anisotropyEnable = vk::True, - .maxAnisotropy = properties.limits.maxSamplerAnisotropy, - .compareEnable = vk::False, - .compareOp = vk::CompareOp::eAlways - }; - textureSampler = vk::raii::Sampler(device, samplerInfo); - } - - vk::raii::ImageView createImageView(vk::raii::Image& image, vk::Format format, vk::ImageAspectFlags aspectFlags) { - vk::ImageViewCreateInfo viewInfo{ - .image = *image, - .viewType = vk::ImageViewType::e2D, - .format = format, - .subresourceRange = { aspectFlags, 0, 1, 0, 1 } - }; - return vk::raii::ImageView(device, viewInfo); - } - - void createImage(uint32_t width, uint32_t height, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image& image, vk::raii::DeviceMemory& imageMemory) { - vk::ImageCreateInfo imageInfo{ - .imageType = vk::ImageType::e2D, - .format = format, - .extent = {width, height, 1}, - .mipLevels = 1, - .arrayLayers = 1, - .samples = vk::SampleCountFlagBits::e1, - .tiling = tiling, - .usage = usage, - .sharingMode = vk::SharingMode::eExclusive, - .initialLayout = vk::ImageLayout::eUndefined - }; - image = vk::raii::Image(device, imageInfo); - - vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ - .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) - }; - imageMemory = vk::raii::DeviceMemory(device, allocInfo); - image.bindMemory(*imageMemory, 0); - } - - void transitionImageLayout(const vk::raii::Image& image, vk::ImageLayout oldLayout, vk::ImageLayout newLayout) { - auto commandBuffer = beginSingleTimeCommands(); - - vk::ImageMemoryBarrier barrier{ - .oldLayout = oldLayout, - .newLayout = newLayout, - .image = *image, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } - }; - - vk::PipelineStageFlags sourceStage; - vk::PipelineStageFlags destinationStage; - - if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) { - barrier.srcAccessMask = {}; - barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; - - sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; - destinationStage = vk::PipelineStageFlagBits::eTransfer; - } else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) { - barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; - barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - - sourceStage = vk::PipelineStageFlagBits::eTransfer; - destinationStage = vk::PipelineStageFlagBits::eFragmentShader; - } else { - throw std::invalid_argument("unsupported layout transition!"); - } - commandBuffer->pipelineBarrier( sourceStage, destinationStage, {}, {}, nullptr, barrier ); - endSingleTimeCommands(*commandBuffer); - } - - void copyBufferToImage(const vk::raii::Buffer& buffer, vk::raii::Image& image, uint32_t width, uint32_t height) { - std::unique_ptr commandBuffer = beginSingleTimeCommands(); - vk::BufferImageCopy region{ - .bufferOffset = 0, - .bufferRowLength = 0, - .bufferImageHeight = 0, - .imageSubresource = { vk::ImageAspectFlagBits::eColor, 0, 0, 1 }, - .imageOffset = {0, 0, 0}, - .imageExtent = {width, height, 1} - }; - commandBuffer->copyBufferToImage(*buffer, *image, vk::ImageLayout::eTransferDstOptimal, {region}); - endSingleTimeCommands(*commandBuffer); - } - - void loadModel() { - // Use tinygltf to load the model instead of tinyobjloader - tinygltf::Model model; - tinygltf::TinyGLTF loader; - std::string err; - std::string warn; - - bool ret = loader.LoadBinaryFromFile(&model, &err, &warn, MODEL_PATH); - - if (!warn.empty()) { - std::cout << "glTF warning: " << warn << std::endl; - } - - if (!err.empty()) { - std::cout << "glTF error: " << err << std::endl; - } - - if (!ret) { - throw std::runtime_error("Failed to load glTF model"); - } - - vertices.clear(); - indices.clear(); - - // Process all meshes in the model - for (const auto& mesh : model.meshes) { - for (const auto& primitive : mesh.primitives) { - // Get indices - const tinygltf::Accessor& indexAccessor = model.accessors[primitive.indices]; - const tinygltf::BufferView& indexBufferView = model.bufferViews[indexAccessor.bufferView]; - const tinygltf::Buffer& indexBuffer = model.buffers[indexBufferView.buffer]; - - // Get vertex positions - const tinygltf::Accessor& posAccessor = model.accessors[primitive.attributes.at("POSITION")]; - const tinygltf::BufferView& posBufferView = model.bufferViews[posAccessor.bufferView]; - const tinygltf::Buffer& posBuffer = model.buffers[posBufferView.buffer]; - - // Get texture coordinates if available - bool hasTexCoords = primitive.attributes.find("TEXCOORD_0") != primitive.attributes.end(); - const tinygltf::Accessor* texCoordAccessor = nullptr; - const tinygltf::BufferView* texCoordBufferView = nullptr; - const tinygltf::Buffer* texCoordBuffer = nullptr; - - if (hasTexCoords) { - texCoordAccessor = &model.accessors[primitive.attributes.at("TEXCOORD_0")]; - texCoordBufferView = &model.bufferViews[texCoordAccessor->bufferView]; - texCoordBuffer = &model.buffers[texCoordBufferView->buffer]; - } - - uint32_t baseVertex = static_cast(vertices.size()); - - for (size_t i = 0; i < posAccessor.count; i++) { - Vertex vertex{}; - - const float* pos = reinterpret_cast(&posBuffer.data[posBufferView.byteOffset + posAccessor.byteOffset + i * 12]); - // glTF uses a right-handed coordinate system with Y-up - // Vulkan uses a right-handed coordinate system with Y-down - // We need to flip the Y coordinate - vertex.pos = {pos[0], -pos[1], pos[2]}; - - if (hasTexCoords) { - const float* texCoord = reinterpret_cast(&texCoordBuffer->data[texCoordBufferView->byteOffset + texCoordAccessor->byteOffset + i * 8]); - vertex.texCoord = {texCoord[0], texCoord[1]}; - } else { - vertex.texCoord = {0.0f, 0.0f}; - } - - vertex.color = {1.0f, 1.0f, 1.0f}; - - vertices.push_back(vertex); - } - - const unsigned char* indexData = &indexBuffer.data[indexBufferView.byteOffset + indexAccessor.byteOffset]; - size_t indexCount = indexAccessor.count; - size_t indexStride = 0; - - // Determine index stride based on component type - if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) { - indexStride = sizeof(uint16_t); - } else if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT) { - indexStride = sizeof(uint32_t); - } else if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) { - indexStride = sizeof(uint8_t); - } else { - throw std::runtime_error("Unsupported index component type"); - } - - indices.reserve(indices.size() + indexCount); - - for (size_t i = 0; i < indexCount; i++) { - uint32_t index = 0; - - if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) { - index = *reinterpret_cast(indexData + i * indexStride); - } else if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT) { - index = *reinterpret_cast(indexData + i * indexStride); - } else if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) { - index = *reinterpret_cast(indexData + i * indexStride); - } - - indices.push_back(baseVertex + index); - } - } - } - } - - void createVertexBuffer() { - vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(dataStaging, vertices.data(), bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); - - copyBuffer(stagingBuffer, vertexBuffer, bufferSize); - } - - void createIndexBuffer() { - vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); - - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(data, indices.data(), bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); - - copyBuffer(stagingBuffer, indexBuffer, bufferSize); - } - - void createUniformBuffers() { - uniformBuffers.clear(); - uniformBuffersMemory.clear(); - uniformBuffersMapped.clear(); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DeviceSize bufferSize = sizeof(UniformBufferObject); - vk::raii::Buffer buffer({}); - vk::raii::DeviceMemory bufferMem({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); - uniformBuffers.emplace_back(std::move(buffer)); - uniformBuffersMemory.emplace_back(std::move(bufferMem)); - uniformBuffersMapped.emplace_back( uniformBuffersMemory[i].mapMemory(0, bufferSize)); - } - } - - void createDescriptorPool() { - std::array poolSize { - vk::DescriptorPoolSize( vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT), - vk::DescriptorPoolSize( vk::DescriptorType::eCombinedImageSampler, MAX_FRAMES_IN_FLIGHT) - }; - vk::DescriptorPoolCreateInfo poolInfo{ - .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, - .maxSets = MAX_FRAMES_IN_FLIGHT, - .poolSizeCount = static_cast(poolSize.size()), - .pPoolSizes = poolSize.data() - }; - descriptorPool = vk::raii::DescriptorPool(device, poolInfo); - } - - void createDescriptorSets() { - std::vector layouts(MAX_FRAMES_IN_FLIGHT, *descriptorSetLayout); - vk::DescriptorSetAllocateInfo allocInfo{ - .descriptorPool = *descriptorPool, - .descriptorSetCount = static_cast(layouts.size()), - .pSetLayouts = layouts.data() - }; - - descriptorSets.clear(); - descriptorSets = device.allocateDescriptorSets(allocInfo); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DescriptorBufferInfo bufferInfo{ - .buffer = *uniformBuffers[i], - .offset = 0, - .range = sizeof(UniformBufferObject) - }; - vk::DescriptorImageInfo imageInfo{ - .sampler = *textureSampler, - .imageView = *textureImageView, - .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal - }; - std::array descriptorWrites{ - vk::WriteDescriptorSet{ - .dstSet = *descriptorSets[i], - .dstBinding = 0, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eUniformBuffer, - .pBufferInfo = &bufferInfo - }, - vk::WriteDescriptorSet{ - .dstSet = *descriptorSets[i], - .dstBinding = 1, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eCombinedImageSampler, - .pImageInfo = &imageInfo - } - }; - device.updateDescriptorSets(descriptorWrites, {}); - } - } - - void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer& buffer, vk::raii::DeviceMemory& bufferMemory) { - vk::BufferCreateInfo bufferInfo{ - .size = size, - .usage = usage, - .sharingMode = vk::SharingMode::eExclusive - }; - buffer = vk::raii::Buffer(device, bufferInfo); - vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ - .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) - }; - bufferMemory = vk::raii::DeviceMemory(device, allocInfo); - buffer.bindMemory(*bufferMemory, 0); - } - - std::unique_ptr beginSingleTimeCommands() { - vk::CommandBufferAllocateInfo allocInfo{ - .commandPool = *commandPool, - .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = 1 - }; - std::unique_ptr commandBuffer = std::make_unique(std::move(vk::raii::CommandBuffers(device, allocInfo).front())); - - vk::CommandBufferBeginInfo beginInfo{ - .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit - }; - commandBuffer->begin(beginInfo); - - return commandBuffer; - } - - void endSingleTimeCommands(const vk::raii::CommandBuffer& commandBuffer) const { - commandBuffer.end(); - - vk::SubmitInfo submitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandBuffer }; - queue.submit(submitInfo, nullptr); - queue.waitIdle(); - } - - void copyBuffer(vk::raii::Buffer & srcBuffer, vk::raii::Buffer & dstBuffer, vk::DeviceSize size) { - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = *commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1 }; - vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); - commandCopyBuffer.begin(vk::CommandBufferBeginInfo{ .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit }); - commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy{ .size = size }); - commandCopyBuffer.end(); - queue.submit(vk::SubmitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer }, nullptr); - queue.waitIdle(); - } - - uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) { - vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); - - for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { - if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { - return i; - } - } - - throw std::runtime_error("failed to find suitable memory type!"); - } - - void createCommandBuffers() { - commandBuffers.clear(); - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = *commandPool, .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = MAX_FRAMES_IN_FLIGHT }; - commandBuffers = vk::raii::CommandBuffers(device, allocInfo); - } - - void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin({}); - transition_image_layout( - imageIndex, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, - vk::AccessFlagBits2::eColorAttachmentWrite, - vk::PipelineStageFlagBits2::eTopOfPipe, - vk::PipelineStageFlagBits2::eColorAttachmentOutput - ); - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::RenderingAttachmentInfo attachmentInfo = { - .imageView = *swapChainImageViews[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor - }; - vk::RenderingInfo renderingInfo = { - .renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &attachmentInfo - }; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].bindIndexBuffer( *indexBuffer, 0, vk::IndexType::eUint32 ); - commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, *pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); - commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); - commandBuffers[currentFrame].endRendering(); - transition_image_layout( - imageIndex, - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, - {}, - vk::PipelineStageFlagBits2::eColorAttachmentOutput, - vk::PipelineStageFlagBits2::eBottomOfPipe - ); - commandBuffers[currentFrame].end(); - } - - void transition_image_layout( - uint32_t imageIndex, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::AccessFlags2 src_access_mask, - vk::AccessFlags2 dst_access_mask, - vk::PipelineStageFlags2 src_stage_mask, - vk::PipelineStageFlags2 dst_stage_mask - ) { - vk::ImageMemoryBarrier2 barrier = { - .srcStageMask = src_stage_mask, - .srcAccessMask = src_access_mask, - .dstStageMask = dst_stage_mask, - .dstAccessMask = dst_access_mask, - .oldLayout = old_layout, - .newLayout = new_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); - } - - void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); - - for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - } - - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - inFlightFences.emplace_back(device, vk::FenceCreateInfo{ .flags = vk::FenceCreateFlagBits::eSignaled }); - } - } - - void updateUniformBuffer(uint32_t currentImage) const { - static auto startTime = std::chrono::high_resolution_clock::now(); - - auto currentTime = std::chrono::high_resolution_clock::now(); - float time = std::chrono::duration(currentTime - startTime).count(); - - UniformBufferObject ubo{}; - glm::mat4 initialRotation = glm::rotate(glm::mat4(1.0f), glm::radians(-90.0f), glm::vec3(1.0f, 0.0f, 0.0f)); - glm::mat4 continuousRotation = glm::rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.model = continuousRotation * initialRotation; - ubo.view = lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); - ubo.proj[1][1] *= -1; - - memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); - } - - void drawFrame() { - while ( vk::Result::eTimeout == device.waitForFences( *inFlightFences[currentFrame], vk::True, UINT64_MAX ) ) - ; - auto [result, imageIndex] = swapChain.acquireNextImage( UINT64_MAX, *presentCompleteSemaphore[currentFrame], nullptr ); - - if (result == vk::Result::eErrorOutOfDateKHR) { - recreateSwapChain(); - return; - } - if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) { - throw std::runtime_error("failed to acquire swap chain image!"); - } - updateUniformBuffer(currentFrame); - - device.resetFences( *inFlightFences[currentFrame] ); - commandBuffers[currentFrame].reset(); - recordCommandBuffer(imageIndex); - - vk::PipelineStageFlags waitDestinationStageMask( vk::PipelineStageFlagBits::eColorAttachmentOutput ); - const vk::SubmitInfo submitInfo{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[currentFrame], - .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], - .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex] }; - queue.submit(submitInfo, *inFlightFences[currentFrame]); - - - const vk::PresentInfoKHR presentInfoKHR{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], - .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex }; - result = queue.presentKHR(presentInfoKHR); - if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { - framebufferResized = false; - recreateSwapChain(); - } else if (result != vk::Result::eSuccess) { - throw std::runtime_error("failed to present swap chain image!"); - } - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; - } - - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size(), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const & surfaceCapabilities) { - auto minImageCount = std::max( 3u, surfaceCapabilities.minImageCount ); - if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) { - minImageCount = surfaceCapabilities.maxImageCount; - } - return minImageCount; - } - - static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { - assert(!availableFormats.empty()); - const auto formatIt = std::ranges::find_if( - availableFormats, - []( const auto & format ) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; } ); - return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - assert(std::ranges::any_of(availablePresentModes, [](auto presentMode){ return presentMode == vk::PresentModeKHR::eFifo; })); - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eFifo; - } - - vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) { - if (capabilities.currentExtent.width != 0xFFFFFFFF) { - return capabilities.currentExtent; - } + if (result && supported == VK_TRUE) + { + appInfo.profileSupported = true; + appInfo.profile = profileProperties; + LOGI("Device supports Vulkan profile: %s", name); + } + else + { + LOGI("Device does not support Vulkan profile: %s", name); + } + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + auto features = physicalDevice.getFeatures2(); + vk::PhysicalDeviceVulkan13Features vulkan13Features; + vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT extendedDynamicStateFeatures; + vulkan13Features.dynamicRendering = vk::True; + vulkan13Features.synchronization2 = vk::True; + extendedDynamicStateFeatures.extendedDynamicState = vk::True; + vulkan13Features.pNext = &extendedDynamicStateFeatures; + features.pNext = &vulkan13Features; + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{ + .pNext = &features, + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + // Create the device with the appropriate features + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(*surface); + swapChainExtent = chooseSwapExtent(surfaceCapabilities); + swapChainSurfaceFormat = chooseSwapSurfaceFormat(physicalDevice.getSurfaceFormatsKHR(*surface)); + vk::SwapchainCreateInfoKHR swapChainCreateInfo{.surface = *surface, + .minImageCount = chooseSwapMinImageCount(surfaceCapabilities), + .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + void createImageViews() + { + vk::ImageViewCreateInfo imageViewCreateInfo{ + .viewType = vk::ImageViewType::e2D, + .format = swapChainSurfaceFormat.format, + .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + for (auto image : swapChainImages) + { + imageViewCreateInfo.image = image; + swapChainImageViews.emplace_back(device, imageViewCreateInfo); + } + } + + void createDescriptorSetLayout() + { + std::array bindings = { + vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr), + vk::DescriptorSetLayoutBinding(1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment, nullptr)}; + + vk::DescriptorSetLayoutCreateInfo layoutInfo{.bindingCount = static_cast(bindings.size()), .pBindings = bindings.data()}; + descriptorSetLayout = vk::raii::DescriptorSetLayout(device, layoutInfo); + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(this->readFile("shaders/slang.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}; + + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{ + .vertexBindingDescriptionCount = 1, + .pVertexBindingDescriptions = &bindingDescription, + .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), + .pVertexAttributeDescriptions = attributeDescriptions.data()}; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ + .topology = vk::PrimitiveTopology::eTriangleList, + .primitiveRestartEnable = vk::False}; + vk::PipelineViewportStateCreateInfo viewportState{ + .viewportCount = 1, + .scissorCount = 1}; + vk::PipelineRasterizationStateCreateInfo rasterizer{ + .depthClampEnable = vk::False, + .rasterizerDiscardEnable = vk::False, + .polygonMode = vk::PolygonMode::eFill, + .cullMode = vk::CullModeFlagBits::eBack, // Re-enabled culling for better performance + .frontFace = vk::FrontFace::eClockwise, // Keeping Clockwise for glTF + .depthBiasEnable = vk::False}; + rasterizer.lineWidth = 1.0f; + vk::PipelineMultisampleStateCreateInfo multisampling{ + .rasterizationSamples = vk::SampleCountFlagBits::e1, + .sampleShadingEnable = vk::False}; + vk::PipelineDepthStencilStateCreateInfo depthStencil{ + .depthTestEnable = vk::True, + .depthWriteEnable = vk::True, + .depthCompareOp = vk::CompareOp::eLess, + .depthBoundsTestEnable = vk::False, + .stencilTestEnable = vk::False}; + vk::PipelineColorBlendAttachmentState colorBlendAttachment; + colorBlendAttachment.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA; + colorBlendAttachment.blendEnable = vk::False; + + vk::PipelineColorBlendStateCreateInfo colorBlending{ + .logicOpEnable = vk::False, + .logicOp = vk::LogicOp::eCopy, + .attachmentCount = 1, + .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0}; + + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format}; + vk::GraphicsPipelineCreateInfo pipelineInfo{.pNext = &pipelineRenderingCreateInfo, + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pDepthStencilState = &depthStencil, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = *pipelineLayout, + .renderPass = nullptr}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{ + .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex}; + commandPool = vk::raii::CommandPool(device, poolInfo); + } + + void createDepthResources() + { + vk::Format depthFormat = findDepthFormat(); + + createImage(swapChainExtent.width, swapChainExtent.height, depthFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eDepthStencilAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, depthImage, depthImageMemory); + depthImageView = createImageView(depthImage, depthFormat, vk::ImageAspectFlagBits::eDepth); + } + + vk::Format findSupportedFormat(const std::vector &candidates, vk::ImageTiling tiling, vk::FormatFeatureFlags features) const + { + for (const auto format : candidates) + { + vk::FormatProperties props = physicalDevice.getFormatProperties(format); + + if (tiling == vk::ImageTiling::eLinear && (props.linearTilingFeatures & features) == features) + { + return format; + } + if (tiling == vk::ImageTiling::eOptimal && (props.optimalTilingFeatures & features) == features) + { + return format; + } + } + + throw std::runtime_error("failed to find supported format!"); + } + + [[nodiscard]] vk::Format findDepthFormat() const + { + return findSupportedFormat( + {vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, vk::Format::eD24UnormS8Uint}, + vk::ImageTiling::eOptimal, + vk::FormatFeatureFlagBits::eDepthStencilAttachment); + } + + static bool hasStencilComponent(vk::Format format) + { + return format == vk::Format::eD32SfloatS8Uint || format == vk::Format::eD24UnormS8Uint; + } + + void createTextureImage() + { + // Load KTX2 texture instead of using stb_image + ktxTexture *kTexture; + KTX_error_code result = ktxTexture_CreateFromNamedFile( + TEXTURE_PATH.c_str(), + KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, + &kTexture); + + if (result != KTX_SUCCESS) + { + throw std::runtime_error("failed to load ktx texture image!"); + } + + // Get texture dimensions and data + uint32_t texWidth = kTexture->baseWidth; + uint32_t texHeight = kTexture->baseHeight; + ktx_size_t imageSize = ktxTexture_GetImageSize(kTexture, 0); + ktx_uint8_t *ktxTextureData = ktxTexture_GetData(kTexture); + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, imageSize); + memcpy(data, ktxTextureData, imageSize); + stagingBufferMemory.unmapMemory(); + + // Determine the Vulkan format from KTX format + vk::Format textureFormat; + + // Check if the KTX texture has a format + if (kTexture->classId == ktxTexture2_c) + { + // For KTX2 files, we can get the format directly + auto *ktx2 = reinterpret_cast(kTexture); + textureFormat = static_cast(ktx2->vkFormat); + if (textureFormat == vk::Format::eUndefined) + { + // If the format is undefined, fall back to a reasonable default + textureFormat = vk::Format::eR8G8B8A8Unorm; + } + } + else + { + // For KTX1 files or if we can't determine the format, use a reasonable default + textureFormat = vk::Format::eR8G8B8A8Unorm; + } + + textureImageFormat = textureFormat; + + createImage(texWidth, texHeight, textureFormat, vk::ImageTiling::eOptimal, + vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, + vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory); + + transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal); + copyBufferToImage(stagingBuffer, textureImage, texWidth, texHeight); + transitionImageLayout(textureImage, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal); + + ktxTexture_Destroy(kTexture); + } + + void createTextureImageView() + { + textureImageView = createImageView(textureImage, textureImageFormat, vk::ImageAspectFlagBits::eColor); + } + + void createTextureSampler() + { + vk::PhysicalDeviceProperties properties = physicalDevice.getProperties(); + vk::SamplerCreateInfo samplerInfo{ + .magFilter = vk::Filter::eLinear, + .minFilter = vk::Filter::eLinear, + .mipmapMode = vk::SamplerMipmapMode::eLinear, + .addressModeU = vk::SamplerAddressMode::eRepeat, + .addressModeV = vk::SamplerAddressMode::eRepeat, + .addressModeW = vk::SamplerAddressMode::eRepeat, + .mipLodBias = 0.0f, + .anisotropyEnable = vk::True, + .maxAnisotropy = properties.limits.maxSamplerAnisotropy, + .compareEnable = vk::False, + .compareOp = vk::CompareOp::eAlways}; + textureSampler = vk::raii::Sampler(device, samplerInfo); + } + + vk::raii::ImageView createImageView(vk::raii::Image &image, vk::Format format, vk::ImageAspectFlags aspectFlags) + { + vk::ImageViewCreateInfo viewInfo{ + .image = *image, + .viewType = vk::ImageViewType::e2D, + .format = format, + .subresourceRange = {aspectFlags, 0, 1, 0, 1}}; + return vk::raii::ImageView(device, viewInfo); + } + + void createImage(uint32_t width, uint32_t height, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image &image, vk::raii::DeviceMemory &imageMemory) + { + vk::ImageCreateInfo imageInfo{ + .imageType = vk::ImageType::e2D, + .format = format, + .extent = {width, height, 1}, + .mipLevels = 1, + .arrayLayers = 1, + .samples = vk::SampleCountFlagBits::e1, + .tiling = tiling, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive, + .initialLayout = vk::ImageLayout::eUndefined}; + image = vk::raii::Image(device, imageInfo); + + vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{ + .allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + imageMemory = vk::raii::DeviceMemory(device, allocInfo); + image.bindMemory(*imageMemory, 0); + } + + void transitionImageLayout(const vk::raii::Image &image, vk::ImageLayout oldLayout, vk::ImageLayout newLayout) + { + auto commandBuffer = beginSingleTimeCommands(); + + vk::ImageMemoryBarrier barrier{ + .oldLayout = oldLayout, + .newLayout = newLayout, + .image = *image, + .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + + vk::PipelineStageFlags sourceStage; + vk::PipelineStageFlags destinationStage; + + if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) + { + barrier.srcAccessMask = {}; + barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; + + sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; + destinationStage = vk::PipelineStageFlagBits::eTransfer; + } + else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) + { + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + + sourceStage = vk::PipelineStageFlagBits::eTransfer; + destinationStage = vk::PipelineStageFlagBits::eFragmentShader; + } + else + { + throw std::invalid_argument("unsupported layout transition!"); + } + commandBuffer->pipelineBarrier(sourceStage, destinationStage, {}, {}, nullptr, barrier); + endSingleTimeCommands(*commandBuffer); + } + + void copyBufferToImage(const vk::raii::Buffer &buffer, vk::raii::Image &image, uint32_t width, uint32_t height) + { + std::unique_ptr commandBuffer = beginSingleTimeCommands(); + vk::BufferImageCopy region{ + .bufferOffset = 0, + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource = {vk::ImageAspectFlagBits::eColor, 0, 0, 1}, + .imageOffset = {0, 0, 0}, + .imageExtent = {width, height, 1}}; + commandBuffer->copyBufferToImage(*buffer, *image, vk::ImageLayout::eTransferDstOptimal, {region}); + endSingleTimeCommands(*commandBuffer); + } + + void loadModel() + { + // Use tinygltf to load the model instead of tinyobjloader + tinygltf::Model model; + tinygltf::TinyGLTF loader; + std::string err; + std::string warn; + + bool ret = loader.LoadBinaryFromFile(&model, &err, &warn, MODEL_PATH); + + if (!warn.empty()) + { + std::cout << "glTF warning: " << warn << std::endl; + } + + if (!err.empty()) + { + std::cout << "glTF error: " << err << std::endl; + } + + if (!ret) + { + throw std::runtime_error("Failed to load glTF model"); + } + + vertices.clear(); + indices.clear(); + + // Process all meshes in the model + for (const auto &mesh : model.meshes) + { + for (const auto &primitive : mesh.primitives) + { + // Get indices + const tinygltf::Accessor &indexAccessor = model.accessors[primitive.indices]; + const tinygltf::BufferView &indexBufferView = model.bufferViews[indexAccessor.bufferView]; + const tinygltf::Buffer &indexBuffer = model.buffers[indexBufferView.buffer]; + + // Get vertex positions + const tinygltf::Accessor &posAccessor = model.accessors[primitive.attributes.at("POSITION")]; + const tinygltf::BufferView &posBufferView = model.bufferViews[posAccessor.bufferView]; + const tinygltf::Buffer &posBuffer = model.buffers[posBufferView.buffer]; + + // Get texture coordinates if available + bool hasTexCoords = primitive.attributes.find("TEXCOORD_0") != primitive.attributes.end(); + const tinygltf::Accessor *texCoordAccessor = nullptr; + const tinygltf::BufferView *texCoordBufferView = nullptr; + const tinygltf::Buffer *texCoordBuffer = nullptr; + + if (hasTexCoords) + { + texCoordAccessor = &model.accessors[primitive.attributes.at("TEXCOORD_0")]; + texCoordBufferView = &model.bufferViews[texCoordAccessor->bufferView]; + texCoordBuffer = &model.buffers[texCoordBufferView->buffer]; + } + + uint32_t baseVertex = static_cast(vertices.size()); + + for (size_t i = 0; i < posAccessor.count; i++) + { + Vertex vertex{}; + + const float *pos = reinterpret_cast(&posBuffer.data[posBufferView.byteOffset + posAccessor.byteOffset + i * 12]); + // glTF uses a right-handed coordinate system with Y-up + // Vulkan uses a right-handed coordinate system with Y-down + // We need to flip the Y coordinate + vertex.pos = {pos[0], -pos[1], pos[2]}; + + if (hasTexCoords) + { + const float *texCoord = reinterpret_cast(&texCoordBuffer->data[texCoordBufferView->byteOffset + texCoordAccessor->byteOffset + i * 8]); + vertex.texCoord = {texCoord[0], texCoord[1]}; + } + else + { + vertex.texCoord = {0.0f, 0.0f}; + } + + vertex.color = {1.0f, 1.0f, 1.0f}; + + vertices.push_back(vertex); + } + + const unsigned char *indexData = &indexBuffer.data[indexBufferView.byteOffset + indexAccessor.byteOffset]; + size_t indexCount = indexAccessor.count; + size_t indexStride = 0; + + // Determine index stride based on component type + if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) + { + indexStride = sizeof(uint16_t); + } + else if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT) + { + indexStride = sizeof(uint32_t); + } + else if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) + { + indexStride = sizeof(uint8_t); + } + else + { + throw std::runtime_error("Unsupported index component type"); + } + + indices.reserve(indices.size() + indexCount); + + for (size_t i = 0; i < indexCount; i++) + { + uint32_t index = 0; + + if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) + { + index = *reinterpret_cast(indexData + i * indexStride); + } + else if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT) + { + index = *reinterpret_cast(indexData + i * indexStride); + } + else if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) + { + index = *reinterpret_cast(indexData + i * indexStride); + } + + indices.push_back(baseVertex + index); + } + } + } + } + + void createVertexBuffer() + { + vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(dataStaging, vertices.data(), bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + } + + void createIndexBuffer() + { + vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(data, indices.data(), bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); + + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + } + + void createUniformBuffers() + { + uniformBuffers.clear(); + uniformBuffersMemory.clear(); + uniformBuffersMapped.clear(); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DeviceSize bufferSize = sizeof(UniformBufferObject); + vk::raii::Buffer buffer({}); + vk::raii::DeviceMemory bufferMem({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); + uniformBuffers.emplace_back(std::move(buffer)); + uniformBuffersMemory.emplace_back(std::move(bufferMem)); + uniformBuffersMapped.emplace_back(uniformBuffersMemory[i].mapMemory(0, bufferSize)); + } + } + + void createDescriptorPool() + { + std::array poolSize{ + vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT), + vk::DescriptorPoolSize(vk::DescriptorType::eCombinedImageSampler, MAX_FRAMES_IN_FLIGHT)}; + vk::DescriptorPoolCreateInfo poolInfo{ + .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, + .maxSets = MAX_FRAMES_IN_FLIGHT, + .poolSizeCount = static_cast(poolSize.size()), + .pPoolSizes = poolSize.data()}; + descriptorPool = vk::raii::DescriptorPool(device, poolInfo); + } + + void createDescriptorSets() + { + std::vector layouts(MAX_FRAMES_IN_FLIGHT, *descriptorSetLayout); + vk::DescriptorSetAllocateInfo allocInfo{ + .descriptorPool = *descriptorPool, + .descriptorSetCount = static_cast(layouts.size()), + .pSetLayouts = layouts.data()}; + + descriptorSets.clear(); + descriptorSets = device.allocateDescriptorSets(allocInfo); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DescriptorBufferInfo bufferInfo{ + .buffer = *uniformBuffers[i], + .offset = 0, + .range = sizeof(UniformBufferObject)}; + vk::DescriptorImageInfo imageInfo{ + .sampler = *textureSampler, + .imageView = *textureImageView, + .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal}; + std::array descriptorWrites{ + vk::WriteDescriptorSet{ + .dstSet = *descriptorSets[i], + .dstBinding = 0, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eUniformBuffer, + .pBufferInfo = &bufferInfo}, + vk::WriteDescriptorSet{ + .dstSet = *descriptorSets[i], + .dstBinding = 1, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .pImageInfo = &imageInfo}}; + device.updateDescriptorSets(descriptorWrites, {}); + } + } + + void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer &buffer, vk::raii::DeviceMemory &bufferMemory) + { + vk::BufferCreateInfo bufferInfo{ + .size = size, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive}; + buffer = vk::raii::Buffer(device, bufferInfo); + vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{ + .allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + bufferMemory = vk::raii::DeviceMemory(device, allocInfo); + buffer.bindMemory(*bufferMemory, 0); + } + + std::unique_ptr beginSingleTimeCommands() + { + vk::CommandBufferAllocateInfo allocInfo{ + .commandPool = *commandPool, + .level = vk::CommandBufferLevel::ePrimary, + .commandBufferCount = 1}; + std::unique_ptr commandBuffer = std::make_unique(std::move(vk::raii::CommandBuffers(device, allocInfo).front())); + + vk::CommandBufferBeginInfo beginInfo{ + .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; + commandBuffer->begin(beginInfo); + + return commandBuffer; + } + + void endSingleTimeCommands(const vk::raii::CommandBuffer &commandBuffer) const + { + commandBuffer.end(); + + vk::SubmitInfo submitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandBuffer}; + queue.submit(submitInfo, nullptr); + queue.waitIdle(); + } + + void copyBuffer(vk::raii::Buffer &srcBuffer, vk::raii::Buffer &dstBuffer, vk::DeviceSize size) + { + vk::CommandBufferAllocateInfo allocInfo{.commandPool = *commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1}; + vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); + commandCopyBuffer.begin(vk::CommandBufferBeginInfo{.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); + commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy{.size = size}); + commandCopyBuffer.end(); + queue.submit(vk::SubmitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer}, nullptr); + queue.waitIdle(); + } + + uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) + { + vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) + { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) + { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + void createCommandBuffers() + { + commandBuffers.clear(); + vk::CommandBufferAllocateInfo allocInfo{.commandPool = *commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = MAX_FRAMES_IN_FLIGHT}; + commandBuffers = vk::raii::CommandBuffers(device, allocInfo); + } + + void recordCommandBuffer(uint32_t imageIndex) + { + commandBuffers[currentFrame].begin({}); + transition_image_layout( + imageIndex, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, + vk::AccessFlagBits2::eColorAttachmentWrite, + vk::PipelineStageFlagBits2::eTopOfPipe, + vk::PipelineStageFlagBits2::eColorAttachmentOutput); + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::RenderingAttachmentInfo attachmentInfo = { + .imageView = *swapChainImageViews[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor}; + vk::RenderingInfo renderingInfo = { + .renderArea = {.offset = {0, 0}, .extent = swapChainExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &attachmentInfo}; + commandBuffers[currentFrame].beginRendering(renderingInfo); + commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint32); + commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, *pipelineLayout, 0, *descriptorSets[currentFrame], nullptr); + commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); + commandBuffers[currentFrame].endRendering(); + transition_image_layout( + imageIndex, + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, + {}, + vk::PipelineStageFlagBits2::eColorAttachmentOutput, + vk::PipelineStageFlagBits2::eBottomOfPipe); + commandBuffers[currentFrame].end(); + } + + void transition_image_layout( + uint32_t imageIndex, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout, + vk::AccessFlags2 src_access_mask, + vk::AccessFlags2 dst_access_mask, + vk::PipelineStageFlags2 src_stage_mask, + vk::PipelineStageFlags2 dst_stage_mask) + { + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = src_stage_mask, + .srcAccessMask = src_access_mask, + .dstStageMask = dst_stage_mask, + .dstAccessMask = dst_access_mask, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo dependency_info = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + } + + void createSyncObjects() + { + presentCompleteSemaphore.clear(); + renderFinishedSemaphore.clear(); + inFlightFences.clear(); + + for (size_t i = 0; i < swapChainImages.size(); i++) + { + presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); + } + } + + void updateUniformBuffer(uint32_t currentImage) const + { + static auto startTime = std::chrono::high_resolution_clock::now(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + UniformBufferObject ubo{}; + glm::mat4 initialRotation = glm::rotate(glm::mat4(1.0f), glm::radians(-90.0f), glm::vec3(1.0f, 0.0f, 0.0f)); + glm::mat4 continuousRotation = glm::rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.model = continuousRotation * initialRotation; + ubo.view = lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); + ubo.proj[1][1] *= -1; + + memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); + } + + void drawFrame() + { + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + ; + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[currentFrame], nullptr); + + if (result == vk::Result::eErrorOutOfDateKHR) + { + recreateSwapChain(); + return; + } + if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) + { + throw std::runtime_error("failed to acquire swap chain image!"); + } + updateUniformBuffer(currentFrame); + + device.resetFences(*inFlightFences[currentFrame]); + commandBuffers[currentFrame].reset(); + recordCommandBuffer(imageIndex); + + vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); + const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, .pWaitSemaphores = &*presentCompleteSemaphore[currentFrame], .pWaitDstStageMask = &waitDestinationStageMask, .commandBufferCount = 1, .pCommandBuffers = &*commandBuffers[currentFrame], .signalSemaphoreCount = 1, .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[currentFrame]); + + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + result = queue.presentKHR(presentInfoKHR); + if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) + { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != vk::Result::eSuccess) + { + throw std::runtime_error("failed to present swap chain image!"); + } + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size(), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const &surfaceCapabilities) + { + auto minImageCount = std::max(3u, surfaceCapabilities.minImageCount); + if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) + { + minImageCount = surfaceCapabilities.maxImageCount; + } + return minImageCount; + } + + static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + assert(!availableFormats.empty()); + const auto formatIt = std::ranges::find_if( + availableFormats, + [](const auto &format) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; }); + return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + assert(std::ranges::any_of(availablePresentModes, [](auto presentMode) { return presentMode == vk::PresentModeKHR::eFifo; })); + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + vk::PresentModeKHR::eFifo; + } + + vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR &capabilities) + { + if (capabilities.currentExtent.width != 0xFFFFFFFF) + { + return capabilities.currentExtent; + } #if PLATFORM_DESKTOP - int width, height; - glfwGetFramebufferSize(window, &width, &height); + int width, height; + glfwGetFramebufferSize(window, &width, &height); #else - ANativeWindow* window = androidAppState.nativeWindow; - int width = ANativeWindow_getWidth(window); - int height = ANativeWindow_getHeight(window); + ANativeWindow *window = androidAppState.nativeWindow; + int width = ANativeWindow_getWidth(window); + int height = ANativeWindow_getHeight(window); #endif - return { - std::clamp(width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width), - std::clamp(height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height) - }; - } + return { + std::clamp(width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width), + std::clamp(height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height)}; + } - [[nodiscard]] std::vector getRequiredExtensions() const { - std::vector extensions; + [[nodiscard]] std::vector getRequiredExtensions() const + { + std::vector extensions; #if PLATFORM_DESKTOP - // Get GLFW extensions - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - extensions.assign(glfwExtensions, glfwExtensions + glfwExtensionCount); + // Get GLFW extensions + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + extensions.assign(glfwExtensions, glfwExtensions + glfwExtensionCount); #else - // Android extensions - extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME); - extensions.push_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME); + // Android extensions + extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME); + extensions.push_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME); #endif - // Add debug extensions if validation layers are enabled - if (enableValidationLayers) { - extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); - } - - return extensions; - } - - [[nodiscard]] bool checkValidationLayerSupport() const { - return (std::ranges::any_of(context.enumerateInstanceLayerProperties(), - []( vk::LayerProperties const & lp ) { return ( strcmp( "VK_LAYER_KHRONOS_validation", lp.layerName ) == 0 ); } ) ); - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - std::vector readFile(const std::string& filename) { + // Add debug extensions if validation layers are enabled + if (enableValidationLayers) + { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + [[nodiscard]] bool checkValidationLayerSupport() const + { + return (std::ranges::any_of(context.enumerateInstanceLayerProperties(), + [](vk::LayerProperties const &lp) { return (strcmp("VK_LAYER_KHRONOS_validation", lp.layerName) == 0); })); + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + std::vector readFile(const std::string &filename) + { #if PLATFORM_ANDROID - // Android asset loading - if (androidAppState.app == nullptr) { - LOGE("Android app not initialized"); - throw std::runtime_error("Android app not initialized"); - } - AAsset* asset = AAssetManager_open(androidAppState.app->activity->assetManager, filename.c_str(), AASSET_MODE_BUFFER); - if (!asset) { - throw std::runtime_error("failed to open file: " + filename); - } - - size_t size = AAsset_getLength(asset); - std::vector buffer(size); - AAsset_read(asset, buffer.data(), size); - AAsset_close(asset); + // Android asset loading + if (androidAppState.app == nullptr) + { + LOGE("Android app not initialized"); + throw std::runtime_error("Android app not initialized"); + } + AAsset *asset = AAssetManager_open(androidAppState.app->activity->assetManager, filename.c_str(), AASSET_MODE_BUFFER); + if (!asset) + { + throw std::runtime_error("failed to open file: " + filename); + } + + size_t size = AAsset_getLength(asset); + std::vector buffer(size); + AAsset_read(asset, buffer.data(), size); + AAsset_close(asset); #else - // Desktop file loading - std::ifstream file(filename, std::ios::ate | std::ios::binary); - if (!file.is_open()) { - throw std::runtime_error("failed to open file: " + filename); - } - - size_t fileSize = static_cast(file.tellg()); - std::vector buffer(fileSize); - file.seekg(0); - file.read(buffer.data(), fileSize); - file.close(); + // Desktop file loading + std::ifstream file(filename, std::ios::ate | std::ios::binary); + if (!file.is_open()) + { + throw std::runtime_error("failed to open file: " + filename); + } + + size_t fileSize = static_cast(file.tellg()); + std::vector buffer(fileSize); + file.seekg(0); + file.read(buffer.data(), fileSize); + file.close(); #endif - return buffer; - } + return buffer; + } }; #if PLATFORM_ANDROID -void android_main(android_app* app) { - app_dummy(); +void android_main(android_app *app) +{ + app_dummy(); - VulkanApplication vulkanApp; - vulkanApp.run(app); + VulkanApplication vulkanApp; + vulkanApp.run(app); } #else -int main() { - try { - VulkanApplication app; - app.run(); - } catch (const std::exception& e) { - LOGE("%s", e.what()); - return EXIT_FAILURE; - } - return EXIT_SUCCESS; +int main() +{ + try + { + VulkanApplication app; + app.run(); + } + catch (const std::exception &e) + { + LOGE("%s", e.what()); + return EXIT_FAILURE; + } + return EXIT_SUCCESS; } #endif diff --git a/attachments/36_multiple_objects.cpp b/attachments/36_multiple_objects.cpp index d23221cc..35e49eb8 100644 --- a/attachments/36_multiple_objects.cpp +++ b/attachments/36_multiple_objects.cpp @@ -13,21 +13,21 @@ #include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include #if defined(__ANDROID__) -#include -#include +# include +# include #endif #include #if defined(__ANDROID__) - #define PLATFORM_ANDROID 1 +# define PLATFORM_ANDROID 1 #else - #define PLATFORM_DESKTOP 1 +# define PLATFORM_DESKTOP 1 #endif // Include tinygltf instead of tinyobjloader @@ -39,34 +39,41 @@ import vulkan_hpp; #include #if PLATFORM_ANDROID - #include - #include - #include - #include - - // Declare and implement app_dummy function from native_app_glue - extern "C" void app_dummy() { - // This is a dummy function that does nothing - // It's used to prevent the linker from stripping out the native_app_glue code - } - - // Define AAssetManager type for Android - typedef AAssetManager AssetManagerType; - - #define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "VulkanTutorial", __VA_ARGS__)) - #define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "VulkanTutorial", __VA_ARGS__)) - #define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "VulkanTutorial", __VA_ARGS__)) +# include +# include +# include +# include + +// Declare and implement app_dummy function from native_app_glue +extern "C" void app_dummy() +{ + // This is a dummy function that does nothing + // It's used to prevent the linker from stripping out the native_app_glue code +} + +// Define AAssetManager type for Android +typedef AAssetManager AssetManagerType; + +# define LOGI(...) ((void) __android_log_print(ANDROID_LOG_INFO, "VulkanTutorial", __VA_ARGS__)) +# define LOGW(...) ((void) __android_log_print(ANDROID_LOG_WARN, "VulkanTutorial", __VA_ARGS__)) +# define LOGE(...) ((void) __android_log_print(ANDROID_LOG_ERROR, "VulkanTutorial", __VA_ARGS__)) #else - // Define AAssetManager type for non-Android platforms - typedef void AssetManagerType; - // Desktop-specific includes - #define GLFW_INCLUDE_VULKAN - #include - - // Define logging macros for Desktop - #define LOGI(...) printf(__VA_ARGS__); printf("\n") - #define LOGW(...) printf(__VA_ARGS__); printf("\n") - #define LOGE(...) fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n") +// Define AAssetManager type for non-Android platforms +typedef void AssetManagerType; +// Desktop-specific includes +# define GLFW_INCLUDE_VULKAN +# include + +// Define logging macros for Desktop +# define LOGI(...) \ + printf(__VA_ARGS__); \ + printf("\n") +# define LOGW(...) \ + printf(__VA_ARGS__); \ + printf("\n") +# define LOGE(...) \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "\n") #endif #define GLM_FORCE_RADIANS @@ -77,48 +84,51 @@ import vulkan_hpp; #include #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; constexpr uint64_t FenceTimeout = 100000000; // Update paths to use glTF model and KTX2 texture -const std::string MODEL_PATH = "models/viking_room.glb"; -const std::string TEXTURE_PATH = "textures/viking_room.ktx2"; -constexpr int MAX_FRAMES_IN_FLIGHT = 2; +const std::string MODEL_PATH = "models/viking_room.glb"; +const std::string TEXTURE_PATH = "textures/viking_room.ktx2"; +constexpr int MAX_FRAMES_IN_FLIGHT = 2; // Define the number of objects to render constexpr int MAX_OBJECTS = 3; // Define VpProfileProperties structure for Android only #if PLATFORM_ANDROID -#ifndef VP_PROFILE_PROPERTIES_DEFINED -#define VP_PROFILE_PROPERTIES_DEFINED -struct VpProfileProperties { - char name[256]; - uint32_t specVersion; +# ifndef VP_PROFILE_PROPERTIES_DEFINED +# define VP_PROFILE_PROPERTIES_DEFINED +struct VpProfileProperties +{ + char name[256]; + uint32_t specVersion; }; -#endif +# endif #endif // Define Vulkan Profile constants #ifndef VP_KHR_ROADMAP_2022_NAME -#define VP_KHR_ROADMAP_2022_NAME "VP_KHR_roadmap_2022" +# define VP_KHR_ROADMAP_2022_NAME "VP_KHR_roadmap_2022" #endif #ifndef VP_KHR_ROADMAP_2022_SPEC_VERSION -#define VP_KHR_ROADMAP_2022_SPEC_VERSION 1 +# define VP_KHR_ROADMAP_2022_SPEC_VERSION 1 #endif -struct AppInfo { - bool profileSupported = false; - VpProfileProperties profile; +struct AppInfo +{ + bool profileSupported = false; + VpProfileProperties profile; }; #if PLATFORM_ANDROID -void android_main(android_app* app); +void android_main(android_app *app); -struct AndroidAppState { - ANativeWindow* nativeWindow = nullptr; - bool initialized = false; - android_app* app = nullptr; +struct AndroidAppState +{ + ANativeWindow *nativeWindow = nullptr; + bool initialized = false; + android_app *app = nullptr; }; #endif @@ -128,1465 +138,1573 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -struct Vertex { - glm::vec3 pos; - glm::vec3 color; - glm::vec2 texCoord; - - static vk::VertexInputBindingDescription getBindingDescription() { - return { 0, sizeof(Vertex), vk::VertexInputRate::eVertex }; - } - - static std::array getAttributeDescriptions() { - return { - vk::VertexInputAttributeDescription( 0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, pos) ), - vk::VertexInputAttributeDescription( 1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color) ), - vk::VertexInputAttributeDescription( 2, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, texCoord) ) - }; - } - - bool operator==(const Vertex& other) const { - return pos == other.pos && color == other.color && texCoord == other.texCoord; - } +struct Vertex +{ + glm::vec3 pos; + glm::vec3 color; + glm::vec2 texCoord; + + static vk::VertexInputBindingDescription getBindingDescription() + { + return {0, sizeof(Vertex), vk::VertexInputRate::eVertex}; + } + + static std::array getAttributeDescriptions() + { + return { + vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, pos)), + vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color)), + vk::VertexInputAttributeDescription(2, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, texCoord))}; + } + + bool operator==(const Vertex &other) const + { + return pos == other.pos && color == other.color && texCoord == other.texCoord; + } }; -template<> struct std::hash { - size_t operator()(Vertex const& vertex) const noexcept { - return ((hash()(vertex.pos) ^ (hash()(vertex.color) << 1)) >> 1) ^ (hash()(vertex.texCoord) << 1); - } +template <> +struct std::hash +{ + size_t operator()(Vertex const &vertex) const noexcept + { + return ((hash()(vertex.pos) ^ (hash()(vertex.color) << 1)) >> 1) ^ (hash()(vertex.texCoord) << 1); + } }; // Define a structure to hold per-object data -struct GameObject { - // Transform properties - glm::vec3 position = {0.0f, 0.0f, 0.0f}; - glm::vec3 rotation = {0.0f, 0.0f, 0.0f}; - glm::vec3 scale = {1.0f, 1.0f, 1.0f}; - - // Uniform buffer for this object (one per frame in flight) - std::vector uniformBuffers; - std::vector uniformBuffersMemory; - std::vector uniformBuffersMapped; - - // Descriptor sets for this object (one per frame in flight) - std::vector descriptorSets; - - // Calculate model matrix based on position, rotation, and scale - glm::mat4 getModelMatrix() const { - glm::mat4 model = glm::mat4(1.0f); - model = glm::translate(model, position); - model = glm::rotate(model, rotation.x, glm::vec3(1.0f, 0.0f, 0.0f)); - model = glm::rotate(model, rotation.y, glm::vec3(0.0f, 1.0f, 0.0f)); - model = glm::rotate(model, rotation.z, glm::vec3(0.0f, 0.0f, 1.0f)); - model = glm::scale(model, scale); - return model; - } +struct GameObject +{ + // Transform properties + glm::vec3 position = {0.0f, 0.0f, 0.0f}; + glm::vec3 rotation = {0.0f, 0.0f, 0.0f}; + glm::vec3 scale = {1.0f, 1.0f, 1.0f}; + + // Uniform buffer for this object (one per frame in flight) + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + std::vector uniformBuffersMapped; + + // Descriptor sets for this object (one per frame in flight) + std::vector descriptorSets; + + // Calculate model matrix based on position, rotation, and scale + glm::mat4 getModelMatrix() const + { + glm::mat4 model = glm::mat4(1.0f); + model = glm::translate(model, position); + model = glm::rotate(model, rotation.x, glm::vec3(1.0f, 0.0f, 0.0f)); + model = glm::rotate(model, rotation.y, glm::vec3(0.0f, 1.0f, 0.0f)); + model = glm::rotate(model, rotation.z, glm::vec3(0.0f, 0.0f, 1.0f)); + model = glm::scale(model, scale); + return model; + } }; -struct UniformBufferObject { - alignas(16) glm::mat4 model; - alignas(16) glm::mat4 view; - alignas(16) glm::mat4 proj; +struct UniformBufferObject +{ + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; }; -class VulkanApplication { -public: +class VulkanApplication +{ + public: #if PLATFORM_ANDROID - void cleanupAndroid() { - // Clean up resources in each GameObject - for (auto& gameObject : gameObjects) { - // Unmap memory - for (size_t i = 0; i < gameObject.uniformBuffersMemory.size(); i++) { - if (gameObject.uniformBuffersMapped[i] != nullptr) { - gameObject.uniformBuffersMemory[i].unmapMemory(); - } - } - - // Clear vectors to release resources - gameObject.uniformBuffers.clear(); - gameObject.uniformBuffersMemory.clear(); - gameObject.uniformBuffersMapped.clear(); - gameObject.descriptorSets.clear(); - } - } - - void run(android_app* app) { - androidAppState.nativeWindow = app->window; - androidAppState.app = app; - app->userData = &androidAppState; - app->onAppCmd = handleAppCommand; - // Note: onInputEvent is no longer a member of android_app in the current NDK version - // Input events are now handled differently - - int events; - android_poll_source* source; - - while (app->destroyRequested == 0) { - while (ALooper_pollOnce(androidAppState.initialized ? 0 : -1, nullptr, &events, (void**)&source) >= 0) { - if (source != nullptr) { - source->process(app, source); - } - } - - if (androidAppState.initialized && androidAppState.nativeWindow != nullptr) { - drawFrame(); - } - } - - if (androidAppState.initialized) { - device.waitIdle(); - cleanupAndroid(); - } - } + void cleanupAndroid() + { + // Clean up resources in each GameObject + for (auto &gameObject : gameObjects) + { + // Unmap memory + for (size_t i = 0; i < gameObject.uniformBuffersMemory.size(); i++) + { + if (gameObject.uniformBuffersMapped[i] != nullptr) + { + gameObject.uniformBuffersMemory[i].unmapMemory(); + } + } + + // Clear vectors to release resources + gameObject.uniformBuffers.clear(); + gameObject.uniformBuffersMemory.clear(); + gameObject.uniformBuffersMapped.clear(); + gameObject.descriptorSets.clear(); + } + } + + void run(android_app *app) + { + androidAppState.nativeWindow = app->window; + androidAppState.app = app; + app->userData = &androidAppState; + app->onAppCmd = handleAppCommand; + // Note: onInputEvent is no longer a member of android_app in the current NDK version + // Input events are now handled differently + + int events; + android_poll_source *source; + + while (app->destroyRequested == 0) + { + while (ALooper_pollOnce(androidAppState.initialized ? 0 : -1, nullptr, &events, (void **) &source) >= 0) + { + if (source != nullptr) + { + source->process(app, source); + } + } + + if (androidAppState.initialized && androidAppState.nativeWindow != nullptr) + { + drawFrame(); + } + } + + if (androidAppState.initialized) + { + device.waitIdle(); + cleanupAndroid(); + } + } #else - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } + void run() + { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } #endif -private: + private: #if PLATFORM_ANDROID - AndroidAppState androidAppState; - - static void handleAppCommand(android_app* app, int32_t cmd) { - auto* appState = static_cast(app->userData); - - switch (cmd) { - case APP_CMD_INIT_WINDOW: - if (app->window != nullptr) { - appState->nativeWindow = app->window; - // We can't cast AndroidAppState to VulkanApplication directly - // Instead, we need to access the VulkanApplication instance through a global variable - // or another mechanism. For now, we'll just set the initialized flag. - appState->initialized = true; - } - break; - case APP_CMD_TERM_WINDOW: - appState->nativeWindow = nullptr; - break; - default: - break; - } - } - - static int32_t handleInputEvent(android_app* app, AInputEvent* event) { - if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) { - float x = AMotionEvent_getX(event, 0); - float y = AMotionEvent_getY(event, 0); - - LOGI("Touch at: %f, %f", x, y); - - return 1; - } - return 0; - } + AndroidAppState androidAppState; + + static void handleAppCommand(android_app *app, int32_t cmd) + { + auto *appState = static_cast(app->userData); + + switch (cmd) + { + case APP_CMD_INIT_WINDOW: + if (app->window != nullptr) + { + appState->nativeWindow = app->window; + // We can't cast AndroidAppState to VulkanApplication directly + // Instead, we need to access the VulkanApplication instance through a global variable + // or another mechanism. For now, we'll just set the initialized flag. + appState->initialized = true; + } + break; + case APP_CMD_TERM_WINDOW: + appState->nativeWindow = nullptr; + break; + default: + break; + } + } + + static int32_t handleInputEvent(android_app *app, AInputEvent *event) + { + if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) + { + float x = AMotionEvent_getX(event, 0); + float y = AMotionEvent_getY(event, 0); + + LOGI("Touch at: %f, %f", x, y); + + return 1; + } + return 0; + } #else - GLFWwindow* window = nullptr; + GLFWwindow *window = nullptr; #endif - AppInfo appInfo = {}; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::SurfaceFormatKHR swapChainSurfaceFormat; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - - vk::raii::Image depthImage = nullptr; - vk::raii::DeviceMemory depthImageMemory = nullptr; - vk::raii::ImageView depthImageView = nullptr; - - vk::raii::Image textureImage = nullptr; - vk::raii::DeviceMemory textureImageMemory = nullptr; - vk::raii::ImageView textureImageView = nullptr; - vk::raii::Sampler textureSampler = nullptr; - vk::Format textureImageFormat = vk::Format::eUndefined; - - std::vector vertices; - std::vector indices; - vk::raii::Buffer vertexBuffer = nullptr; - vk::raii::DeviceMemory vertexBufferMemory = nullptr; - vk::raii::Buffer indexBuffer = nullptr; - vk::raii::DeviceMemory indexBufferMemory = nullptr; - - // Array of game objects to render - std::array gameObjects; - - vk::raii::DescriptorPool descriptorPool = nullptr; - - vk::raii::CommandPool commandPool = nullptr; - std::vector commandBuffers; - - std::vector presentCompleteSemaphore; - std::vector renderFinishedSemaphore; - std::vector inFlightFences; - uint32_t currentFrame = 0; - - bool framebufferResized = false; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; + AppInfo appInfo = {}; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::SurfaceFormatKHR swapChainSurfaceFormat; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + + vk::raii::Image depthImage = nullptr; + vk::raii::DeviceMemory depthImageMemory = nullptr; + vk::raii::ImageView depthImageView = nullptr; + + vk::raii::Image textureImage = nullptr; + vk::raii::DeviceMemory textureImageMemory = nullptr; + vk::raii::ImageView textureImageView = nullptr; + vk::raii::Sampler textureSampler = nullptr; + vk::Format textureImageFormat = vk::Format::eUndefined; + + std::vector vertices; + std::vector indices; + vk::raii::Buffer vertexBuffer = nullptr; + vk::raii::DeviceMemory vertexBufferMemory = nullptr; + vk::raii::Buffer indexBuffer = nullptr; + vk::raii::DeviceMemory indexBufferMemory = nullptr; + + // Array of game objects to render + std::array gameObjects; + + vk::raii::DescriptorPool descriptorPool = nullptr; + + vk::raii::CommandPool commandPool = nullptr; + std::vector commandBuffers; + + std::vector presentCompleteSemaphore; + std::vector renderFinishedSemaphore; + std::vector inFlightFences; + uint32_t currentFrame = 0; + + bool framebufferResized = false; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; #if PLATFORM_DESKTOP - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); - } - - static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { - auto app = static_cast(glfwGetWindowUserPointer(window)); - app->framebufferResized = true; - } + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow *window, int width, int height) + { + auto app = static_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } #endif -public: - void initVulkan() { - createInstance(); - setupDebugMessenger(); - createSurface(); - pickPhysicalDevice(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createDescriptorSetLayout(); - createGraphicsPipeline(); - createCommandPool(); - createDepthResources(); - createTextureImage(); - createTextureImageView(); - createTextureSampler(); - loadModel(); - createVertexBuffer(); - createIndexBuffer(); - setupGameObjects(); - createUniformBuffers(); - createDescriptorPool(); - createDescriptorSets(); - createCommandBuffers(); - createSyncObjects(); - } - -private: - + public: + void initVulkan() + { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createDescriptorSetLayout(); + createGraphicsPipeline(); + createCommandPool(); + createDepthResources(); + createTextureImage(); + createTextureImageView(); + createTextureSampler(); + loadModel(); + createVertexBuffer(); + createIndexBuffer(); + setupGameObjects(); + createUniformBuffers(); + createDescriptorPool(); + createDescriptorSets(); + createCommandBuffers(); + createSyncObjects(); + } + + private: #if PLATFORM_DESKTOP - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - drawFrame(); - } - - device.waitIdle(); - } + void mainLoop() + { + while (!glfwWindowShouldClose(window)) + { + glfwPollEvents(); + drawFrame(); + } + + device.waitIdle(); + } #endif - void cleanupSwapChain() { - swapChainImageViews.clear(); - swapChain = nullptr; - } + void cleanupSwapChain() + { + swapChainImageViews.clear(); + swapChain = nullptr; + } #if PLATFORM_DESKTOP - void cleanup() { - // Clean up resources in each GameObject - for (auto& gameObject : gameObjects) { - // Unmap memory - for (size_t i = 0; i < gameObject.uniformBuffersMemory.size(); i++) { - if (gameObject.uniformBuffersMapped[i] != nullptr) { - gameObject.uniformBuffersMemory[i].unmapMemory(); - } - } - - // Clear vectors to release resources - gameObject.uniformBuffers.clear(); - gameObject.uniformBuffersMemory.clear(); - gameObject.uniformBuffersMapped.clear(); - gameObject.descriptorSets.clear(); - } - - // Clean up GLFW resources - glfwDestroyWindow(window); - glfwTerminate(); - } + void cleanup() + { + // Clean up resources in each GameObject + for (auto &gameObject : gameObjects) + { + // Unmap memory + for (size_t i = 0; i < gameObject.uniformBuffersMemory.size(); i++) + { + if (gameObject.uniformBuffersMapped[i] != nullptr) + { + gameObject.uniformBuffersMemory[i].unmapMemory(); + } + } + + // Clear vectors to release resources + gameObject.uniformBuffers.clear(); + gameObject.uniformBuffersMemory.clear(); + gameObject.uniformBuffersMapped.clear(); + gameObject.descriptorSets.clear(); + } + + // Clean up GLFW resources + glfwDestroyWindow(window); + glfwTerminate(); + } #endif - void recreateSwapChain() { + void recreateSwapChain() + { #if PLATFORM_DESKTOP - int width = 0, height = 0; - glfwGetFramebufferSize(window, &width, &height); - while (width == 0 || height == 0) { - glfwGetFramebufferSize(window, &width, &height); - glfwWaitEvents(); - } + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) + { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } #endif - device.waitIdle(); - - cleanupSwapChain(); - createSwapChain(); - createImageViews(); - createDepthResources(); - } - - 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_API_VERSION_1_3 - }; - - auto extensions = getRequiredExtensions(); - - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledExtensionCount = static_cast(extensions.size()), - .ppEnabledExtensionNames = extensions.data() - }; - - instance = vk::raii::Instance(context, createInfo); - LOGI("Vulkan instance created"); - } - - void setupDebugMessenger() { - // Debug messenger setup is disabled for now to avoid compatibility issues - // This is a simplified approach to get the code compiling - if (!enableValidationLayers) return; - - LOGI("Debug messenger setup skipped for compatibility"); - } - - void createSurface() { + device.waitIdle(); + + cleanupSwapChain(); + createSwapChain(); + createImageViews(); + createDepthResources(); + } + + 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_API_VERSION_1_3}; + + auto extensions = getRequiredExtensions(); + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledExtensionCount = static_cast(extensions.size()), + .ppEnabledExtensionNames = extensions.data()}; + + instance = vk::raii::Instance(context, createInfo); + LOGI("Vulkan instance created"); + } + + void setupDebugMessenger() + { + // Debug messenger setup is disabled for now to avoid compatibility issues + // This is a simplified approach to get the code compiling + if (!enableValidationLayers) + return; + + LOGI("Debug messenger setup skipped for compatibility"); + } + + void createSurface() + { #if PLATFORM_DESKTOP - VkSurfaceKHR _surface; - if (glfwCreateWindowSurface(*instance, window, nullptr, &_surface) != VK_SUCCESS) { - throw std::runtime_error("failed to create window surface!"); - } - surface = vk::raii::SurfaceKHR(instance, _surface); + VkSurfaceKHR _surface; + if (glfwCreateWindowSurface(*instance, window, nullptr, &_surface) != VK_SUCCESS) + { + throw std::runtime_error("failed to create window surface!"); + } + surface = vk::raii::SurfaceKHR(instance, _surface); #else - VkSurfaceKHR _surface; - VkAndroidSurfaceCreateInfoKHR createInfo{ - .sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR, - .window = androidAppState.nativeWindow - }; - if (vkCreateAndroidSurfaceKHR(*instance, &createInfo, nullptr, &_surface) != VK_SUCCESS) { - throw std::runtime_error("failed to create Android surface!"); - } - surface = vk::raii::SurfaceKHR(instance, _surface); + VkSurfaceKHR _surface; + VkAndroidSurfaceCreateInfoKHR createInfo{ + .sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR, + .window = androidAppState.nativeWindow}; + if (vkCreateAndroidSurfaceKHR(*instance, &createInfo, nullptr, &_surface) != VK_SUCCESS) + { + throw std::runtime_error("failed to create Android surface!"); + } + surface = vk::raii::SurfaceKHR(instance, _surface); #endif - } + } - void pickPhysicalDevice() { - std::vector devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( + void pickPhysicalDevice() + { + std::vector devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( devices, - [&](auto const& device) { + [&](auto const &device) { // Check if the device supports the Vulkan 1.3 API version bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; // Check if any of the queue families support graphics operations auto queueFamilies = device.getQueueFamilyProperties(); bool supportsGraphics = - std::ranges::any_of(queueFamilies, [](auto const& qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); // Check if all required device extensions are available auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); bool supportsAllRequiredExtensions = std::ranges::all_of(requiredDeviceExtension, - [&availableDeviceExtensions](auto const& requiredDeviceExtension) { - return std::ranges::any_of(availableDeviceExtensions, - [requiredDeviceExtension](auto const& availableDeviceExtension) { - return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; - }); - }); - - auto features = device.template getFeatures2(); + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { + return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; + }); + }); + + auto features = device.template getFeatures2(); bool supportsRequiredFeatures = features.template get().dynamicRendering && - features.template get().extendedDynamicState; + features.template get().extendedDynamicState; return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; }); - if (devIter != devices.end()) { - physicalDevice = *devIter; + if (devIter != devices.end()) + { + physicalDevice = *devIter; - // Check for Vulkan profile support - VpProfileProperties profileProperties; + // Check for Vulkan profile support + VpProfileProperties profileProperties; #if PLATFORM_ANDROID - strcpy(profileProperties.name, VP_KHR_ROADMAP_2022_NAME); + strcpy(profileProperties.name, VP_KHR_ROADMAP_2022_NAME); #else - strcpy(profileProperties.profileName, VP_KHR_ROADMAP_2022_NAME); + strcpy(profileProperties.profileName, VP_KHR_ROADMAP_2022_NAME); #endif - profileProperties.specVersion = VP_KHR_ROADMAP_2022_SPEC_VERSION; + profileProperties.specVersion = VP_KHR_ROADMAP_2022_SPEC_VERSION; - VkBool32 supported = VK_FALSE; - bool result = false; + VkBool32 supported = VK_FALSE; + bool result = false; #if PLATFORM_ANDROID - // Create a vp::ProfileDesc from our VpProfileProperties - vp::ProfileDesc profileDesc = { - profileProperties.name, - profileProperties.specVersion - }; - - // Use vp::GetProfileSupport for Android - result = vp::GetProfileSupport( - *physicalDevice, // Pass the physical device directly - &profileDesc, // Pass the profile description - &supported // Output parameter for support status - ); + // Create a vp::ProfileDesc from our VpProfileProperties + vp::ProfileDesc profileDesc = { + profileProperties.name, + profileProperties.specVersion}; + + // Use vp::GetProfileSupport for Android + result = vp::GetProfileSupport( + *physicalDevice, // Pass the physical device directly + &profileDesc, // Pass the profile description + &supported // Output parameter for support status + ); #else - // Use vpGetPhysicalDeviceProfileSupport for Desktop - VkResult vk_result = vpGetPhysicalDeviceProfileSupport( - *instance, - *physicalDevice, - &profileProperties, - &supported - ); - result = vk_result == static_cast(vk::Result::eSuccess); + // Use vpGetPhysicalDeviceProfileSupport for Desktop + VkResult vk_result = vpGetPhysicalDeviceProfileSupport( + *instance, + *physicalDevice, + &profileProperties, + &supported); + result = vk_result == static_cast(vk::Result::eSuccess); #endif - const char* name = nullptr; + const char *name = nullptr; #ifdef PLATFORM_ANDROID - name = profileProperties.name; + name = profileProperties.name; #else - name = profileProperties.profileName; + name = profileProperties.profileName; #endif - if (result && supported == VK_TRUE) { - appInfo.profileSupported = true; - appInfo.profile = profileProperties; - LOGI("Device supports Vulkan profile: %s", name); - } else { - LOGI("Device does not support Vulkan profile: %s", name); - } - } else { - throw std::runtime_error("failed to find a suitable GPU!"); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - // query for Vulkan 1.3 features - auto features = physicalDevice.getFeatures2(); - vk::PhysicalDeviceVulkan13Features vulkan13Features; - vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT extendedDynamicStateFeatures; - vulkan13Features.dynamicRendering = vk::True; - vulkan13Features.synchronization2 = vk::True; - extendedDynamicStateFeatures.extendedDynamicState = vk::True; - vulkan13Features.pNext = &extendedDynamicStateFeatures; - features.pNext = &vulkan13Features; - // create a Device - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo { .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; - vk::DeviceCreateInfo deviceCreateInfo{ - .pNext = &features, - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() - }; - - // Create the device with the appropriate features - device = vk::raii::Device(physicalDevice, deviceCreateInfo); - - queue = vk::raii::Queue(device, queueIndex, 0); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( *surface ); - swapChainExtent = chooseSwapExtent( surfaceCapabilities ); - swapChainSurfaceFormat = chooseSwapSurfaceFormat( physicalDevice.getSurfaceFormatsKHR( *surface ) ); - vk::SwapchainCreateInfoKHR swapChainCreateInfo{ .surface = *surface, - .minImageCount = chooseSwapMinImageCount( surfaceCapabilities ), - .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 }; - - swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); - swapChainImages = swapChain.getImages(); - } - - void createImageViews() { - vk::ImageViewCreateInfo imageViewCreateInfo{ - .viewType = vk::ImageViewType::e2D, - .format = swapChainSurfaceFormat.format, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } - }; - for ( auto image : swapChainImages ) - { - imageViewCreateInfo.image = image; - swapChainImageViews.emplace_back( device, imageViewCreateInfo ); - } - } - - void createDescriptorSetLayout() { - std::array bindings = { - vk::DescriptorSetLayoutBinding( 0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr), - vk::DescriptorSetLayoutBinding( 1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment, nullptr) - }; - - vk::DescriptorSetLayoutCreateInfo layoutInfo{ .bindingCount = static_cast(bindings.size()), .pBindings = bindings.data() }; - descriptorSetLayout = vk::raii::DescriptorSetLayout(device, layoutInfo); - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(this->readFile("shaders/slang.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}; - - auto bindingDescription = Vertex::getBindingDescription(); - auto attributeDescriptions = Vertex::getAttributeDescriptions(); - vk::PipelineVertexInputStateCreateInfo vertexInputInfo{ - .vertexBindingDescriptionCount = 1, - .pVertexBindingDescriptions = &bindingDescription, - .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), - .pVertexAttributeDescriptions = attributeDescriptions.data() - }; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ - .topology = vk::PrimitiveTopology::eTriangleList, - .primitiveRestartEnable = vk::False - }; - vk::PipelineViewportStateCreateInfo viewportState{ - .viewportCount = 1, - .scissorCount = 1 - }; - vk::PipelineRasterizationStateCreateInfo rasterizer{ - .depthClampEnable = vk::False, - .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, - .cullMode = vk::CullModeFlagBits::eBack, // Re-enabled culling for better performance - .frontFace = vk::FrontFace::eClockwise, // Keeping Clockwise for glTF - .depthBiasEnable = vk::False - }; - rasterizer.lineWidth = 1.0f; - vk::PipelineMultisampleStateCreateInfo multisampling{ - .rasterizationSamples = vk::SampleCountFlagBits::e1, - .sampleShadingEnable = vk::False - }; - vk::PipelineDepthStencilStateCreateInfo depthStencil{ - .depthTestEnable = vk::True, - .depthWriteEnable = vk::True, - .depthCompareOp = vk::CompareOp::eLess, - .depthBoundsTestEnable = vk::False, - .stencilTestEnable = vk::False - }; - vk::PipelineColorBlendAttachmentState colorBlendAttachment; - colorBlendAttachment.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA; - colorBlendAttachment.blendEnable = vk::False; - - vk::PipelineColorBlendStateCreateInfo colorBlending{ - .logicOpEnable = vk::False, - .logicOp = vk::LogicOp::eCopy, - .attachmentCount = 1, - .pAttachments = &colorBlendAttachment - }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0 }; - - pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); - - vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ .colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format }; - vk::GraphicsPipelineCreateInfo pipelineInfo{ .pNext = &pipelineRenderingCreateInfo, - .stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pDepthStencilState = &depthStencil, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = *pipelineLayout, - .renderPass = nullptr - }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{ - .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex - }; - commandPool = vk::raii::CommandPool(device, poolInfo); - } - - void createDepthResources() { - vk::Format depthFormat = findDepthFormat(); - - createImage(swapChainExtent.width, swapChainExtent.height, depthFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eDepthStencilAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, depthImage, depthImageMemory); - depthImageView = createImageView(depthImage, depthFormat, vk::ImageAspectFlagBits::eDepth); - } - - vk::Format findSupportedFormat(const std::vector& candidates, vk::ImageTiling tiling, vk::FormatFeatureFlags features) const { - for (const auto format : candidates) { - vk::FormatProperties props = physicalDevice.getFormatProperties(format); - - if (tiling == vk::ImageTiling::eLinear && (props.linearTilingFeatures & features) == features) { - return format; - } - if (tiling == vk::ImageTiling::eOptimal && (props.optimalTilingFeatures & features) == features) { - return format; - } - } - - throw std::runtime_error("failed to find supported format!"); - } - - [[nodiscard]] vk::Format findDepthFormat() const { - return findSupportedFormat( - {vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, vk::Format::eD24UnormS8Uint}, - vk::ImageTiling::eOptimal, - vk::FormatFeatureFlagBits::eDepthStencilAttachment - ); - } - - static bool hasStencilComponent(vk::Format format) { - return format == vk::Format::eD32SfloatS8Uint || format == vk::Format::eD24UnormS8Uint; - } - - void createTextureImage() { - // Load KTX2 texture instead of using stb_image - ktxTexture* kTexture; - KTX_error_code result = ktxTexture_CreateFromNamedFile( - TEXTURE_PATH.c_str(), - KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, - &kTexture); - - if (result != KTX_SUCCESS) { - throw std::runtime_error("failed to load ktx texture image!"); - } - - // Get texture dimensions and data - uint32_t texWidth = kTexture->baseWidth; - uint32_t texHeight = kTexture->baseHeight; - ktx_size_t imageSize = ktxTexture_GetImageSize(kTexture, 0); - ktx_uint8_t* ktxTextureData = ktxTexture_GetData(kTexture); - - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, imageSize); - memcpy(data, ktxTextureData, imageSize); - stagingBufferMemory.unmapMemory(); - - // Determine the Vulkan format from KTX format - vk::Format textureFormat; - - // Check if the KTX texture has a format - if (kTexture->classId == ktxTexture2_c) { - // For KTX2 files, we can get the format directly - auto* ktx2 = reinterpret_cast(kTexture); - textureFormat = static_cast(ktx2->vkFormat); - if (textureFormat == vk::Format::eUndefined) { - // If the format is undefined, fall back to a reasonable default - textureFormat = vk::Format::eR8G8B8A8Unorm; - } - } else { - // For KTX1 files or if we can't determine the format, use a reasonable default - textureFormat = vk::Format::eR8G8B8A8Unorm; - } - - textureImageFormat = textureFormat; - - createImage(texWidth, texHeight, textureFormat, vk::ImageTiling::eOptimal, - vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, - vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory); - - transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal); - copyBufferToImage(stagingBuffer, textureImage, texWidth, texHeight); - transitionImageLayout(textureImage, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal); - - ktxTexture_Destroy(kTexture); - } - - void createTextureImageView() { - textureImageView = createImageView(textureImage, textureImageFormat, vk::ImageAspectFlagBits::eColor); - } - - void createTextureSampler() { - vk::PhysicalDeviceProperties properties = physicalDevice.getProperties(); - vk::SamplerCreateInfo samplerInfo{ - .magFilter = vk::Filter::eLinear, - .minFilter = vk::Filter::eLinear, - .mipmapMode = vk::SamplerMipmapMode::eLinear, - .addressModeU = vk::SamplerAddressMode::eRepeat, - .addressModeV = vk::SamplerAddressMode::eRepeat, - .addressModeW = vk::SamplerAddressMode::eRepeat, - .mipLodBias = 0.0f, - .anisotropyEnable = vk::True, - .maxAnisotropy = properties.limits.maxSamplerAnisotropy, - .compareEnable = vk::False, - .compareOp = vk::CompareOp::eAlways - }; - textureSampler = vk::raii::Sampler(device, samplerInfo); - } - - vk::raii::ImageView createImageView(vk::raii::Image& image, vk::Format format, vk::ImageAspectFlags aspectFlags) { - vk::ImageViewCreateInfo viewInfo{ - .image = *image, - .viewType = vk::ImageViewType::e2D, - .format = format, - .subresourceRange = { aspectFlags, 0, 1, 0, 1 } - }; - return vk::raii::ImageView(device, viewInfo); - } - - void createImage(uint32_t width, uint32_t height, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image& image, vk::raii::DeviceMemory& imageMemory) { - vk::ImageCreateInfo imageInfo{ - .imageType = vk::ImageType::e2D, - .format = format, - .extent = {width, height, 1}, - .mipLevels = 1, - .arrayLayers = 1, - .samples = vk::SampleCountFlagBits::e1, - .tiling = tiling, - .usage = usage, - .sharingMode = vk::SharingMode::eExclusive, - .initialLayout = vk::ImageLayout::eUndefined - }; - image = vk::raii::Image(device, imageInfo); - - vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ - .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) - }; - imageMemory = vk::raii::DeviceMemory(device, allocInfo); - image.bindMemory(*imageMemory, 0); - } - - void transitionImageLayout(const vk::raii::Image& image, vk::ImageLayout oldLayout, vk::ImageLayout newLayout) { - auto commandBuffer = beginSingleTimeCommands(); - - vk::ImageMemoryBarrier barrier{ - .oldLayout = oldLayout, - .newLayout = newLayout, - .image = *image, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } - }; - - vk::PipelineStageFlags sourceStage; - vk::PipelineStageFlags destinationStage; - - if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) { - barrier.srcAccessMask = {}; - barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; - - sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; - destinationStage = vk::PipelineStageFlagBits::eTransfer; - } else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) { - barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; - barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - - sourceStage = vk::PipelineStageFlagBits::eTransfer; - destinationStage = vk::PipelineStageFlagBits::eFragmentShader; - } else { - throw std::invalid_argument("unsupported layout transition!"); - } - commandBuffer->pipelineBarrier( sourceStage, destinationStage, {}, {}, nullptr, barrier ); - endSingleTimeCommands(*commandBuffer); - } - - void copyBufferToImage(const vk::raii::Buffer& buffer, vk::raii::Image& image, uint32_t width, uint32_t height) { - std::unique_ptr commandBuffer = beginSingleTimeCommands(); - vk::BufferImageCopy region{ - .bufferOffset = 0, - .bufferRowLength = 0, - .bufferImageHeight = 0, - .imageSubresource = { vk::ImageAspectFlagBits::eColor, 0, 0, 1 }, - .imageOffset = {0, 0, 0}, - .imageExtent = {width, height, 1} - }; - commandBuffer->copyBufferToImage(*buffer, *image, vk::ImageLayout::eTransferDstOptimal, {region}); - endSingleTimeCommands(*commandBuffer); - } - - void loadModel() { - // Use tinygltf to load the model instead of tinyobjloader - tinygltf::Model model; - tinygltf::TinyGLTF loader; - std::string err; - std::string warn; - - bool ret = loader.LoadBinaryFromFile(&model, &err, &warn, MODEL_PATH); - - if (!warn.empty()) { - std::cout << "glTF warning: " << warn << std::endl; - } - - if (!err.empty()) { - std::cout << "glTF error: " << err << std::endl; - } - - if (!ret) { - throw std::runtime_error("Failed to load glTF model"); - } - - vertices.clear(); - indices.clear(); - - // Process all meshes in the model - for (const auto& mesh : model.meshes) { - for (const auto& primitive : mesh.primitives) { - // Get indices - const tinygltf::Accessor& indexAccessor = model.accessors[primitive.indices]; - const tinygltf::BufferView& indexBufferView = model.bufferViews[indexAccessor.bufferView]; - const tinygltf::Buffer& indexBuffer = model.buffers[indexBufferView.buffer]; - - // Get vertex positions - const tinygltf::Accessor& posAccessor = model.accessors[primitive.attributes.at("POSITION")]; - const tinygltf::BufferView& posBufferView = model.bufferViews[posAccessor.bufferView]; - const tinygltf::Buffer& posBuffer = model.buffers[posBufferView.buffer]; - - // Get texture coordinates if available - bool hasTexCoords = primitive.attributes.find("TEXCOORD_0") != primitive.attributes.end(); - const tinygltf::Accessor* texCoordAccessor = nullptr; - const tinygltf::BufferView* texCoordBufferView = nullptr; - const tinygltf::Buffer* texCoordBuffer = nullptr; - - if (hasTexCoords) { - texCoordAccessor = &model.accessors[primitive.attributes.at("TEXCOORD_0")]; - texCoordBufferView = &model.bufferViews[texCoordAccessor->bufferView]; - texCoordBuffer = &model.buffers[texCoordBufferView->buffer]; - } - - uint32_t baseVertex = static_cast(vertices.size()); - - for (size_t i = 0; i < posAccessor.count; i++) { - Vertex vertex{}; - - const float* pos = reinterpret_cast(&posBuffer.data[posBufferView.byteOffset + posAccessor.byteOffset + i * 12]); - // glTF uses a right-handed coordinate system with Y-up - // Vulkan uses a right-handed coordinate system with Y-down - // We need to flip the Y coordinate - vertex.pos = {pos[0], -pos[1], pos[2]}; - - if (hasTexCoords) { - const float* texCoord = reinterpret_cast(&texCoordBuffer->data[texCoordBufferView->byteOffset + texCoordAccessor->byteOffset + i * 8]); - vertex.texCoord = {texCoord[0], texCoord[1]}; - } else { - vertex.texCoord = {0.0f, 0.0f}; - } - - vertex.color = {1.0f, 1.0f, 1.0f}; - - vertices.push_back(vertex); - } - - const unsigned char* indexData = &indexBuffer.data[indexBufferView.byteOffset + indexAccessor.byteOffset]; - size_t indexCount = indexAccessor.count; - size_t indexStride = 0; - - // Determine index stride based on component type - if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) { - indexStride = sizeof(uint16_t); - } else if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT) { - indexStride = sizeof(uint32_t); - } else if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) { - indexStride = sizeof(uint8_t); - } else { - throw std::runtime_error("Unsupported index component type"); - } - - indices.reserve(indices.size() + indexCount); - - for (size_t i = 0; i < indexCount; i++) { - uint32_t index = 0; - - if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) { - index = *reinterpret_cast(indexData + i * indexStride); - } else if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT) { - index = *reinterpret_cast(indexData + i * indexStride); - } else if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) { - index = *reinterpret_cast(indexData + i * indexStride); - } - - indices.push_back(baseVertex + index); - } - } - } - } - - void createVertexBuffer() { - vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(dataStaging, vertices.data(), bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); - - copyBuffer(stagingBuffer, vertexBuffer, bufferSize); - } - - void createIndexBuffer() { - vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); - - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* data = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(data, indices.data(), bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); - - copyBuffer(stagingBuffer, indexBuffer, bufferSize); - } - - // Initialize the game objects with different positions, rotations, and scales - void setupGameObjects() { - // Object 1 - Center - gameObjects[0].position = {0.0f, 0.0f, 0.0f}; - gameObjects[0].rotation = {0.0f, 0.0f, 0.0f}; - gameObjects[0].scale = {1.0f, 1.0f, 1.0f}; - - // Object 2 - Left - gameObjects[1].position = {-2.0f, 0.0f, -1.0f}; - gameObjects[1].rotation = {0.0f, glm::radians(45.0f), 0.0f}; - gameObjects[1].scale = {0.75f, 0.75f, 0.75f}; - - // Object 3 - Right - gameObjects[2].position = {2.0f, 0.0f, -1.0f}; - gameObjects[2].rotation = {0.0f, glm::radians(-45.0f), 0.0f}; - gameObjects[2].scale = {0.75f, 0.75f, 0.75f}; - } - - // Create uniform buffers for each object - void createUniformBuffers() { - // For each game object - for (auto& gameObject : gameObjects) { - gameObject.uniformBuffers.clear(); - gameObject.uniformBuffersMemory.clear(); - gameObject.uniformBuffersMapped.clear(); - - // Create uniform buffers for each frame in flight - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DeviceSize bufferSize = sizeof(UniformBufferObject); - vk::raii::Buffer buffer({}); - vk::raii::DeviceMemory bufferMem({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); - gameObject.uniformBuffers.emplace_back(std::move(buffer)); - gameObject.uniformBuffersMemory.emplace_back(std::move(bufferMem)); - gameObject.uniformBuffersMapped.emplace_back(gameObject.uniformBuffersMemory[i].mapMemory(0, bufferSize)); - } - } - } - - void createDescriptorPool() { - // We need MAX_OBJECTS * MAX_FRAMES_IN_FLIGHT descriptor sets - std::array poolSize { - vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, MAX_OBJECTS * MAX_FRAMES_IN_FLIGHT), - vk::DescriptorPoolSize(vk::DescriptorType::eCombinedImageSampler, MAX_OBJECTS * MAX_FRAMES_IN_FLIGHT) - }; - vk::DescriptorPoolCreateInfo poolInfo{ - .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, - .maxSets = MAX_OBJECTS * MAX_FRAMES_IN_FLIGHT, - .poolSizeCount = static_cast(poolSize.size()), - .pPoolSizes = poolSize.data() - }; - descriptorPool = vk::raii::DescriptorPool(device, poolInfo); - } - - void createDescriptorSets() { - // For each game object - for (auto& gameObject : gameObjects) { - // Create descriptor sets for each frame in flight - std::vector layouts(MAX_FRAMES_IN_FLIGHT, *descriptorSetLayout); - vk::DescriptorSetAllocateInfo allocInfo{ - .descriptorPool = *descriptorPool, - .descriptorSetCount = static_cast(layouts.size()), - .pSetLayouts = layouts.data() - }; - - gameObject.descriptorSets.clear(); - gameObject.descriptorSets = device.allocateDescriptorSets(allocInfo); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DescriptorBufferInfo bufferInfo{ - .buffer = *gameObject.uniformBuffers[i], - .offset = 0, - .range = sizeof(UniformBufferObject) - }; - vk::DescriptorImageInfo imageInfo{ - .sampler = *textureSampler, - .imageView = *textureImageView, - .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal - }; - std::array descriptorWrites{ - vk::WriteDescriptorSet{ - .dstSet = *gameObject.descriptorSets[i], - .dstBinding = 0, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eUniformBuffer, - .pBufferInfo = &bufferInfo - }, - vk::WriteDescriptorSet{ - .dstSet = *gameObject.descriptorSets[i], - .dstBinding = 1, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eCombinedImageSampler, - .pImageInfo = &imageInfo - } - }; - device.updateDescriptorSets(descriptorWrites, {}); - } - } - } - - void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer& buffer, vk::raii::DeviceMemory& bufferMemory) { - vk::BufferCreateInfo bufferInfo{ - .size = size, - .usage = usage, - .sharingMode = vk::SharingMode::eExclusive - }; - buffer = vk::raii::Buffer(device, bufferInfo); - vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ - .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) - }; - bufferMemory = vk::raii::DeviceMemory(device, allocInfo); - buffer.bindMemory(*bufferMemory, 0); - } - - std::unique_ptr beginSingleTimeCommands() { - vk::CommandBufferAllocateInfo allocInfo{ - .commandPool = *commandPool, - .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = 1 - }; - std::unique_ptr commandBuffer = std::make_unique(std::move(vk::raii::CommandBuffers(device, allocInfo).front())); - - vk::CommandBufferBeginInfo beginInfo{ - .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit - }; - commandBuffer->begin(beginInfo); - - return commandBuffer; - } - - void endSingleTimeCommands(const vk::raii::CommandBuffer& commandBuffer) const { - commandBuffer.end(); - - vk::SubmitInfo submitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandBuffer }; - queue.submit(submitInfo, nullptr); - queue.waitIdle(); - } - - void copyBuffer(vk::raii::Buffer & srcBuffer, vk::raii::Buffer & dstBuffer, vk::DeviceSize size) { - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = *commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1 }; - vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); - commandCopyBuffer.begin(vk::CommandBufferBeginInfo{ .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit }); - commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy{ .size = size }); - commandCopyBuffer.end(); - queue.submit(vk::SubmitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer }, nullptr); - queue.waitIdle(); - } - - uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) { - vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); - - for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { - if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { - return i; - } - } - - throw std::runtime_error("failed to find suitable memory type!"); - } - - void createCommandBuffers() { - commandBuffers.clear(); - vk::CommandBufferAllocateInfo allocInfo{ .commandPool = *commandPool, .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = MAX_FRAMES_IN_FLIGHT }; - commandBuffers = vk::raii::CommandBuffers(device, allocInfo); - } - - void recordCommandBuffer(uint32_t imageIndex) { - commandBuffers[currentFrame].begin({}); - transition_image_layout( - imageIndex, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, - vk::AccessFlagBits2::eColorAttachmentWrite, - vk::PipelineStageFlagBits2::eTopOfPipe, - vk::PipelineStageFlagBits2::eColorAttachmentOutput - ); - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::RenderingAttachmentInfo attachmentInfo = { - .imageView = *swapChainImageViews[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor - }; - vk::RenderingInfo renderingInfo = { - .renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &attachmentInfo - }; - commandBuffers[currentFrame].beginRendering(renderingInfo); - commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); - - // Bind vertex and index buffers (shared by all objects) - commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint32); - - // Draw each object with its own descriptor set - for (const auto& gameObject : gameObjects) { - // Bind the descriptor set for this object - commandBuffers[currentFrame].bindDescriptorSets( - vk::PipelineBindPoint::eGraphics, - *pipelineLayout, - 0, - *gameObject.descriptorSets[currentFrame], - nullptr - ); - - // Draw the object - commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); - } - - commandBuffers[currentFrame].endRendering(); - transition_image_layout( - imageIndex, - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, - {}, - vk::PipelineStageFlagBits2::eColorAttachmentOutput, - vk::PipelineStageFlagBits2::eBottomOfPipe - ); - commandBuffers[currentFrame].end(); - } - - void transition_image_layout( - uint32_t imageIndex, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::AccessFlags2 src_access_mask, - vk::AccessFlags2 dst_access_mask, - vk::PipelineStageFlags2 src_stage_mask, - vk::PipelineStageFlags2 dst_stage_mask - ) { - vk::ImageMemoryBarrier2 barrier = { - .srcStageMask = src_stage_mask, - .srcAccessMask = src_access_mask, - .dstStageMask = dst_stage_mask, - .dstAccessMask = dst_access_mask, - .oldLayout = old_layout, - .newLayout = new_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); - } - - void createSyncObjects() { - presentCompleteSemaphore.clear(); - renderFinishedSemaphore.clear(); - inFlightFences.clear(); - - for (size_t i = 0; i < swapChainImages.size(); i++) { - presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); - } - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - inFlightFences.emplace_back(device, vk::FenceCreateInfo{ .flags = vk::FenceCreateFlagBits::eSignaled }); - } - } - - void updateUniformBuffers() { - static auto startTime = std::chrono::high_resolution_clock::now(); - static auto lastFrameTime = startTime; - auto currentTime = std::chrono::high_resolution_clock::now(); - float time = std::chrono::duration(currentTime - startTime).count(); - float deltaTime = std::chrono::duration(currentTime - lastFrameTime).count(); - lastFrameTime = currentTime; - - // Camera and projection matrices (shared by all objects) - glm::mat4 view = glm::lookAt(glm::vec3(2.0f, 2.0f, 6.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f)); - glm::mat4 proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 20.0f); - // Update uniform buffers for each object - for (auto& gameObject : gameObjects) { - // Apply continuous rotation to the object based on frame time - const float rotationSpeed = 0.5f; // Rotation speed in radians per second - gameObject.rotation.y += rotationSpeed * deltaTime; // Slow rotation around Y axis scaled by frame time - - // Get the model matrix for this object - glm::mat4 model = gameObject.getModelMatrix(); - - // Create and update the UBO - UniformBufferObject ubo{ - .model = model, - .view = view, - .proj = proj - }; - - // Copy the UBO data to the mapped memory - memcpy(gameObject.uniformBuffersMapped[currentFrame], &ubo, sizeof(ubo)); - } - } - - void drawFrame() { - while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)); - auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[currentFrame], nullptr); - - if (result == vk::Result::eErrorOutOfDateKHR) { - recreateSwapChain(); - return; - } - if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) { - throw std::runtime_error("failed to acquire swap chain image!"); - } - - // Update uniform buffers for all objects - updateUniformBuffers(); - - device.resetFences(*inFlightFences[currentFrame]); - commandBuffers[currentFrame].reset(); - recordCommandBuffer(imageIndex); - - vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); - const vk::SubmitInfo submitInfo{ - .waitSemaphoreCount = 1, - .pWaitSemaphores = &*presentCompleteSemaphore[currentFrame], - .pWaitDstStageMask = &waitDestinationStageMask, - .commandBufferCount = 1, - .pCommandBuffers = &*commandBuffers[currentFrame], - .signalSemaphoreCount = 1, - .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex] - }; - queue.submit(submitInfo, *inFlightFences[currentFrame]); - - const vk::PresentInfoKHR presentInfoKHR{ - .waitSemaphoreCount = 1, - .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], - .swapchainCount = 1, - .pSwapchains = &*swapChain, - .pImageIndices = &imageIndex - }; - result = queue.presentKHR(presentInfoKHR); - if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { - framebufferResized = false; - recreateSwapChain(); - } else if (result != vk::Result::eSuccess) { - throw std::runtime_error("failed to present swap chain image!"); - } - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; - } - - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size(), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - - static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const & surfaceCapabilities) { - auto minImageCount = std::max( 3u, surfaceCapabilities.minImageCount ); - if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) { - minImageCount = surfaceCapabilities.maxImageCount; - } - return minImageCount; - } - - static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { - assert(!availableFormats.empty()); - const auto formatIt = std::ranges::find_if( - availableFormats, - []( const auto & format ) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; } ); - return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - assert(std::ranges::any_of(availablePresentModes, [](auto presentMode){ return presentMode == vk::PresentModeKHR::eFifo; })); - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eFifo; - } - - vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) { - if (capabilities.currentExtent.width != 0xFFFFFFFF) { - return capabilities.currentExtent; - } + if (result && supported == VK_TRUE) + { + appInfo.profileSupported = true; + appInfo.profile = profileProperties; + LOGI("Device supports Vulkan profile: %s", name); + } + else + { + LOGI("Device does not support Vulkan profile: %s", name); + } + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + // query for Vulkan 1.3 features + auto features = physicalDevice.getFeatures2(); + vk::PhysicalDeviceVulkan13Features vulkan13Features; + vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT extendedDynamicStateFeatures; + vulkan13Features.dynamicRendering = vk::True; + vulkan13Features.synchronization2 = vk::True; + extendedDynamicStateFeatures.extendedDynamicState = vk::True; + vulkan13Features.pNext = &extendedDynamicStateFeatures; + features.pNext = &vulkan13Features; + // create a Device + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{ + .pNext = &features, + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + // Create the device with the appropriate features + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(*surface); + swapChainExtent = chooseSwapExtent(surfaceCapabilities); + swapChainSurfaceFormat = chooseSwapSurfaceFormat(physicalDevice.getSurfaceFormatsKHR(*surface)); + vk::SwapchainCreateInfoKHR swapChainCreateInfo{.surface = *surface, + .minImageCount = chooseSwapMinImageCount(surfaceCapabilities), + .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}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + void createImageViews() + { + vk::ImageViewCreateInfo imageViewCreateInfo{ + .viewType = vk::ImageViewType::e2D, + .format = swapChainSurfaceFormat.format, + .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + for (auto image : swapChainImages) + { + imageViewCreateInfo.image = image; + swapChainImageViews.emplace_back(device, imageViewCreateInfo); + } + } + + void createDescriptorSetLayout() + { + std::array bindings = { + vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr), + vk::DescriptorSetLayoutBinding(1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment, nullptr)}; + + vk::DescriptorSetLayoutCreateInfo layoutInfo{.bindingCount = static_cast(bindings.size()), .pBindings = bindings.data()}; + descriptorSetLayout = vk::raii::DescriptorSetLayout(device, layoutInfo); + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(this->readFile("shaders/slang.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}; + + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{ + .vertexBindingDescriptionCount = 1, + .pVertexBindingDescriptions = &bindingDescription, + .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), + .pVertexAttributeDescriptions = attributeDescriptions.data()}; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ + .topology = vk::PrimitiveTopology::eTriangleList, + .primitiveRestartEnable = vk::False}; + vk::PipelineViewportStateCreateInfo viewportState{ + .viewportCount = 1, + .scissorCount = 1}; + vk::PipelineRasterizationStateCreateInfo rasterizer{ + .depthClampEnable = vk::False, + .rasterizerDiscardEnable = vk::False, + .polygonMode = vk::PolygonMode::eFill, + .cullMode = vk::CullModeFlagBits::eBack, // Re-enabled culling for better performance + .frontFace = vk::FrontFace::eClockwise, // Keeping Clockwise for glTF + .depthBiasEnable = vk::False}; + rasterizer.lineWidth = 1.0f; + vk::PipelineMultisampleStateCreateInfo multisampling{ + .rasterizationSamples = vk::SampleCountFlagBits::e1, + .sampleShadingEnable = vk::False}; + vk::PipelineDepthStencilStateCreateInfo depthStencil{ + .depthTestEnable = vk::True, + .depthWriteEnable = vk::True, + .depthCompareOp = vk::CompareOp::eLess, + .depthBoundsTestEnable = vk::False, + .stencilTestEnable = vk::False}; + vk::PipelineColorBlendAttachmentState colorBlendAttachment; + colorBlendAttachment.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA; + colorBlendAttachment.blendEnable = vk::False; + + vk::PipelineColorBlendStateCreateInfo colorBlending{ + .logicOpEnable = vk::False, + .logicOp = vk::LogicOp::eCopy, + .attachmentCount = 1, + .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0}; + + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format}; + vk::GraphicsPipelineCreateInfo pipelineInfo{.pNext = &pipelineRenderingCreateInfo, + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pDepthStencilState = &depthStencil, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = *pipelineLayout, + .renderPass = nullptr}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{ + .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex}; + commandPool = vk::raii::CommandPool(device, poolInfo); + } + + void createDepthResources() + { + vk::Format depthFormat = findDepthFormat(); + + createImage(swapChainExtent.width, swapChainExtent.height, depthFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eDepthStencilAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, depthImage, depthImageMemory); + depthImageView = createImageView(depthImage, depthFormat, vk::ImageAspectFlagBits::eDepth); + } + + vk::Format findSupportedFormat(const std::vector &candidates, vk::ImageTiling tiling, vk::FormatFeatureFlags features) const + { + for (const auto format : candidates) + { + vk::FormatProperties props = physicalDevice.getFormatProperties(format); + + if (tiling == vk::ImageTiling::eLinear && (props.linearTilingFeatures & features) == features) + { + return format; + } + if (tiling == vk::ImageTiling::eOptimal && (props.optimalTilingFeatures & features) == features) + { + return format; + } + } + + throw std::runtime_error("failed to find supported format!"); + } + + [[nodiscard]] vk::Format findDepthFormat() const + { + return findSupportedFormat( + {vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, vk::Format::eD24UnormS8Uint}, + vk::ImageTiling::eOptimal, + vk::FormatFeatureFlagBits::eDepthStencilAttachment); + } + + static bool hasStencilComponent(vk::Format format) + { + return format == vk::Format::eD32SfloatS8Uint || format == vk::Format::eD24UnormS8Uint; + } + + void createTextureImage() + { + // Load KTX2 texture instead of using stb_image + ktxTexture *kTexture; + KTX_error_code result = ktxTexture_CreateFromNamedFile( + TEXTURE_PATH.c_str(), + KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, + &kTexture); + + if (result != KTX_SUCCESS) + { + throw std::runtime_error("failed to load ktx texture image!"); + } + + // Get texture dimensions and data + uint32_t texWidth = kTexture->baseWidth; + uint32_t texHeight = kTexture->baseHeight; + ktx_size_t imageSize = ktxTexture_GetImageSize(kTexture, 0); + ktx_uint8_t *ktxTextureData = ktxTexture_GetData(kTexture); + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, imageSize); + memcpy(data, ktxTextureData, imageSize); + stagingBufferMemory.unmapMemory(); + + // Determine the Vulkan format from KTX format + vk::Format textureFormat; + + // Check if the KTX texture has a format + if (kTexture->classId == ktxTexture2_c) + { + // For KTX2 files, we can get the format directly + auto *ktx2 = reinterpret_cast(kTexture); + textureFormat = static_cast(ktx2->vkFormat); + if (textureFormat == vk::Format::eUndefined) + { + // If the format is undefined, fall back to a reasonable default + textureFormat = vk::Format::eR8G8B8A8Unorm; + } + } + else + { + // For KTX1 files or if we can't determine the format, use a reasonable default + textureFormat = vk::Format::eR8G8B8A8Unorm; + } + + textureImageFormat = textureFormat; + + createImage(texWidth, texHeight, textureFormat, vk::ImageTiling::eOptimal, + vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, + vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory); + + transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal); + copyBufferToImage(stagingBuffer, textureImage, texWidth, texHeight); + transitionImageLayout(textureImage, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal); + + ktxTexture_Destroy(kTexture); + } + + void createTextureImageView() + { + textureImageView = createImageView(textureImage, textureImageFormat, vk::ImageAspectFlagBits::eColor); + } + + void createTextureSampler() + { + vk::PhysicalDeviceProperties properties = physicalDevice.getProperties(); + vk::SamplerCreateInfo samplerInfo{ + .magFilter = vk::Filter::eLinear, + .minFilter = vk::Filter::eLinear, + .mipmapMode = vk::SamplerMipmapMode::eLinear, + .addressModeU = vk::SamplerAddressMode::eRepeat, + .addressModeV = vk::SamplerAddressMode::eRepeat, + .addressModeW = vk::SamplerAddressMode::eRepeat, + .mipLodBias = 0.0f, + .anisotropyEnable = vk::True, + .maxAnisotropy = properties.limits.maxSamplerAnisotropy, + .compareEnable = vk::False, + .compareOp = vk::CompareOp::eAlways}; + textureSampler = vk::raii::Sampler(device, samplerInfo); + } + + vk::raii::ImageView createImageView(vk::raii::Image &image, vk::Format format, vk::ImageAspectFlags aspectFlags) + { + vk::ImageViewCreateInfo viewInfo{ + .image = *image, + .viewType = vk::ImageViewType::e2D, + .format = format, + .subresourceRange = {aspectFlags, 0, 1, 0, 1}}; + return vk::raii::ImageView(device, viewInfo); + } + + void createImage(uint32_t width, uint32_t height, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image &image, vk::raii::DeviceMemory &imageMemory) + { + vk::ImageCreateInfo imageInfo{ + .imageType = vk::ImageType::e2D, + .format = format, + .extent = {width, height, 1}, + .mipLevels = 1, + .arrayLayers = 1, + .samples = vk::SampleCountFlagBits::e1, + .tiling = tiling, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive, + .initialLayout = vk::ImageLayout::eUndefined}; + image = vk::raii::Image(device, imageInfo); + + vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{ + .allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + imageMemory = vk::raii::DeviceMemory(device, allocInfo); + image.bindMemory(*imageMemory, 0); + } + + void transitionImageLayout(const vk::raii::Image &image, vk::ImageLayout oldLayout, vk::ImageLayout newLayout) + { + auto commandBuffer = beginSingleTimeCommands(); + + vk::ImageMemoryBarrier barrier{ + .oldLayout = oldLayout, + .newLayout = newLayout, + .image = *image, + .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + + vk::PipelineStageFlags sourceStage; + vk::PipelineStageFlags destinationStage; + + if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) + { + barrier.srcAccessMask = {}; + barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; + + sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; + destinationStage = vk::PipelineStageFlagBits::eTransfer; + } + else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) + { + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + + sourceStage = vk::PipelineStageFlagBits::eTransfer; + destinationStage = vk::PipelineStageFlagBits::eFragmentShader; + } + else + { + throw std::invalid_argument("unsupported layout transition!"); + } + commandBuffer->pipelineBarrier(sourceStage, destinationStage, {}, {}, nullptr, barrier); + endSingleTimeCommands(*commandBuffer); + } + + void copyBufferToImage(const vk::raii::Buffer &buffer, vk::raii::Image &image, uint32_t width, uint32_t height) + { + std::unique_ptr commandBuffer = beginSingleTimeCommands(); + vk::BufferImageCopy region{ + .bufferOffset = 0, + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource = {vk::ImageAspectFlagBits::eColor, 0, 0, 1}, + .imageOffset = {0, 0, 0}, + .imageExtent = {width, height, 1}}; + commandBuffer->copyBufferToImage(*buffer, *image, vk::ImageLayout::eTransferDstOptimal, {region}); + endSingleTimeCommands(*commandBuffer); + } + + void loadModel() + { + // Use tinygltf to load the model instead of tinyobjloader + tinygltf::Model model; + tinygltf::TinyGLTF loader; + std::string err; + std::string warn; + + bool ret = loader.LoadBinaryFromFile(&model, &err, &warn, MODEL_PATH); + + if (!warn.empty()) + { + std::cout << "glTF warning: " << warn << std::endl; + } + + if (!err.empty()) + { + std::cout << "glTF error: " << err << std::endl; + } + + if (!ret) + { + throw std::runtime_error("Failed to load glTF model"); + } + + vertices.clear(); + indices.clear(); + + // Process all meshes in the model + for (const auto &mesh : model.meshes) + { + for (const auto &primitive : mesh.primitives) + { + // Get indices + const tinygltf::Accessor &indexAccessor = model.accessors[primitive.indices]; + const tinygltf::BufferView &indexBufferView = model.bufferViews[indexAccessor.bufferView]; + const tinygltf::Buffer &indexBuffer = model.buffers[indexBufferView.buffer]; + + // Get vertex positions + const tinygltf::Accessor &posAccessor = model.accessors[primitive.attributes.at("POSITION")]; + const tinygltf::BufferView &posBufferView = model.bufferViews[posAccessor.bufferView]; + const tinygltf::Buffer &posBuffer = model.buffers[posBufferView.buffer]; + + // Get texture coordinates if available + bool hasTexCoords = primitive.attributes.find("TEXCOORD_0") != primitive.attributes.end(); + const tinygltf::Accessor *texCoordAccessor = nullptr; + const tinygltf::BufferView *texCoordBufferView = nullptr; + const tinygltf::Buffer *texCoordBuffer = nullptr; + + if (hasTexCoords) + { + texCoordAccessor = &model.accessors[primitive.attributes.at("TEXCOORD_0")]; + texCoordBufferView = &model.bufferViews[texCoordAccessor->bufferView]; + texCoordBuffer = &model.buffers[texCoordBufferView->buffer]; + } + + uint32_t baseVertex = static_cast(vertices.size()); + + for (size_t i = 0; i < posAccessor.count; i++) + { + Vertex vertex{}; + + const float *pos = reinterpret_cast(&posBuffer.data[posBufferView.byteOffset + posAccessor.byteOffset + i * 12]); + // glTF uses a right-handed coordinate system with Y-up + // Vulkan uses a right-handed coordinate system with Y-down + // We need to flip the Y coordinate + vertex.pos = {pos[0], -pos[1], pos[2]}; + + if (hasTexCoords) + { + const float *texCoord = reinterpret_cast(&texCoordBuffer->data[texCoordBufferView->byteOffset + texCoordAccessor->byteOffset + i * 8]); + vertex.texCoord = {texCoord[0], texCoord[1]}; + } + else + { + vertex.texCoord = {0.0f, 0.0f}; + } + + vertex.color = {1.0f, 1.0f, 1.0f}; + + vertices.push_back(vertex); + } + + const unsigned char *indexData = &indexBuffer.data[indexBufferView.byteOffset + indexAccessor.byteOffset]; + size_t indexCount = indexAccessor.count; + size_t indexStride = 0; + + // Determine index stride based on component type + if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) + { + indexStride = sizeof(uint16_t); + } + else if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT) + { + indexStride = sizeof(uint32_t); + } + else if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) + { + indexStride = sizeof(uint8_t); + } + else + { + throw std::runtime_error("Unsupported index component type"); + } + + indices.reserve(indices.size() + indexCount); + + for (size_t i = 0; i < indexCount; i++) + { + uint32_t index = 0; + + if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) + { + index = *reinterpret_cast(indexData + i * indexStride); + } + else if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT) + { + index = *reinterpret_cast(indexData + i * indexStride); + } + else if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) + { + index = *reinterpret_cast(indexData + i * indexStride); + } + + indices.push_back(baseVertex + index); + } + } + } + } + + void createVertexBuffer() + { + vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(dataStaging, vertices.data(), bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + } + + void createIndexBuffer() + { + vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *data = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(data, indices.data(), bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); + + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + } + + // Initialize the game objects with different positions, rotations, and scales + void setupGameObjects() + { + // Object 1 - Center + gameObjects[0].position = {0.0f, 0.0f, 0.0f}; + gameObjects[0].rotation = {0.0f, 0.0f, 0.0f}; + gameObjects[0].scale = {1.0f, 1.0f, 1.0f}; + + // Object 2 - Left + gameObjects[1].position = {-2.0f, 0.0f, -1.0f}; + gameObjects[1].rotation = {0.0f, glm::radians(45.0f), 0.0f}; + gameObjects[1].scale = {0.75f, 0.75f, 0.75f}; + + // Object 3 - Right + gameObjects[2].position = {2.0f, 0.0f, -1.0f}; + gameObjects[2].rotation = {0.0f, glm::radians(-45.0f), 0.0f}; + gameObjects[2].scale = {0.75f, 0.75f, 0.75f}; + } + + // Create uniform buffers for each object + void createUniformBuffers() + { + // For each game object + for (auto &gameObject : gameObjects) + { + gameObject.uniformBuffers.clear(); + gameObject.uniformBuffersMemory.clear(); + gameObject.uniformBuffersMapped.clear(); + + // Create uniform buffers for each frame in flight + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DeviceSize bufferSize = sizeof(UniformBufferObject); + vk::raii::Buffer buffer({}); + vk::raii::DeviceMemory bufferMem({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); + gameObject.uniformBuffers.emplace_back(std::move(buffer)); + gameObject.uniformBuffersMemory.emplace_back(std::move(bufferMem)); + gameObject.uniformBuffersMapped.emplace_back(gameObject.uniformBuffersMemory[i].mapMemory(0, bufferSize)); + } + } + } + + void createDescriptorPool() + { + // We need MAX_OBJECTS * MAX_FRAMES_IN_FLIGHT descriptor sets + std::array poolSize{ + vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, MAX_OBJECTS * MAX_FRAMES_IN_FLIGHT), + vk::DescriptorPoolSize(vk::DescriptorType::eCombinedImageSampler, MAX_OBJECTS * MAX_FRAMES_IN_FLIGHT)}; + vk::DescriptorPoolCreateInfo poolInfo{ + .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, + .maxSets = MAX_OBJECTS * MAX_FRAMES_IN_FLIGHT, + .poolSizeCount = static_cast(poolSize.size()), + .pPoolSizes = poolSize.data()}; + descriptorPool = vk::raii::DescriptorPool(device, poolInfo); + } + + void createDescriptorSets() + { + // For each game object + for (auto &gameObject : gameObjects) + { + // Create descriptor sets for each frame in flight + std::vector layouts(MAX_FRAMES_IN_FLIGHT, *descriptorSetLayout); + vk::DescriptorSetAllocateInfo allocInfo{ + .descriptorPool = *descriptorPool, + .descriptorSetCount = static_cast(layouts.size()), + .pSetLayouts = layouts.data()}; + + gameObject.descriptorSets.clear(); + gameObject.descriptorSets = device.allocateDescriptorSets(allocInfo); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DescriptorBufferInfo bufferInfo{ + .buffer = *gameObject.uniformBuffers[i], + .offset = 0, + .range = sizeof(UniformBufferObject)}; + vk::DescriptorImageInfo imageInfo{ + .sampler = *textureSampler, + .imageView = *textureImageView, + .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal}; + std::array descriptorWrites{ + vk::WriteDescriptorSet{ + .dstSet = *gameObject.descriptorSets[i], + .dstBinding = 0, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eUniformBuffer, + .pBufferInfo = &bufferInfo}, + vk::WriteDescriptorSet{ + .dstSet = *gameObject.descriptorSets[i], + .dstBinding = 1, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .pImageInfo = &imageInfo}}; + device.updateDescriptorSets(descriptorWrites, {}); + } + } + } + + void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer &buffer, vk::raii::DeviceMemory &bufferMemory) + { + vk::BufferCreateInfo bufferInfo{ + .size = size, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive}; + buffer = vk::raii::Buffer(device, bufferInfo); + vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{ + .allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + bufferMemory = vk::raii::DeviceMemory(device, allocInfo); + buffer.bindMemory(*bufferMemory, 0); + } + + std::unique_ptr beginSingleTimeCommands() + { + vk::CommandBufferAllocateInfo allocInfo{ + .commandPool = *commandPool, + .level = vk::CommandBufferLevel::ePrimary, + .commandBufferCount = 1}; + std::unique_ptr commandBuffer = std::make_unique(std::move(vk::raii::CommandBuffers(device, allocInfo).front())); + + vk::CommandBufferBeginInfo beginInfo{ + .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; + commandBuffer->begin(beginInfo); + + return commandBuffer; + } + + void endSingleTimeCommands(const vk::raii::CommandBuffer &commandBuffer) const + { + commandBuffer.end(); + + vk::SubmitInfo submitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandBuffer}; + queue.submit(submitInfo, nullptr); + queue.waitIdle(); + } + + void copyBuffer(vk::raii::Buffer &srcBuffer, vk::raii::Buffer &dstBuffer, vk::DeviceSize size) + { + vk::CommandBufferAllocateInfo allocInfo{.commandPool = *commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1}; + vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); + commandCopyBuffer.begin(vk::CommandBufferBeginInfo{.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); + commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy{.size = size}); + commandCopyBuffer.end(); + queue.submit(vk::SubmitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer}, nullptr); + queue.waitIdle(); + } + + uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) + { + vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) + { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) + { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + void createCommandBuffers() + { + commandBuffers.clear(); + vk::CommandBufferAllocateInfo allocInfo{.commandPool = *commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = MAX_FRAMES_IN_FLIGHT}; + commandBuffers = vk::raii::CommandBuffers(device, allocInfo); + } + + void recordCommandBuffer(uint32_t imageIndex) + { + commandBuffers[currentFrame].begin({}); + transition_image_layout( + imageIndex, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, + vk::AccessFlagBits2::eColorAttachmentWrite, + vk::PipelineStageFlagBits2::eTopOfPipe, + vk::PipelineStageFlagBits2::eColorAttachmentOutput); + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::RenderingAttachmentInfo attachmentInfo = { + .imageView = *swapChainImageViews[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor}; + vk::RenderingInfo renderingInfo = { + .renderArea = {.offset = {0, 0}, .extent = swapChainExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &attachmentInfo}; + commandBuffers[currentFrame].beginRendering(renderingInfo); + commandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + commandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + + // Bind vertex and index buffers (shared by all objects) + commandBuffers[currentFrame].bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffers[currentFrame].bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint32); + + // Draw each object with its own descriptor set + for (const auto &gameObject : gameObjects) + { + // Bind the descriptor set for this object + commandBuffers[currentFrame].bindDescriptorSets( + vk::PipelineBindPoint::eGraphics, + *pipelineLayout, + 0, + *gameObject.descriptorSets[currentFrame], + nullptr); + + // Draw the object + commandBuffers[currentFrame].drawIndexed(indices.size(), 1, 0, 0, 0); + } + + commandBuffers[currentFrame].endRendering(); + transition_image_layout( + imageIndex, + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, + {}, + vk::PipelineStageFlagBits2::eColorAttachmentOutput, + vk::PipelineStageFlagBits2::eBottomOfPipe); + commandBuffers[currentFrame].end(); + } + + void transition_image_layout( + uint32_t imageIndex, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout, + vk::AccessFlags2 src_access_mask, + vk::AccessFlags2 dst_access_mask, + vk::PipelineStageFlags2 src_stage_mask, + vk::PipelineStageFlags2 dst_stage_mask) + { + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = src_stage_mask, + .srcAccessMask = src_access_mask, + .dstStageMask = dst_stage_mask, + .dstAccessMask = dst_access_mask, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo dependency_info = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + } + + void createSyncObjects() + { + presentCompleteSemaphore.clear(); + renderFinishedSemaphore.clear(); + inFlightFences.clear(); + + for (size_t i = 0; i < swapChainImages.size(); i++) + { + presentCompleteSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + renderFinishedSemaphore.emplace_back(device, vk::SemaphoreCreateInfo()); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); + } + } + + void updateUniformBuffers() + { + static auto startTime = std::chrono::high_resolution_clock::now(); + static auto lastFrameTime = startTime; + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + float deltaTime = std::chrono::duration(currentTime - lastFrameTime).count(); + lastFrameTime = currentTime; + + // Camera and projection matrices (shared by all objects) + glm::mat4 view = glm::lookAt(glm::vec3(2.0f, 2.0f, 6.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f)); + glm::mat4 proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 20.0f); + // Update uniform buffers for each object + for (auto &gameObject : gameObjects) + { + // Apply continuous rotation to the object based on frame time + const float rotationSpeed = 0.5f; // Rotation speed in radians per second + gameObject.rotation.y += rotationSpeed * deltaTime; // Slow rotation around Y axis scaled by frame time + + // Get the model matrix for this object + glm::mat4 model = gameObject.getModelMatrix(); + + // Create and update the UBO + UniformBufferObject ubo{ + .model = model, + .view = view, + .proj = proj}; + + // Copy the UBO data to the mapped memory + memcpy(gameObject.uniformBuffersMapped[currentFrame], &ubo, sizeof(ubo)); + } + } + + void drawFrame() + { + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + ; + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphore[currentFrame], nullptr); + + if (result == vk::Result::eErrorOutOfDateKHR) + { + recreateSwapChain(); + return; + } + if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) + { + throw std::runtime_error("failed to acquire swap chain image!"); + } + + // Update uniform buffers for all objects + updateUniformBuffers(); + + device.resetFences(*inFlightFences[currentFrame]); + commandBuffers[currentFrame].reset(); + recordCommandBuffer(imageIndex); + + vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); + const vk::SubmitInfo submitInfo{ + .waitSemaphoreCount = 1, + .pWaitSemaphores = &*presentCompleteSemaphore[currentFrame], + .pWaitDstStageMask = &waitDestinationStageMask, + .commandBufferCount = 1, + .pCommandBuffers = &*commandBuffers[currentFrame], + .signalSemaphoreCount = 1, + .pSignalSemaphores = &*renderFinishedSemaphore[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[currentFrame]); + + const vk::PresentInfoKHR presentInfoKHR{ + .waitSemaphoreCount = 1, + .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], + .swapchainCount = 1, + .pSwapchains = &*swapChain, + .pImageIndices = &imageIndex}; + result = queue.presentKHR(presentInfoKHR); + if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) + { + framebufferResized = false; + recreateSwapChain(); + } + else if (result != vk::Result::eSuccess) + { + throw std::runtime_error("failed to present swap chain image!"); + } + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size(), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + + static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const &surfaceCapabilities) + { + auto minImageCount = std::max(3u, surfaceCapabilities.minImageCount); + if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) + { + minImageCount = surfaceCapabilities.maxImageCount; + } + return minImageCount; + } + + static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + assert(!availableFormats.empty()); + const auto formatIt = std::ranges::find_if( + availableFormats, + [](const auto &format) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; }); + return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + assert(std::ranges::any_of(availablePresentModes, [](auto presentMode) { return presentMode == vk::PresentModeKHR::eFifo; })); + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + vk::PresentModeKHR::eFifo; + } + + vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR &capabilities) + { + if (capabilities.currentExtent.width != 0xFFFFFFFF) + { + return capabilities.currentExtent; + } #if PLATFORM_DESKTOP - int width, height; - glfwGetFramebufferSize(window, &width, &height); + int width, height; + glfwGetFramebufferSize(window, &width, &height); #else - ANativeWindow* window = androidAppState.nativeWindow; - int width = ANativeWindow_getWidth(window); - int height = ANativeWindow_getHeight(window); + ANativeWindow *window = androidAppState.nativeWindow; + int width = ANativeWindow_getWidth(window); + int height = ANativeWindow_getHeight(window); #endif - return { - std::clamp(width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width), - std::clamp(height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height) - }; - } + return { + std::clamp(width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width), + std::clamp(height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height)}; + } - [[nodiscard]] std::vector getRequiredExtensions() const { - std::vector extensions; + [[nodiscard]] std::vector getRequiredExtensions() const + { + std::vector extensions; #if PLATFORM_DESKTOP - // Get GLFW extensions - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - extensions.assign(glfwExtensions, glfwExtensions + glfwExtensionCount); + // Get GLFW extensions + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + extensions.assign(glfwExtensions, glfwExtensions + glfwExtensionCount); #else - // Android extensions - extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME); - extensions.push_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME); + // Android extensions + extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME); + extensions.push_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME); #endif - // Add debug extensions if validation layers are enabled - if (enableValidationLayers) { - extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); - } - - return extensions; - } - - [[nodiscard]] bool checkValidationLayerSupport() const { - return (std::ranges::any_of(context.enumerateInstanceLayerProperties(), - []( vk::LayerProperties const & lp ) { return ( strcmp( "VK_LAYER_KHRONOS_validation", lp.layerName ) == 0 ); } ) ); - } - - static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - } - - return vk::False; - } - - std::vector readFile(const std::string& filename) { + // Add debug extensions if validation layers are enabled + if (enableValidationLayers) + { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + return extensions; + } + + [[nodiscard]] bool checkValidationLayerSupport() const + { + return (std::ranges::any_of(context.enumerateInstanceLayerProperties(), + [](vk::LayerProperties const &lp) { return (strcmp("VK_LAYER_KHRONOS_validation", lp.layerName) == 0); })); + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, void *) + { + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) + { + std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + } + + return vk::False; + } + + std::vector readFile(const std::string &filename) + { #if PLATFORM_ANDROID - // Android asset loading - if (androidAppState.app == nullptr) { - LOGE("Android app not initialized"); - throw std::runtime_error("Android app not initialized"); - } - AAsset* asset = AAssetManager_open(androidAppState.app->activity->assetManager, filename.c_str(), AASSET_MODE_BUFFER); - if (!asset) { - throw std::runtime_error("failed to open file: " + filename); - } - - size_t size = AAsset_getLength(asset); - std::vector buffer(size); - AAsset_read(asset, buffer.data(), size); - AAsset_close(asset); + // Android asset loading + if (androidAppState.app == nullptr) + { + LOGE("Android app not initialized"); + throw std::runtime_error("Android app not initialized"); + } + AAsset *asset = AAssetManager_open(androidAppState.app->activity->assetManager, filename.c_str(), AASSET_MODE_BUFFER); + if (!asset) + { + throw std::runtime_error("failed to open file: " + filename); + } + + size_t size = AAsset_getLength(asset); + std::vector buffer(size); + AAsset_read(asset, buffer.data(), size); + AAsset_close(asset); #else - // Desktop file loading - std::ifstream file(filename, std::ios::ate | std::ios::binary); - if (!file.is_open()) { - throw std::runtime_error("failed to open file: " + filename); - } - - size_t fileSize = static_cast(file.tellg()); - std::vector buffer(fileSize); - file.seekg(0); - file.read(buffer.data(), fileSize); - file.close(); + // Desktop file loading + std::ifstream file(filename, std::ios::ate | std::ios::binary); + if (!file.is_open()) + { + throw std::runtime_error("failed to open file: " + filename); + } + + size_t fileSize = static_cast(file.tellg()); + std::vector buffer(fileSize); + file.seekg(0); + file.read(buffer.data(), fileSize); + file.close(); #endif - return buffer; - } + return buffer; + } }; #if PLATFORM_ANDROID -void android_main(android_app* app) { - app_dummy(); +void android_main(android_app *app) +{ + app_dummy(); - VulkanApplication vulkanApp; - vulkanApp.run(app); + VulkanApplication vulkanApp; + vulkanApp.run(app); } #else -int main() { - try { - VulkanApplication app; - app.run(); - } catch (const std::exception& e) { - LOGE("%s", e.what()); - return EXIT_FAILURE; - } - return EXIT_SUCCESS; +int main() +{ + try + { + VulkanApplication app; + app.run(); + } + catch (const std::exception &e) + { + LOGE("%s", e.what()); + return EXIT_FAILURE; + } + return EXIT_SUCCESS; } #endif diff --git a/attachments/37_multithreading.cpp b/attachments/37_multithreading.cpp index b08274c6..6843793d 100644 --- a/attachments/37_multithreading.cpp +++ b/attachments/37_multithreading.cpp @@ -18,1198 +18,1274 @@ #include #ifdef __INTELLISENSE__ -#include +# include #else import vulkan_hpp; #endif #include -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include #define GLM_FORCE_RADIANS #include #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -constexpr uint64_t FenceTimeout = 100000000; +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; +constexpr uint64_t FenceTimeout = 100000000; constexpr uint32_t PARTICLE_COUNT = 8192; constexpr int MAX_FRAMES_IN_FLIGHT = 2; - -struct UniformBufferObject { - float deltaTime = 1.0f; +struct UniformBufferObject +{ + float deltaTime = 1.0f; }; -struct Particle { - glm::vec2 position; - glm::vec2 velocity; - glm::vec4 color; - - static vk::VertexInputBindingDescription getBindingDescription() { - return { 0, sizeof(Particle), vk::VertexInputRate::eVertex }; - } - - static std::array getAttributeDescriptions() { - return { - vk::VertexInputAttributeDescription( 0, 0, vk::Format::eR32G32Sfloat, offsetof(Particle, position) ), - vk::VertexInputAttributeDescription( 1, 0, vk::Format::eR32G32B32A32Sfloat, offsetof(Particle, color) ), - }; - } +struct Particle +{ + glm::vec2 position; + glm::vec2 velocity; + glm::vec4 color; + + static vk::VertexInputBindingDescription getBindingDescription() + { + return {0, sizeof(Particle), vk::VertexInputRate::eVertex}; + } + + static std::array getAttributeDescriptions() + { + return { + vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32Sfloat, offsetof(Particle, position)), + vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32A32Sfloat, offsetof(Particle, color)), + }; + } }; // Simple logging function -template -void log(Args&&... args) { - // Only log in debug builds +template +void log(Args &&...args) +{ + // Only log in debug builds #ifdef _DEBUG - (std::cout << ... << std::forward(args)) << std::endl; + (std::cout << ... << std::forward(args)) << std::endl; #endif } -class ThreadSafeResourceManager { -private: - std::mutex resourceMutex; - std::vector commandPools; - std::vector commandBuffers; - -public: - void createThreadCommandPools(vk::raii::Device& device, uint32_t queueFamilyIndex, uint32_t threadCount) { - std::lock_guard lock(resourceMutex); - - commandBuffers.clear(); - commandPools.clear(); - - for (uint32_t i = 0; i < threadCount; i++) { - vk::CommandPoolCreateInfo poolInfo{ - .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueFamilyIndex - }; - try { - commandPools.emplace_back(device, poolInfo); - } catch (const std::exception&) { - throw; // Re-throw the exception to be caught by the caller - } - } - } - - vk::raii::CommandPool& getCommandPool(uint32_t threadIndex) { - std::lock_guard lock(resourceMutex); - return commandPools[threadIndex]; - } - - void allocateCommandBuffers(vk::raii::Device& device, uint32_t threadCount, uint32_t buffersPerThread) { - std::lock_guard lock(resourceMutex); - - commandBuffers.clear(); - - if (commandPools.size() < threadCount) { - throw std::runtime_error("Not enough command pools for thread count"); - } - - for (uint32_t i = 0; i < threadCount; i++) { - vk::CommandBufferAllocateInfo allocInfo{ - .commandPool = *commandPools[i], - .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = buffersPerThread - }; - try { - auto threadBuffers = device.allocateCommandBuffers(allocInfo); - for (auto& buffer : threadBuffers) { - commandBuffers.emplace_back(std::move(buffer)); - } - } catch (const std::exception&) { - throw; // Re-throw the exception to be caught by the caller - } - } - } - - vk::raii::CommandBuffer& getCommandBuffer(uint32_t index) { - // No need for mutex here as each thread accesses its own command buffer - if (index >= commandBuffers.size()) { - throw std::runtime_error("Command buffer index out of range: " + std::to_string(index) + - " (available: " + std::to_string(commandBuffers.size()) + ")"); - } - return commandBuffers[index]; - } +class ThreadSafeResourceManager +{ + private: + std::mutex resourceMutex; + std::vector commandPools; + std::vector commandBuffers; + + public: + void createThreadCommandPools(vk::raii::Device &device, uint32_t queueFamilyIndex, uint32_t threadCount) + { + std::lock_guard lock(resourceMutex); + + commandBuffers.clear(); + commandPools.clear(); + + for (uint32_t i = 0; i < threadCount; i++) + { + vk::CommandPoolCreateInfo poolInfo{ + .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueFamilyIndex}; + try + { + commandPools.emplace_back(device, poolInfo); + } + catch (const std::exception &) + { + throw; // Re-throw the exception to be caught by the caller + } + } + } + + vk::raii::CommandPool &getCommandPool(uint32_t threadIndex) + { + std::lock_guard lock(resourceMutex); + return commandPools[threadIndex]; + } + + void allocateCommandBuffers(vk::raii::Device &device, uint32_t threadCount, uint32_t buffersPerThread) + { + std::lock_guard lock(resourceMutex); + + commandBuffers.clear(); + + if (commandPools.size() < threadCount) + { + throw std::runtime_error("Not enough command pools for thread count"); + } + + for (uint32_t i = 0; i < threadCount; i++) + { + vk::CommandBufferAllocateInfo allocInfo{ + .commandPool = *commandPools[i], + .level = vk::CommandBufferLevel::ePrimary, + .commandBufferCount = buffersPerThread}; + try + { + auto threadBuffers = device.allocateCommandBuffers(allocInfo); + for (auto &buffer : threadBuffers) + { + commandBuffers.emplace_back(std::move(buffer)); + } + } + catch (const std::exception &) + { + throw; // Re-throw the exception to be caught by the caller + } + } + } + + vk::raii::CommandBuffer &getCommandBuffer(uint32_t index) + { + // No need for mutex here as each thread accesses its own command buffer + if (index >= commandBuffers.size()) + { + throw std::runtime_error("Command buffer index out of range: " + std::to_string(index) + + " (available: " + std::to_string(commandBuffers.size()) + ")"); + } + return commandBuffers[index]; + } }; -class MultithreadedApplication { -public: - void run() { - initWindow(); - initVulkan(); - initThreads(); - mainLoop(); - cleanup(); - } - -private: - GLFWwindow * window = nullptr; - vk::raii::Context context; - vk::raii::Instance instance = nullptr; - vk::raii::SurfaceKHR surface = nullptr; - vk::raii::PhysicalDevice physicalDevice = nullptr; - vk::raii::Device device = nullptr; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::SurfaceFormatKHR swapChainSurfaceFormat; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - - vk::raii::DescriptorSetLayout computeDescriptorSetLayout = nullptr; - vk::raii::PipelineLayout computePipelineLayout = nullptr; - vk::raii::Pipeline computePipeline = nullptr; - - std::vector shaderStorageBuffers; - std::vector shaderStorageBuffersMemory; - - std::vector uniformBuffers; - std::vector uniformBuffersMemory; - std::vector uniformBuffersMapped; - - vk::raii::DescriptorPool descriptorPool = nullptr; - std::vector computeDescriptorSets; - - vk::raii::CommandPool commandPool = nullptr; - std::vector graphicsCommandBuffers; - - vk::raii::Semaphore timelineSemaphore = nullptr; - uint64_t timelineValue = 0; - std::vector imageAvailableSemaphores; - std::vector inFlightFences; - uint32_t currentFrame = 0; - - double lastFrameTime = 0.0; - - // Removed resize-related variables and FSM state management as per simplification request - - double lastTime = 0.0f; - - uint32_t threadCount = 0; - std::vector workerThreads; - std::atomic shouldExit{false}; - std::vector> threadWorkReady; - std::vector> threadWorkDone; - - std::mutex queueSubmitMutex; - std::mutex workCompleteMutex; - std::condition_variable workCompleteCv; - - ThreadSafeResourceManager resourceManager; - struct ParticleGroup { - uint32_t startIndex; - uint32_t count; - }; - std::vector particleGroups; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName, - vk::KHRSpirv14ExtensionName, - vk::KHRSynchronization2ExtensionName, - vk::KHRCreateRenderpass2ExtensionName - }; - - // Helper functions - [[nodiscard]] static std::vector getRequiredExtensions() { - uint32_t glfwExtensionCount = 0; - auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - return extensions; - } - - static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const & surfaceCapabilities) { - auto minImageCount = std::max( 3u, surfaceCapabilities.minImageCount ); - if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) { - minImageCount = surfaceCapabilities.maxImageCount; - } - return minImageCount; - } - - static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { - assert(!availableFormats.empty()); - const auto formatIt = std::ranges::find_if( - availableFormats, - []( const auto & format ) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; } ); - return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - assert(std::ranges::any_of(availablePresentModes, [](auto presentMode){ return presentMode == vk::PresentModeKHR::eFifo; })); - return std::ranges::any_of(availablePresentModes, - [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eFifo; - } - [[nodiscard]] vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) const { - if (capabilities.currentExtent.width != 0xFFFFFFFF) { - 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) - }; - } - [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { - vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size(), .pCode = reinterpret_cast(code.data()) }; - vk::raii::ShaderModule shaderModule{ device, createInfo }; - - return shaderModule; - } - 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; - } - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan Multithreading", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - - lastTime = glfwGetTime(); - } - - void initVulkan() { - createInstance(); - createSurface(); - pickPhysicalDevice(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createComputeDescriptorSetLayout(); - createGraphicsPipeline(); - createComputePipeline(); - createCommandPool(); - createShaderStorageBuffers(); - createUniformBuffers(); - createDescriptorPool(); - createComputeDescriptorSets(); - createGraphicsCommandBuffers(); - createSyncObjects(); - } - - void initThreads() { - // Increase thread count for better parallelism - threadCount = 8u; - log("Initializing ", threadCount, " threads for sequential execution"); - - threadWorkReady = std::vector>(threadCount); - threadWorkDone = std::vector>(threadCount); - - for (uint32_t i = 0; i < threadCount; i++) { - threadWorkReady[i] = false; - threadWorkDone[i] = true; - } - - initThreadResources(); - - const uint32_t particlesPerThread = PARTICLE_COUNT / threadCount; - particleGroups.resize(threadCount); - - for (uint32_t i = 0; i < threadCount; i++) { - particleGroups[i].startIndex = i * particlesPerThread; - particleGroups[i].count = (i == threadCount - 1) ? - (PARTICLE_COUNT - i * particlesPerThread) : particlesPerThread; - log("Thread ", i, " will process particles ", - particleGroups[i].startIndex, " to ", - (particleGroups[i].startIndex + particleGroups[i].count - 1), - " (count: ", particleGroups[i].count, ")"); - } - - for (uint32_t i = 0; i < threadCount; i++) { - workerThreads.emplace_back(&MultithreadedApplication::workerThreadFunc, this, i); - log("Started worker thread ", i); - } - } - - void workerThreadFunc(uint32_t threadIndex) { - while (!shouldExit) { - // Wait for work using condition variable - { - std::unique_lock lock(workCompleteMutex); - workCompleteCv.wait(lock, [this, threadIndex]() { - return shouldExit || threadWorkReady[threadIndex].load(std::memory_order_acquire); - }); - - if (shouldExit) { - break; - } - - if (!threadWorkReady[threadIndex].load(std::memory_order_acquire)) { - continue; - } - } - - const ParticleGroup& group = particleGroups[threadIndex]; - bool workCompleted = false; - - try { - // Get command buffer and record commands - vk::raii::CommandBuffer* cmdBuffer = &resourceManager.getCommandBuffer(threadIndex); - recordComputeCommandBuffer(*cmdBuffer, group.startIndex, group.count); - workCompleted = true; - } catch (const std::exception&) { - workCompleted = false; - } - - // Mark work as done - threadWorkDone[threadIndex].store(true, std::memory_order_release); - threadWorkReady[threadIndex].store(false, std::memory_order_release); - - // If this is not the last thread, signal the next thread to start - if (threadIndex < threadCount - 1) { - threadWorkReady[threadIndex + 1].store(true, std::memory_order_release); - } - - // Notify main thread and other threads - { - std::lock_guard lock(workCompleteMutex); - workCompleteCv.notify_all(); - } - } - } - - void mainLoop() { - const double targetFrameTime = 1.0 / 60.0; - - while (!glfwWindowShouldClose(window)) { - double frameStartTime = glfwGetTime(); - - glfwPollEvents(); - drawFrame(); - - double currentTime = glfwGetTime(); - lastFrameTime = (currentTime - lastTime) * 1000.0; - lastTime = currentTime; - - double frameTime = currentTime - frameStartTime; - - if (frameTime < targetFrameTime) { - double sleepTime = targetFrameTime - frameTime; - std::this_thread::sleep_for(std::chrono::duration(sleepTime)); - } - } - - device.waitIdle(); - } - - void cleanupSwapChain() { - swapChainImageViews.clear(); - graphicsPipeline = nullptr; - pipelineLayout = nullptr; - computePipeline = nullptr; - computePipelineLayout = nullptr; - computeDescriptorSets.clear(); - computeDescriptorSetLayout = nullptr; - descriptorPool = nullptr; - - // Unmap and clean up uniform buffers - for (size_t i = 0; i < uniformBuffersMapped.size(); i++) { - uniformBuffersMemory[i].unmapMemory(); - } - uniformBuffers.clear(); - uniformBuffersMemory.clear(); - uniformBuffersMapped.clear(); - - // Clean up shader storage buffers - shaderStorageBuffers.clear(); - shaderStorageBuffersMemory.clear(); - - swapChain = nullptr; - } - - void stopThreads() { - shouldExit.store(true, std::memory_order_release); - - for (uint32_t i = 0; i < threadCount; i++) { - threadWorkDone[i].store(true, std::memory_order_release); - threadWorkReady[i].store(false, std::memory_order_release); - } - - // Notify all threads in case they're waiting on the condition variable - { - std::lock_guard lock(workCompleteMutex); - workCompleteCv.notify_all(); - } - - for (auto& thread : workerThreads) { - if (thread.joinable()) { - thread.join(); - } - } - - workerThreads.clear(); - } - - void initThreadResources() { - resourceManager.createThreadCommandPools(device, queueIndex, threadCount); - resourceManager.allocateCommandBuffers(device, threadCount, 1); - } - - void cleanup() { - stopThreads(); - - glfwDestroyWindow(window); - glfwTerminate(); - } - - - void createInstance() { - constexpr vk::ApplicationInfo appInfo{ .pApplicationName = "Vulkan Multithreading", - .applicationVersion = VK_MAKE_VERSION( 1, 0, 0 ), - .pEngineName = "No Engine", - .engineVersion = VK_MAKE_VERSION( 1, 0, 0 ), - .apiVersion = vk::ApiVersion14 }; - auto extensions = getRequiredExtensions(); - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = 0, - .ppEnabledLayerNames = nullptr, - .enabledExtensionCount = static_cast(extensions.size()), - .ppEnabledExtensionNames = extensions.data() }; - 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 devices = instance.enumeratePhysicalDevices(); - const auto devIter = std::ranges::find_if( - devices, - [&](auto const & device) - { - bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; - - auto queueFamilies = device.getQueueFamilyProperties(); - bool supportsGraphics = - std::ranges::any_of(queueFamilies, [](auto const & qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); - - auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); - bool supportsAllRequiredExtensions = - std::ranges::all_of(requiredDeviceExtension, - [&availableDeviceExtensions](auto const & requiredDeviceExtension) - { - return std::ranges::any_of(availableDeviceExtensions, - [requiredDeviceExtension](auto const & availableDeviceExtension) - { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); - }); - - auto features = device.template getFeatures2(); - bool supportsRequiredFeatures = features.template get().dynamicRendering && - features.template get().extendedDynamicState; - - return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; - }); - if (devIter != devices.end()) - { - physicalDevice = *devIter; - } - else - { - throw std::runtime_error("failed to find a suitable GPU!"); - } - } - - void createLogicalDevice() { - std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - - // get the first index into queueFamilyProperties which supports both graphics and present - for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) - { - if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && +class MultithreadedApplication +{ + public: + void run() + { + initWindow(); + initVulkan(); + initThreads(); + mainLoop(); + cleanup(); + } + + private: + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + uint32_t queueIndex = ~0; + vk::raii::Queue queue = nullptr; + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::SurfaceFormatKHR swapChainSurfaceFormat; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + + vk::raii::DescriptorSetLayout computeDescriptorSetLayout = nullptr; + vk::raii::PipelineLayout computePipelineLayout = nullptr; + vk::raii::Pipeline computePipeline = nullptr; + + std::vector shaderStorageBuffers; + std::vector shaderStorageBuffersMemory; + + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + std::vector uniformBuffersMapped; + + vk::raii::DescriptorPool descriptorPool = nullptr; + std::vector computeDescriptorSets; + + vk::raii::CommandPool commandPool = nullptr; + std::vector graphicsCommandBuffers; + + vk::raii::Semaphore timelineSemaphore = nullptr; + uint64_t timelineValue = 0; + std::vector imageAvailableSemaphores; + std::vector inFlightFences; + uint32_t currentFrame = 0; + + double lastFrameTime = 0.0; + + // Removed resize-related variables and FSM state management as per simplification request + + double lastTime = 0.0f; + + uint32_t threadCount = 0; + std::vector workerThreads; + std::atomic shouldExit{false}; + std::vector> threadWorkReady; + std::vector> threadWorkDone; + + std::mutex queueSubmitMutex; + std::mutex workCompleteMutex; + std::condition_variable workCompleteCv; + + ThreadSafeResourceManager resourceManager; + struct ParticleGroup + { + uint32_t startIndex; + uint32_t count; + }; + std::vector particleGroups; + + std::vector requiredDeviceExtension = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName}; + + // Helper functions + [[nodiscard]] static std::vector getRequiredExtensions() + { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + return extensions; + } + + static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const &surfaceCapabilities) + { + auto minImageCount = std::max(3u, surfaceCapabilities.minImageCount); + if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) + { + minImageCount = surfaceCapabilities.maxImageCount; + } + return minImageCount; + } + + static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector &availableFormats) + { + assert(!availableFormats.empty()); + const auto formatIt = std::ranges::find_if( + availableFormats, + [](const auto &format) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; }); + return formatIt != availableFormats.end() ? *formatIt : availableFormats[0]; + } + + static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) + { + assert(std::ranges::any_of(availablePresentModes, [](auto presentMode) { return presentMode == vk::PresentModeKHR::eFifo; })); + return std::ranges::any_of(availablePresentModes, + [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; }) ? + vk::PresentModeKHR::eMailbox : + vk::PresentModeKHR::eFifo; + } + [[nodiscard]] vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR &capabilities) const + { + if (capabilities.currentExtent.width != 0xFFFFFFFF) + { + 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)}; + } + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const + { + vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size(), .pCode = reinterpret_cast(code.data())}; + vk::raii::ShaderModule shaderModule{device, createInfo}; + + return shaderModule; + } + 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; + } + + void initWindow() + { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan Multithreading", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + + lastTime = glfwGetTime(); + } + + void initVulkan() + { + createInstance(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createComputeDescriptorSetLayout(); + createGraphicsPipeline(); + createComputePipeline(); + createCommandPool(); + createShaderStorageBuffers(); + createUniformBuffers(); + createDescriptorPool(); + createComputeDescriptorSets(); + createGraphicsCommandBuffers(); + createSyncObjects(); + } + + void initThreads() + { + // Increase thread count for better parallelism + threadCount = 8u; + log("Initializing ", threadCount, " threads for sequential execution"); + + threadWorkReady = std::vector>(threadCount); + threadWorkDone = std::vector>(threadCount); + + for (uint32_t i = 0; i < threadCount; i++) + { + threadWorkReady[i] = false; + threadWorkDone[i] = true; + } + + initThreadResources(); + + const uint32_t particlesPerThread = PARTICLE_COUNT / threadCount; + particleGroups.resize(threadCount); + + for (uint32_t i = 0; i < threadCount; i++) + { + particleGroups[i].startIndex = i * particlesPerThread; + particleGroups[i].count = (i == threadCount - 1) ? + (PARTICLE_COUNT - i * particlesPerThread) : + particlesPerThread; + log("Thread ", i, " will process particles ", + particleGroups[i].startIndex, " to ", + (particleGroups[i].startIndex + particleGroups[i].count - 1), + " (count: ", particleGroups[i].count, ")"); + } + + for (uint32_t i = 0; i < threadCount; i++) + { + workerThreads.emplace_back(&MultithreadedApplication::workerThreadFunc, this, i); + log("Started worker thread ", i); + } + } + + void workerThreadFunc(uint32_t threadIndex) + { + while (!shouldExit) + { + // Wait for work using condition variable + { + std::unique_lock lock(workCompleteMutex); + workCompleteCv.wait(lock, [this, threadIndex]() { + return shouldExit || threadWorkReady[threadIndex].load(std::memory_order_acquire); + }); + + if (shouldExit) + { + break; + } + + if (!threadWorkReady[threadIndex].load(std::memory_order_acquire)) + { + continue; + } + } + + const ParticleGroup &group = particleGroups[threadIndex]; + bool workCompleted = false; + + try + { + // Get command buffer and record commands + vk::raii::CommandBuffer *cmdBuffer = &resourceManager.getCommandBuffer(threadIndex); + recordComputeCommandBuffer(*cmdBuffer, group.startIndex, group.count); + workCompleted = true; + } + catch (const std::exception &) + { + workCompleted = false; + } + + // Mark work as done + threadWorkDone[threadIndex].store(true, std::memory_order_release); + threadWorkReady[threadIndex].store(false, std::memory_order_release); + + // If this is not the last thread, signal the next thread to start + if (threadIndex < threadCount - 1) + { + threadWorkReady[threadIndex + 1].store(true, std::memory_order_release); + } + + // Notify main thread and other threads + { + std::lock_guard lock(workCompleteMutex); + workCompleteCv.notify_all(); + } + } + } + + void mainLoop() + { + const double targetFrameTime = 1.0 / 60.0; + + while (!glfwWindowShouldClose(window)) + { + double frameStartTime = glfwGetTime(); + + glfwPollEvents(); + drawFrame(); + + double currentTime = glfwGetTime(); + lastFrameTime = (currentTime - lastTime) * 1000.0; + lastTime = currentTime; + + double frameTime = currentTime - frameStartTime; + + if (frameTime < targetFrameTime) + { + double sleepTime = targetFrameTime - frameTime; + std::this_thread::sleep_for(std::chrono::duration(sleepTime)); + } + } + + device.waitIdle(); + } + + void cleanupSwapChain() + { + swapChainImageViews.clear(); + graphicsPipeline = nullptr; + pipelineLayout = nullptr; + computePipeline = nullptr; + computePipelineLayout = nullptr; + computeDescriptorSets.clear(); + computeDescriptorSetLayout = nullptr; + descriptorPool = nullptr; + + // Unmap and clean up uniform buffers + for (size_t i = 0; i < uniformBuffersMapped.size(); i++) + { + uniformBuffersMemory[i].unmapMemory(); + } + uniformBuffers.clear(); + uniformBuffersMemory.clear(); + uniformBuffersMapped.clear(); + + // Clean up shader storage buffers + shaderStorageBuffers.clear(); + shaderStorageBuffersMemory.clear(); + + swapChain = nullptr; + } + + void stopThreads() + { + shouldExit.store(true, std::memory_order_release); + + for (uint32_t i = 0; i < threadCount; i++) + { + threadWorkDone[i].store(true, std::memory_order_release); + threadWorkReady[i].store(false, std::memory_order_release); + } + + // Notify all threads in case they're waiting on the condition variable + { + std::lock_guard lock(workCompleteMutex); + workCompleteCv.notify_all(); + } + + for (auto &thread : workerThreads) + { + if (thread.joinable()) + { + thread.join(); + } + } + + workerThreads.clear(); + } + + void initThreadResources() + { + resourceManager.createThreadCommandPools(device, queueIndex, threadCount); + resourceManager.allocateCommandBuffers(device, threadCount, 1); + } + + void cleanup() + { + stopThreads(); + + glfwDestroyWindow(window); + glfwTerminate(); + } + + void createInstance() + { + constexpr vk::ApplicationInfo appInfo{.pApplicationName = "Vulkan Multithreading", + .applicationVersion = VK_MAKE_VERSION(1, 0, 0), + .pEngineName = "No Engine", + .engineVersion = VK_MAKE_VERSION(1, 0, 0), + .apiVersion = vk::ApiVersion14}; + auto extensions = getRequiredExtensions(); + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = 0, + .ppEnabledLayerNames = nullptr, + .enabledExtensionCount = static_cast(extensions.size()), + .ppEnabledExtensionNames = extensions.data()}; + 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 devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = + std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + + auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); + bool supportsAllRequiredExtensions = + std::ranges::all_of(requiredDeviceExtension, + [&availableDeviceExtensions](auto const &requiredDeviceExtension) { + return std::ranges::any_of(availableDeviceExtensions, + [requiredDeviceExtension](auto const &availableDeviceExtension) { return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0; }); + }); + + auto features = device.template getFeatures2(); + bool supportsRequiredFeatures = features.template get().dynamicRendering && + features.template get().extendedDynamicState; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; + }); + if (devIter != devices.end()) + { + physicalDevice = *devIter; + } + else + { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() + { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which supports both graphics and present + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) + { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && (queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eCompute) && - physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) - { - // found a queue family that supports both graphics and present - queueIndex = qfpIndex; - break; - } - } - if (queueIndex == ~0) - { - throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); - } - - auto features = physicalDevice.getFeatures2(); - features.features.samplerAnisotropy = vk::True; - vk::PhysicalDeviceVulkan13Features vulkan13Features; - vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT extendedDynamicStateFeatures; - vk::PhysicalDeviceTimelineSemaphoreFeaturesKHR timelineSemaphoreFeatures; - timelineSemaphoreFeatures.timelineSemaphore = vk::True; - vulkan13Features.dynamicRendering = vk::True; - vulkan13Features.synchronization2 = vk::True; - extendedDynamicStateFeatures.extendedDynamicState = vk::True; - extendedDynamicStateFeatures.pNext = &timelineSemaphoreFeatures; - vulkan13Features.pNext = &extendedDynamicStateFeatures; - features.pNext = &vulkan13Features; - - float queuePriority = 0.0f; - vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; - vk::DeviceCreateInfo deviceCreateInfo{ - .pNext = &features, - .queueCreateInfoCount = 1, - .pQueueCreateInfos = &deviceQueueCreateInfo, - .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), - .ppEnabledExtensionNames = requiredDeviceExtension.data() - }; - - device = vk::raii::Device(physicalDevice, deviceCreateInfo); - queue = vk::raii::Queue(device, queueIndex, 0); - } - - void createSwapChain() { - auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( *surface ); - swapChainExtent = chooseSwapExtent( surfaceCapabilities ); - swapChainSurfaceFormat = chooseSwapSurfaceFormat( physicalDevice.getSurfaceFormatsKHR( *surface ) ); - vk::SwapchainCreateInfoKHR swapChainCreateInfo{ .surface = *surface, - .minImageCount = chooseSwapMinImageCount( surfaceCapabilities ), - .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 = *swapChain ? *swapChain : nullptr }; - - swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); - swapChainImages = swapChain.getImages(); - } - - void createImageViews() { - vk::ImageViewCreateInfo imageViewCreateInfo{ - .viewType = vk::ImageViewType::e2D, - .format = swapChainSurfaceFormat.format, - .components = {vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity}, - .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } - }; - for ( auto image : swapChainImages ) - { - imageViewCreateInfo.image = image; - swapChainImageViews.emplace_back( device, imageViewCreateInfo ); - } - } - - void createComputeDescriptorSetLayout() { - std::array layoutBindings{ - vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eCompute, nullptr), - vk::DescriptorSetLayoutBinding(1, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eCompute, nullptr), - vk::DescriptorSetLayoutBinding(2, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eCompute, nullptr) - }; - - vk::DescriptorSetLayoutCreateInfo layoutInfo{ .bindingCount = static_cast(layoutBindings.size()), .pBindings = layoutBindings.data() }; - computeDescriptorSetLayout = vk::raii::DescriptorSetLayout( device, layoutInfo ); - } - - void createGraphicsPipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; - - auto bindingDescription = Particle::getBindingDescription(); - auto attributeDescriptions = Particle::getAttributeDescriptions(); - - vk::PipelineVertexInputStateCreateInfo vertexInputInfo{ .vertexBindingDescriptionCount = 1, .pVertexBindingDescriptions = &bindingDescription, .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), .pVertexAttributeDescriptions = attributeDescriptions.data() }; - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ .topology = vk::PrimitiveTopology::ePointList, .primitiveRestartEnable = vk::False }; - vk::PipelineViewportStateCreateInfo viewportState{ .viewportCount = 1, .scissorCount = 1 }; - vk::PipelineRasterizationStateCreateInfo rasterizer{ - .depthClampEnable = vk::False, - .rasterizerDiscardEnable = vk::False, - .polygonMode = vk::PolygonMode::eFill, - .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eCounterClockwise, - .depthBiasEnable = vk::False, - .lineWidth = 1.0f - }; - vk::PipelineMultisampleStateCreateInfo multisampling{ .rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False }; - - vk::PipelineColorBlendAttachmentState colorBlendAttachment{ - .blendEnable = vk::True, - .srcColorBlendFactor = vk::BlendFactor::eSrcAlpha, - .dstColorBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha, - .colorBlendOp = vk::BlendOp::eAdd, - .srcAlphaBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha, - .dstAlphaBlendFactor = vk::BlendFactor::eZero, - .alphaBlendOp = vk::BlendOp::eAdd, - .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA, - }; - - vk::PipelineColorBlendStateCreateInfo colorBlending{ .logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment }; - - std::vector dynamicStates = { - vk::DynamicState::eViewport, - vk::DynamicState::eScissor - }; - vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo; - pipelineLayout = vk::raii::PipelineLayout( device, pipelineLayoutInfo ); - - vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ .colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format }; - vk::GraphicsPipelineCreateInfo pipelineInfo{ .pNext = &pipelineRenderingCreateInfo, - .stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = *pipelineLayout, - .subpass = 0 - }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - void createComputePipeline() { - vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.spv")); - - // Create push constant range for particle group information - vk::PushConstantRange pushConstantRange{ - .stageFlags = vk::ShaderStageFlagBits::eCompute, - .offset = 0, - .size = sizeof(uint32_t) * 2 // startIndex and count - }; - - vk::PipelineShaderStageCreateInfo computeShaderStageInfo{ .stage = vk::ShaderStageFlagBits::eCompute, .module = shaderModule, .pName = "compMain" }; - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ - .setLayoutCount = 1, - .pSetLayouts = &*computeDescriptorSetLayout, - .pushConstantRangeCount = 1, - .pPushConstantRanges = &pushConstantRange - }; - computePipelineLayout = vk::raii::PipelineLayout( device, pipelineLayoutInfo ); - vk::ComputePipelineCreateInfo pipelineInfo{ .stage = computeShaderStageInfo, .layout = *computePipelineLayout }; - computePipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); - } - - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{}; - poolInfo.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer; - poolInfo.queueFamilyIndex = queueIndex; - commandPool = vk::raii::CommandPool(device, poolInfo); - } - - void createShaderStorageBuffers() { - std::default_random_engine rndEngine(static_cast(time(nullptr))); - std::uniform_real_distribution rndDist(0.0f, 1.0f); - - std::vector particles(PARTICLE_COUNT); - for (auto& particle : particles) { - // Generate a random position for the particle - float theta = rndDist(rndEngine) * 2.0f * 3.14159265358979323846f; - - // Use square root of random value to ensure uniform distribution across the area - // This prevents clustering near the center (which causes the donut effect) - float r = sqrtf(rndDist(rndEngine)) * 0.25f; - - float x = r * cosf(theta) * HEIGHT / WIDTH; - float y = r * sinf(theta); - particle.position = glm::vec2(x, y); - - // Ensure a minimum velocity and scale based on distance from center - float minVelocity = 0.001f; - float velocityScale = 0.003f; - float velocityMagnitude = std::max(minVelocity, r * velocityScale); - particle.velocity = normalize(glm::vec2(x,y)) * velocityMagnitude; - particle.color = glm::vec4(rndDist(rndEngine), rndDist(rndEngine), rndDist(rndEngine), 1.0f); - } - - vk::DeviceSize bufferSize = sizeof(Particle) * PARTICLE_COUNT; - - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(dataStaging, particles.data(), (size_t)bufferSize); - stagingBufferMemory.unmapMemory(); - - shaderStorageBuffers.clear(); - shaderStorageBuffersMemory.clear(); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::raii::Buffer shaderStorageBufferTemp({}); - vk::raii::DeviceMemory shaderStorageBufferTempMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eStorageBuffer | vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eTransferDst, vk::MemoryPropertyFlagBits::eDeviceLocal, shaderStorageBufferTemp, shaderStorageBufferTempMemory); - copyBuffer(stagingBuffer, shaderStorageBufferTemp, bufferSize); - shaderStorageBuffers.emplace_back(std::move(shaderStorageBufferTemp)); - shaderStorageBuffersMemory.emplace_back(std::move(shaderStorageBufferTempMemory)); - } - } - - void createUniformBuffers() { - uniformBuffers.clear(); - uniformBuffersMemory.clear(); - uniformBuffersMapped.clear(); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DeviceSize bufferSize = sizeof(UniformBufferObject); - vk::raii::Buffer buffer({}); - vk::raii::DeviceMemory bufferMem({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); - uniformBuffers.emplace_back(std::move(buffer)); - uniformBuffersMemory.emplace_back(std::move(bufferMem)); - uniformBuffersMapped.emplace_back( uniformBuffersMemory[i].mapMemory(0, bufferSize)); - } - } - - void createDescriptorPool() { - std::array poolSize { - vk::DescriptorPoolSize( vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT), - vk::DescriptorPoolSize( vk::DescriptorType::eStorageBuffer, MAX_FRAMES_IN_FLIGHT * 2) - }; - vk::DescriptorPoolCreateInfo poolInfo{}; - poolInfo.flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet; - poolInfo.maxSets = MAX_FRAMES_IN_FLIGHT; - poolInfo.poolSizeCount = poolSize.size(); - poolInfo.pPoolSizes = poolSize.data(); - descriptorPool = vk::raii::DescriptorPool(device, poolInfo); - } - - void createComputeDescriptorSets() { - std::vector layouts(MAX_FRAMES_IN_FLIGHT, computeDescriptorSetLayout); - vk::DescriptorSetAllocateInfo allocInfo{}; - allocInfo.descriptorPool = *descriptorPool; - allocInfo.descriptorSetCount = MAX_FRAMES_IN_FLIGHT; - allocInfo.pSetLayouts = layouts.data(); - computeDescriptorSets.clear(); - computeDescriptorSets = device.allocateDescriptorSets(allocInfo); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DescriptorBufferInfo bufferInfo(uniformBuffers[i], 0, sizeof(UniformBufferObject)); - - vk::DescriptorBufferInfo storageBufferInfoLastFrame(shaderStorageBuffers[(i + MAX_FRAMES_IN_FLIGHT - 1) % MAX_FRAMES_IN_FLIGHT], 0, sizeof(Particle) * PARTICLE_COUNT); - vk::DescriptorBufferInfo storageBufferInfoCurrentFrame(shaderStorageBuffers[i], 0, sizeof(Particle) * PARTICLE_COUNT); - std::array descriptorWrites{ - vk::WriteDescriptorSet{ .dstSet = *computeDescriptorSets[i], .dstBinding = 0, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eUniformBuffer, .pImageInfo = nullptr, .pBufferInfo = &bufferInfo, .pTexelBufferView = nullptr }, - vk::WriteDescriptorSet{ .dstSet = *computeDescriptorSets[i], .dstBinding = 1, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eStorageBuffer, .pImageInfo = nullptr, .pBufferInfo = &storageBufferInfoLastFrame, .pTexelBufferView = nullptr }, - vk::WriteDescriptorSet{ .dstSet = *computeDescriptorSets[i], .dstBinding = 2, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eStorageBuffer, .pImageInfo = nullptr, .pBufferInfo = &storageBufferInfoCurrentFrame, .pTexelBufferView = nullptr }, + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) + { + // found a queue family that supports both graphics and present + queueIndex = qfpIndex; + break; + } + } + if (queueIndex == ~0) + { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + auto features = physicalDevice.getFeatures2(); + features.features.samplerAnisotropy = vk::True; + vk::PhysicalDeviceVulkan13Features vulkan13Features; + vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT extendedDynamicStateFeatures; + vk::PhysicalDeviceTimelineSemaphoreFeaturesKHR timelineSemaphoreFeatures; + timelineSemaphoreFeatures.timelineSemaphore = vk::True; + vulkan13Features.dynamicRendering = vk::True; + vulkan13Features.synchronization2 = vk::True; + extendedDynamicStateFeatures.extendedDynamicState = vk::True; + extendedDynamicStateFeatures.pNext = &timelineSemaphoreFeatures; + vulkan13Features.pNext = &extendedDynamicStateFeatures; + features.pNext = &vulkan13Features; + + float queuePriority = 0.0f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo{.queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority}; + vk::DeviceCreateInfo deviceCreateInfo{ + .pNext = &features, + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), + .ppEnabledExtensionNames = requiredDeviceExtension.data()}; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + queue = vk::raii::Queue(device, queueIndex, 0); + } + + void createSwapChain() + { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(*surface); + swapChainExtent = chooseSwapExtent(surfaceCapabilities); + swapChainSurfaceFormat = chooseSwapSurfaceFormat(physicalDevice.getSurfaceFormatsKHR(*surface)); + vk::SwapchainCreateInfoKHR swapChainCreateInfo{.surface = *surface, + .minImageCount = chooseSwapMinImageCount(surfaceCapabilities), + .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 = *swapChain ? *swapChain : nullptr}; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + } + + void createImageViews() + { + vk::ImageViewCreateInfo imageViewCreateInfo{ + .viewType = vk::ImageViewType::e2D, + .format = swapChainSurfaceFormat.format, + .components = {vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity}, + .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + for (auto image : swapChainImages) + { + imageViewCreateInfo.image = image; + swapChainImageViews.emplace_back(device, imageViewCreateInfo); + } + } + + void createComputeDescriptorSetLayout() + { + std::array layoutBindings{ + vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eCompute, nullptr), + vk::DescriptorSetLayoutBinding(1, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eCompute, nullptr), + vk::DescriptorSetLayoutBinding(2, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eCompute, nullptr)}; + + vk::DescriptorSetLayoutCreateInfo layoutInfo{.bindingCount = static_cast(layoutBindings.size()), .pBindings = layoutBindings.data()}; + computeDescriptorSetLayout = vk::raii::DescriptorSetLayout(device, layoutInfo); + } + + void createGraphicsPipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.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}; + + auto bindingDescription = Particle::getBindingDescription(); + auto attributeDescriptions = Particle::getAttributeDescriptions(); + + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{.vertexBindingDescriptionCount = 1, .pVertexBindingDescriptions = &bindingDescription, .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), .pVertexAttributeDescriptions = attributeDescriptions.data()}; + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{.topology = vk::PrimitiveTopology::ePointList, .primitiveRestartEnable = vk::False}; + vk::PipelineViewportStateCreateInfo viewportState{.viewportCount = 1, .scissorCount = 1}; + vk::PipelineRasterizationStateCreateInfo rasterizer{ + .depthClampEnable = vk::False, + .rasterizerDiscardEnable = vk::False, + .polygonMode = vk::PolygonMode::eFill, + .cullMode = vk::CullModeFlagBits::eBack, + .frontFace = vk::FrontFace::eCounterClockwise, + .depthBiasEnable = vk::False, + .lineWidth = 1.0f}; + vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; + + vk::PipelineColorBlendAttachmentState colorBlendAttachment{ + .blendEnable = vk::True, + .srcColorBlendFactor = vk::BlendFactor::eSrcAlpha, + .dstColorBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha, + .colorBlendOp = vk::BlendOp::eAdd, + .srcAlphaBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha, + .dstAlphaBlendFactor = vk::BlendFactor::eZero, + .alphaBlendOp = vk::BlendOp::eAdd, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA, + }; + + vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment}; + + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo; + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format}; + vk::GraphicsPipelineCreateInfo pipelineInfo{.pNext = &pipelineRenderingCreateInfo, + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = *pipelineLayout, + .subpass = 0}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + void createComputePipeline() + { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.spv")); + + // Create push constant range for particle group information + vk::PushConstantRange pushConstantRange{ + .stageFlags = vk::ShaderStageFlagBits::eCompute, + .offset = 0, + .size = sizeof(uint32_t) * 2 // startIndex and count + }; + + vk::PipelineShaderStageCreateInfo computeShaderStageInfo{.stage = vk::ShaderStageFlagBits::eCompute, .module = shaderModule, .pName = "compMain"}; + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ + .setLayoutCount = 1, + .pSetLayouts = &*computeDescriptorSetLayout, + .pushConstantRangeCount = 1, + .pPushConstantRanges = &pushConstantRange}; + computePipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + vk::ComputePipelineCreateInfo pipelineInfo{.stage = computeShaderStageInfo, .layout = *computePipelineLayout}; + computePipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{}; + poolInfo.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer; + poolInfo.queueFamilyIndex = queueIndex; + commandPool = vk::raii::CommandPool(device, poolInfo); + } + + void createShaderStorageBuffers() + { + std::default_random_engine rndEngine(static_cast(time(nullptr))); + std::uniform_real_distribution rndDist(0.0f, 1.0f); + + std::vector particles(PARTICLE_COUNT); + for (auto &particle : particles) + { + // Generate a random position for the particle + float theta = rndDist(rndEngine) * 2.0f * 3.14159265358979323846f; + + // Use square root of random value to ensure uniform distribution across the area + // This prevents clustering near the center (which causes the donut effect) + float r = sqrtf(rndDist(rndEngine)) * 0.25f; + + float x = r * cosf(theta) * HEIGHT / WIDTH; + float y = r * sinf(theta); + particle.position = glm::vec2(x, y); + + // Ensure a minimum velocity and scale based on distance from center + float minVelocity = 0.001f; + float velocityScale = 0.003f; + float velocityMagnitude = std::max(minVelocity, r * velocityScale); + particle.velocity = normalize(glm::vec2(x, y)) * velocityMagnitude; + particle.color = glm::vec4(rndDist(rndEngine), rndDist(rndEngine), rndDist(rndEngine), 1.0f); + } + + vk::DeviceSize bufferSize = sizeof(Particle) * PARTICLE_COUNT; + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(dataStaging, particles.data(), (size_t) bufferSize); + stagingBufferMemory.unmapMemory(); + + shaderStorageBuffers.clear(); + shaderStorageBuffersMemory.clear(); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::raii::Buffer shaderStorageBufferTemp({}); + vk::raii::DeviceMemory shaderStorageBufferTempMemory({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eStorageBuffer | vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eTransferDst, vk::MemoryPropertyFlagBits::eDeviceLocal, shaderStorageBufferTemp, shaderStorageBufferTempMemory); + copyBuffer(stagingBuffer, shaderStorageBufferTemp, bufferSize); + shaderStorageBuffers.emplace_back(std::move(shaderStorageBufferTemp)); + shaderStorageBuffersMemory.emplace_back(std::move(shaderStorageBufferTempMemory)); + } + } + + void createUniformBuffers() + { + uniformBuffers.clear(); + uniformBuffersMemory.clear(); + uniformBuffersMapped.clear(); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DeviceSize bufferSize = sizeof(UniformBufferObject); + vk::raii::Buffer buffer({}); + vk::raii::DeviceMemory bufferMem({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); + uniformBuffers.emplace_back(std::move(buffer)); + uniformBuffersMemory.emplace_back(std::move(bufferMem)); + uniformBuffersMapped.emplace_back(uniformBuffersMemory[i].mapMemory(0, bufferSize)); + } + } + + void createDescriptorPool() + { + std::array poolSize{ + vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT), + vk::DescriptorPoolSize(vk::DescriptorType::eStorageBuffer, MAX_FRAMES_IN_FLIGHT * 2)}; + vk::DescriptorPoolCreateInfo poolInfo{}; + poolInfo.flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet; + poolInfo.maxSets = MAX_FRAMES_IN_FLIGHT; + poolInfo.poolSizeCount = poolSize.size(); + poolInfo.pPoolSizes = poolSize.data(); + descriptorPool = vk::raii::DescriptorPool(device, poolInfo); + } + + void createComputeDescriptorSets() + { + std::vector layouts(MAX_FRAMES_IN_FLIGHT, computeDescriptorSetLayout); + vk::DescriptorSetAllocateInfo allocInfo{}; + allocInfo.descriptorPool = *descriptorPool; + allocInfo.descriptorSetCount = MAX_FRAMES_IN_FLIGHT; + allocInfo.pSetLayouts = layouts.data(); + computeDescriptorSets.clear(); + computeDescriptorSets = device.allocateDescriptorSets(allocInfo); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + vk::DescriptorBufferInfo bufferInfo(uniformBuffers[i], 0, sizeof(UniformBufferObject)); + + vk::DescriptorBufferInfo storageBufferInfoLastFrame(shaderStorageBuffers[(i + MAX_FRAMES_IN_FLIGHT - 1) % MAX_FRAMES_IN_FLIGHT], 0, sizeof(Particle) * PARTICLE_COUNT); + vk::DescriptorBufferInfo storageBufferInfoCurrentFrame(shaderStorageBuffers[i], 0, sizeof(Particle) * PARTICLE_COUNT); + std::array descriptorWrites{ + vk::WriteDescriptorSet{.dstSet = *computeDescriptorSets[i], .dstBinding = 0, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eUniformBuffer, .pImageInfo = nullptr, .pBufferInfo = &bufferInfo, .pTexelBufferView = nullptr}, + vk::WriteDescriptorSet{.dstSet = *computeDescriptorSets[i], .dstBinding = 1, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eStorageBuffer, .pImageInfo = nullptr, .pBufferInfo = &storageBufferInfoLastFrame, .pTexelBufferView = nullptr}, + vk::WriteDescriptorSet{.dstSet = *computeDescriptorSets[i], .dstBinding = 2, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eStorageBuffer, .pImageInfo = nullptr, .pBufferInfo = &storageBufferInfoCurrentFrame, .pTexelBufferView = nullptr}, }; - device.updateDescriptorSets(descriptorWrites, {}); - } - } - - void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer& buffer, vk::raii::DeviceMemory& bufferMemory) const { - vk::BufferCreateInfo bufferInfo{}; - bufferInfo.size = size; - bufferInfo.usage = usage; - bufferInfo.sharingMode = vk::SharingMode::eExclusive; - buffer = vk::raii::Buffer(device, bufferInfo); - vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{}; - allocInfo.allocationSize = memRequirements.size; - allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); - bufferMemory = vk::raii::DeviceMemory(device, allocInfo); - buffer.bindMemory(bufferMemory, 0); - } - - [[nodiscard]] vk::raii::CommandBuffer beginSingleTimeCommands() const { - vk::CommandBufferAllocateInfo allocInfo{}; - allocInfo.commandPool = *commandPool; - allocInfo.level = vk::CommandBufferLevel::ePrimary; - allocInfo.commandBufferCount = 1; - vk::raii::CommandBuffer commandBuffer = std::move(vk::raii::CommandBuffers( device, allocInfo ).front()); - - vk::CommandBufferBeginInfo beginInfo{ .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit }; - commandBuffer.begin(beginInfo); - - return commandBuffer; - } - - void endSingleTimeCommands(const vk::raii::CommandBuffer& commandBuffer) const { - commandBuffer.end(); - - vk::SubmitInfo submitInfo{}; - submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &*commandBuffer; - queue.submit(submitInfo, nullptr); - queue.waitIdle(); - } - - void copyBuffer(const vk::raii::Buffer & srcBuffer, const vk::raii::Buffer & dstBuffer, vk::DeviceSize size) const { - vk::raii::CommandBuffer commandCopyBuffer = beginSingleTimeCommands(); - commandCopyBuffer.copyBuffer(srcBuffer, dstBuffer, vk::BufferCopy(0, 0, size)); - endSingleTimeCommands(commandCopyBuffer); - } - - [[nodiscard]] uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) const { - vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); - - for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { - if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { - return i; - } - } - - throw std::runtime_error("failed to find suitable memory type!"); - } - - void createGraphicsCommandBuffers() { - graphicsCommandBuffers.clear(); - vk::CommandBufferAllocateInfo allocInfo{}; - allocInfo.commandPool = *commandPool; - allocInfo.level = vk::CommandBufferLevel::ePrimary; - allocInfo.commandBufferCount = MAX_FRAMES_IN_FLIGHT; - graphicsCommandBuffers = vk::raii::CommandBuffers(device, allocInfo); - } - - void recordComputeCommandBuffer(vk::raii::CommandBuffer& cmdBuffer, uint32_t startIndex, uint32_t count) { - cmdBuffer.reset(); - - vk::CommandBufferBeginInfo beginInfo{ - .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit - }; - cmdBuffer.begin(beginInfo); - - cmdBuffer.bindPipeline(vk::PipelineBindPoint::eCompute, *computePipeline); - cmdBuffer.bindDescriptorSets(vk::PipelineBindPoint::eCompute, *computePipelineLayout, 0, {*computeDescriptorSets[currentFrame]}, {}); - - struct PushConstants { - uint32_t startIndex; - uint32_t count; - } pushConstants{startIndex, count}; - - cmdBuffer.pushConstants(*computePipelineLayout, vk::ShaderStageFlagBits::eCompute, 0, pushConstants); - - uint32_t groupCount = (count + 255) / 256; - cmdBuffer.dispatch(groupCount, 1, 1); - - cmdBuffer.end(); - } - - void recordGraphicsCommandBuffer(uint32_t imageIndex) { - graphicsCommandBuffers[currentFrame].reset(); - - vk::CommandBufferBeginInfo beginInfo{ - .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit - }; - graphicsCommandBuffers[currentFrame].begin(beginInfo); - - transition_image_layout( - imageIndex, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, - vk::AccessFlagBits2::eColorAttachmentWrite, - vk::PipelineStageFlagBits2::eTopOfPipe, - vk::PipelineStageFlagBits2::eColorAttachmentOutput - ); - - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::RenderingAttachmentInfo attachmentInfo = { - .imageView = swapChainImageViews[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor - }; - vk::RenderingInfo renderingInfo = { - .renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &attachmentInfo - }; - - graphicsCommandBuffers[currentFrame].beginRendering(renderingInfo); - - graphicsCommandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - graphicsCommandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - graphicsCommandBuffers[currentFrame].setScissor( 0, vk::Rect2D( vk::Offset2D( 0, 0 ), swapChainExtent ) ); - graphicsCommandBuffers[currentFrame].bindVertexBuffers(0, { shaderStorageBuffers[currentFrame] }, {0}); - graphicsCommandBuffers[currentFrame].draw( PARTICLE_COUNT, 1, 0, 0 ); - graphicsCommandBuffers[currentFrame].endRendering(); - - transition_image_layout( - imageIndex, - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, - {}, - vk::PipelineStageFlagBits2::eColorAttachmentOutput, - vk::PipelineStageFlagBits2::eBottomOfPipe - ); - - graphicsCommandBuffers[currentFrame].end(); - } - - void transition_image_layout( - uint32_t imageIndex, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::AccessFlags2 src_access_mask, - vk::AccessFlags2 dst_access_mask, - vk::PipelineStageFlags2 src_stage_mask, - vk::PipelineStageFlags2 dst_stage_mask - ) { - vk::ImageMemoryBarrier2 barrier = { - .srcStageMask = src_stage_mask, - .srcAccessMask = src_access_mask, - .dstStageMask = dst_stage_mask, - .dstAccessMask = dst_access_mask, - .oldLayout = old_layout, - .newLayout = new_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - graphicsCommandBuffers[currentFrame].pipelineBarrier2(dependency_info); - } - - void signalThreadsToWork() { - // Mark all threads as not done - for (uint32_t i = 0; i < threadCount; i++) { - threadWorkDone[i].store(false, std::memory_order_release); - } - - // Memory barrier to ensure all threads see the updated threadWorkDone values - std::atomic_thread_fence(std::memory_order_seq_cst); - - // Only signal the first thread to start work - threadWorkReady[0].store(true, std::memory_order_release); - - // Notify all threads in case they're waiting on the condition variable - { - std::lock_guard lock(workCompleteMutex); - workCompleteCv.notify_all(); - } - } - - void waitForThreadsToComplete() { - std::unique_lock lock(workCompleteMutex); - - // Wait for the last thread to complete with a timeout - auto waitResult = workCompleteCv.wait_for(lock, std::chrono::milliseconds(3000), [this]() { - return threadWorkDone[threadCount - 1].load(std::memory_order_acquire); - }); - - // If we timed out, force completion - if (!waitResult) { - // Force all threads to complete - for (uint32_t i = 0; i < threadCount; i++) { - threadWorkDone[i].store(true, std::memory_order_release); - threadWorkReady[i].store(false, std::memory_order_release); - } - - // Notify all threads - workCompleteCv.notify_all(); - lock.unlock(); - - // Give threads a chance to respond to the forced completion - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } - } - - void createSyncObjects() { - imageAvailableSemaphores.clear(); - inFlightFences.clear(); - - vk::SemaphoreTypeCreateInfo semaphoreType{ .semaphoreType = vk::SemaphoreType::eTimeline, .initialValue = 0 }; - timelineSemaphore = vk::raii::Semaphore(device, {.pNext = &semaphoreType}); - timelineValue = 0; - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - imageAvailableSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); - - vk::FenceCreateInfo fenceInfo; - fenceInfo.flags = vk::FenceCreateFlagBits::eSignaled; - inFlightFences.emplace_back(device, fenceInfo); - } - } - - void updateUniformBuffer(uint32_t currentImage) { - UniformBufferObject ubo{}; - ubo.deltaTime = static_cast(lastFrameTime) * 2.0f; - - memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); - } - - void drawFrame() { - // Wait for the previous frame to finish - while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) - ; - device.resetFences(*inFlightFences[currentFrame]); - - // Acquire the next image - auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *imageAvailableSemaphores[currentFrame], nullptr); - - // Update timeline values for synchronization - uint64_t computeWaitValue = timelineValue; - uint64_t computeSignalValue = ++timelineValue; - uint64_t graphicsWaitValue = computeSignalValue; - uint64_t graphicsSignalValue = ++timelineValue; - - // Update uniform buffer with the latest delta time - updateUniformBuffer(currentFrame); - - // Signal worker threads to start processing particles - signalThreadsToWork(); - - // Record graphics command buffer while worker threads are busy - recordGraphicsCommandBuffer(imageIndex); - - // Wait for all worker threads to complete - waitForThreadsToComplete(); - - // Collect command buffers from all threads - std::vector computeCmdBuffers; - computeCmdBuffers.reserve(threadCount); - for (uint32_t i = 0; i < threadCount; i++) { - try { - computeCmdBuffers.push_back(*resourceManager.getCommandBuffer(i)); - } catch (const std::exception&) { - // Skip this thread's command buffer if there was an error - } - } - - // Ensure we have at least one command buffer - if (computeCmdBuffers.empty()) { - return; - } - - // Set up compute submission - vk::TimelineSemaphoreSubmitInfo computeTimelineInfo{ - .waitSemaphoreValueCount = 1, - .pWaitSemaphoreValues = &computeWaitValue, - .signalSemaphoreValueCount = 1, - .pSignalSemaphoreValues = &computeSignalValue - }; - - vk::PipelineStageFlags waitStages[] = {vk::PipelineStageFlagBits::eComputeShader}; - - vk::SubmitInfo computeSubmitInfo{ - .pNext = &computeTimelineInfo, - .waitSemaphoreCount = 1, - .pWaitSemaphores = &*timelineSemaphore, - .pWaitDstStageMask = waitStages, - .commandBufferCount = static_cast(computeCmdBuffers.size()), - .pCommandBuffers = computeCmdBuffers.data(), - .signalSemaphoreCount = 1, - .pSignalSemaphores = &*timelineSemaphore - }; - - // Submit compute work - { - std::lock_guard lock(queueSubmitMutex); - queue.submit(computeSubmitInfo, nullptr); - } - - // Set up graphics submission - vk::PipelineStageFlags graphicsWaitStages[] = {vk::PipelineStageFlagBits::eVertexInput, vk::PipelineStageFlagBits::eColorAttachmentOutput}; - - std::array waitSemaphores = {*timelineSemaphore, *imageAvailableSemaphores[currentFrame]}; - std::array waitSemaphoreValues = {graphicsWaitValue, 0}; - - vk::TimelineSemaphoreSubmitInfo graphicsTimelineInfo{ - .waitSemaphoreValueCount = static_cast(waitSemaphoreValues.size()), - .pWaitSemaphoreValues = waitSemaphoreValues.data(), - .signalSemaphoreValueCount = 1, - .pSignalSemaphoreValues = &graphicsSignalValue - }; - - vk::SubmitInfo graphicsSubmitInfo{ - .pNext = &graphicsTimelineInfo, - .waitSemaphoreCount = static_cast(waitSemaphores.size()), - .pWaitSemaphores = waitSemaphores.data(), - .pWaitDstStageMask = graphicsWaitStages, - .commandBufferCount = 1, - .pCommandBuffers = &*graphicsCommandBuffers[currentFrame], - .signalSemaphoreCount = 1, - .pSignalSemaphores = &*timelineSemaphore - }; - - // Submit graphics work - { - std::lock_guard lock(queueSubmitMutex); - queue.submit(graphicsSubmitInfo, *inFlightFences[currentFrame]); - } - - // Wait for graphics to complete before presenting - vk::SemaphoreWaitInfo waitInfo{ - .semaphoreCount = 1, - .pSemaphores = &*timelineSemaphore, - .pValues = &graphicsSignalValue - }; - - auto waitResult = device.waitSemaphores(waitInfo, 5000000000); - if (waitResult == vk::Result::eTimeout) { - device.waitIdle(); - return; - } - - // Present the image - vk::PresentInfoKHR presentInfo{ - .waitSemaphoreCount = 0, - .pWaitSemaphores = nullptr, - .swapchainCount = 1, - .pSwapchains = &*swapChain, - .pImageIndices = &imageIndex - }; - - result = queue.presentKHR(presentInfo); - - // Move to the next frame - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; - } - + device.updateDescriptorSets(descriptorWrites, {}); + } + } + + void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer &buffer, vk::raii::DeviceMemory &bufferMemory) const + { + vk::BufferCreateInfo bufferInfo{}; + bufferInfo.size = size; + bufferInfo.usage = usage; + bufferInfo.sharingMode = vk::SharingMode::eExclusive; + buffer = vk::raii::Buffer(device, bufferInfo); + vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{}; + allocInfo.allocationSize = memRequirements.size; + allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); + bufferMemory = vk::raii::DeviceMemory(device, allocInfo); + buffer.bindMemory(bufferMemory, 0); + } + + [[nodiscard]] vk::raii::CommandBuffer beginSingleTimeCommands() const + { + vk::CommandBufferAllocateInfo allocInfo{}; + allocInfo.commandPool = *commandPool; + allocInfo.level = vk::CommandBufferLevel::ePrimary; + allocInfo.commandBufferCount = 1; + vk::raii::CommandBuffer commandBuffer = std::move(vk::raii::CommandBuffers(device, allocInfo).front()); + + vk::CommandBufferBeginInfo beginInfo{.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; + commandBuffer.begin(beginInfo); + + return commandBuffer; + } + + void endSingleTimeCommands(const vk::raii::CommandBuffer &commandBuffer) const + { + commandBuffer.end(); + + vk::SubmitInfo submitInfo{}; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &*commandBuffer; + queue.submit(submitInfo, nullptr); + queue.waitIdle(); + } + + void copyBuffer(const vk::raii::Buffer &srcBuffer, const vk::raii::Buffer &dstBuffer, vk::DeviceSize size) const + { + vk::raii::CommandBuffer commandCopyBuffer = beginSingleTimeCommands(); + commandCopyBuffer.copyBuffer(srcBuffer, dstBuffer, vk::BufferCopy(0, 0, size)); + endSingleTimeCommands(commandCopyBuffer); + } + + [[nodiscard]] uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) const + { + vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) + { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) + { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + + void createGraphicsCommandBuffers() + { + graphicsCommandBuffers.clear(); + vk::CommandBufferAllocateInfo allocInfo{}; + allocInfo.commandPool = *commandPool; + allocInfo.level = vk::CommandBufferLevel::ePrimary; + allocInfo.commandBufferCount = MAX_FRAMES_IN_FLIGHT; + graphicsCommandBuffers = vk::raii::CommandBuffers(device, allocInfo); + } + + void recordComputeCommandBuffer(vk::raii::CommandBuffer &cmdBuffer, uint32_t startIndex, uint32_t count) + { + cmdBuffer.reset(); + + vk::CommandBufferBeginInfo beginInfo{ + .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; + cmdBuffer.begin(beginInfo); + + cmdBuffer.bindPipeline(vk::PipelineBindPoint::eCompute, *computePipeline); + cmdBuffer.bindDescriptorSets(vk::PipelineBindPoint::eCompute, *computePipelineLayout, 0, {*computeDescriptorSets[currentFrame]}, {}); + + struct PushConstants + { + uint32_t startIndex; + uint32_t count; + } pushConstants{startIndex, count}; + + cmdBuffer.pushConstants(*computePipelineLayout, vk::ShaderStageFlagBits::eCompute, 0, pushConstants); + + uint32_t groupCount = (count + 255) / 256; + cmdBuffer.dispatch(groupCount, 1, 1); + + cmdBuffer.end(); + } + + void recordGraphicsCommandBuffer(uint32_t imageIndex) + { + graphicsCommandBuffers[currentFrame].reset(); + + vk::CommandBufferBeginInfo beginInfo{ + .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; + graphicsCommandBuffers[currentFrame].begin(beginInfo); + + transition_image_layout( + imageIndex, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, + vk::AccessFlagBits2::eColorAttachmentWrite, + vk::PipelineStageFlagBits2::eTopOfPipe, + vk::PipelineStageFlagBits2::eColorAttachmentOutput); + + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::RenderingAttachmentInfo attachmentInfo = { + .imageView = swapChainImageViews[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor}; + vk::RenderingInfo renderingInfo = { + .renderArea = {.offset = {0, 0}, .extent = swapChainExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &attachmentInfo}; + + graphicsCommandBuffers[currentFrame].beginRendering(renderingInfo); + + graphicsCommandBuffers[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); + graphicsCommandBuffers[currentFrame].setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + graphicsCommandBuffers[currentFrame].setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + graphicsCommandBuffers[currentFrame].bindVertexBuffers(0, {shaderStorageBuffers[currentFrame]}, {0}); + graphicsCommandBuffers[currentFrame].draw(PARTICLE_COUNT, 1, 0, 0); + graphicsCommandBuffers[currentFrame].endRendering(); + + transition_image_layout( + imageIndex, + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, + {}, + vk::PipelineStageFlagBits2::eColorAttachmentOutput, + vk::PipelineStageFlagBits2::eBottomOfPipe); + + graphicsCommandBuffers[currentFrame].end(); + } + + void transition_image_layout( + uint32_t imageIndex, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout, + vk::AccessFlags2 src_access_mask, + vk::AccessFlags2 dst_access_mask, + vk::PipelineStageFlags2 src_stage_mask, + vk::PipelineStageFlags2 dst_stage_mask) + { + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = src_stage_mask, + .srcAccessMask = src_access_mask, + .dstStageMask = dst_stage_mask, + .dstAccessMask = dst_access_mask, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapChainImages[imageIndex], + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo dependency_info = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + graphicsCommandBuffers[currentFrame].pipelineBarrier2(dependency_info); + } + + void signalThreadsToWork() + { + // Mark all threads as not done + for (uint32_t i = 0; i < threadCount; i++) + { + threadWorkDone[i].store(false, std::memory_order_release); + } + + // Memory barrier to ensure all threads see the updated threadWorkDone values + std::atomic_thread_fence(std::memory_order_seq_cst); + + // Only signal the first thread to start work + threadWorkReady[0].store(true, std::memory_order_release); + + // Notify all threads in case they're waiting on the condition variable + { + std::lock_guard lock(workCompleteMutex); + workCompleteCv.notify_all(); + } + } + + void waitForThreadsToComplete() + { + std::unique_lock lock(workCompleteMutex); + + // Wait for the last thread to complete with a timeout + auto waitResult = workCompleteCv.wait_for(lock, std::chrono::milliseconds(3000), [this]() { + return threadWorkDone[threadCount - 1].load(std::memory_order_acquire); + }); + + // If we timed out, force completion + if (!waitResult) + { + // Force all threads to complete + for (uint32_t i = 0; i < threadCount; i++) + { + threadWorkDone[i].store(true, std::memory_order_release); + threadWorkReady[i].store(false, std::memory_order_release); + } + + // Notify all threads + workCompleteCv.notify_all(); + lock.unlock(); + + // Give threads a chance to respond to the forced completion + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + } + + void createSyncObjects() + { + imageAvailableSemaphores.clear(); + inFlightFences.clear(); + + vk::SemaphoreTypeCreateInfo semaphoreType{.semaphoreType = vk::SemaphoreType::eTimeline, .initialValue = 0}; + timelineSemaphore = vk::raii::Semaphore(device, {.pNext = &semaphoreType}); + timelineValue = 0; + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + imageAvailableSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); + + vk::FenceCreateInfo fenceInfo; + fenceInfo.flags = vk::FenceCreateFlagBits::eSignaled; + inFlightFences.emplace_back(device, fenceInfo); + } + } + + void updateUniformBuffer(uint32_t currentImage) + { + UniformBufferObject ubo{}; + ubo.deltaTime = static_cast(lastFrameTime) * 2.0f; + + memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); + } + + void drawFrame() + { + // Wait for the previous frame to finish + while (vk::Result::eTimeout == device.waitForFences(*inFlightFences[currentFrame], vk::True, UINT64_MAX)) + ; + device.resetFences(*inFlightFences[currentFrame]); + + // Acquire the next image + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *imageAvailableSemaphores[currentFrame], nullptr); + + // Update timeline values for synchronization + uint64_t computeWaitValue = timelineValue; + uint64_t computeSignalValue = ++timelineValue; + uint64_t graphicsWaitValue = computeSignalValue; + uint64_t graphicsSignalValue = ++timelineValue; + + // Update uniform buffer with the latest delta time + updateUniformBuffer(currentFrame); + + // Signal worker threads to start processing particles + signalThreadsToWork(); + + // Record graphics command buffer while worker threads are busy + recordGraphicsCommandBuffer(imageIndex); + + // Wait for all worker threads to complete + waitForThreadsToComplete(); + + // Collect command buffers from all threads + std::vector computeCmdBuffers; + computeCmdBuffers.reserve(threadCount); + for (uint32_t i = 0; i < threadCount; i++) + { + try + { + computeCmdBuffers.push_back(*resourceManager.getCommandBuffer(i)); + } + catch (const std::exception &) + { + // Skip this thread's command buffer if there was an error + } + } + + // Ensure we have at least one command buffer + if (computeCmdBuffers.empty()) + { + return; + } + + // Set up compute submission + vk::TimelineSemaphoreSubmitInfo computeTimelineInfo{ + .waitSemaphoreValueCount = 1, + .pWaitSemaphoreValues = &computeWaitValue, + .signalSemaphoreValueCount = 1, + .pSignalSemaphoreValues = &computeSignalValue}; + + vk::PipelineStageFlags waitStages[] = {vk::PipelineStageFlagBits::eComputeShader}; + + vk::SubmitInfo computeSubmitInfo{ + .pNext = &computeTimelineInfo, + .waitSemaphoreCount = 1, + .pWaitSemaphores = &*timelineSemaphore, + .pWaitDstStageMask = waitStages, + .commandBufferCount = static_cast(computeCmdBuffers.size()), + .pCommandBuffers = computeCmdBuffers.data(), + .signalSemaphoreCount = 1, + .pSignalSemaphores = &*timelineSemaphore}; + + // Submit compute work + { + std::lock_guard lock(queueSubmitMutex); + queue.submit(computeSubmitInfo, nullptr); + } + + // Set up graphics submission + vk::PipelineStageFlags graphicsWaitStages[] = {vk::PipelineStageFlagBits::eVertexInput, vk::PipelineStageFlagBits::eColorAttachmentOutput}; + + std::array waitSemaphores = {*timelineSemaphore, *imageAvailableSemaphores[currentFrame]}; + std::array waitSemaphoreValues = {graphicsWaitValue, 0}; + + vk::TimelineSemaphoreSubmitInfo graphicsTimelineInfo{ + .waitSemaphoreValueCount = static_cast(waitSemaphoreValues.size()), + .pWaitSemaphoreValues = waitSemaphoreValues.data(), + .signalSemaphoreValueCount = 1, + .pSignalSemaphoreValues = &graphicsSignalValue}; + + vk::SubmitInfo graphicsSubmitInfo{ + .pNext = &graphicsTimelineInfo, + .waitSemaphoreCount = static_cast(waitSemaphores.size()), + .pWaitSemaphores = waitSemaphores.data(), + .pWaitDstStageMask = graphicsWaitStages, + .commandBufferCount = 1, + .pCommandBuffers = &*graphicsCommandBuffers[currentFrame], + .signalSemaphoreCount = 1, + .pSignalSemaphores = &*timelineSemaphore}; + + // Submit graphics work + { + std::lock_guard lock(queueSubmitMutex); + queue.submit(graphicsSubmitInfo, *inFlightFences[currentFrame]); + } + + // Wait for graphics to complete before presenting + vk::SemaphoreWaitInfo waitInfo{ + .semaphoreCount = 1, + .pSemaphores = &*timelineSemaphore, + .pValues = &graphicsSignalValue}; + + auto waitResult = device.waitSemaphores(waitInfo, 5000000000); + if (waitResult == vk::Result::eTimeout) + { + device.waitIdle(); + return; + } + + // Present the image + vk::PresentInfoKHR presentInfo{ + .waitSemaphoreCount = 0, + .pWaitSemaphores = nullptr, + .swapchainCount = 1, + .pSwapchains = &*swapChain, + .pImageIndices = &imageIndex}; + + result = queue.presentKHR(presentInfo); + + // Move to the next frame + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } }; - -int main() { - try { - MultithreadedApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + MultithreadedApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } From 3ea68a8a42dfe231b9fa1438a73786f7b1f6cf8b Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Sat, 23 Aug 2025 16:01:58 +0200 Subject: [PATCH 6/6] Don't sort includes --- attachments/.clang-format | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/attachments/.clang-format b/attachments/.clang-format index 109e0b7f..9513aa5f 100644 --- a/attachments/.clang-format +++ b/attachments/.clang-format @@ -86,7 +86,7 @@ PenaltyExcessCharacter: 1000000 PenaltyReturnTypeOnItsOwnLine: 60 PointerAlignment: Right ReflowComments: true -SortIncludes: true +SortIncludes: false SortUsingDeclarations: true SpaceAfterCStyleCast: true SpaceAfterTemplateKeyword: true