diff --git a/CONTRIBUTING.adoc b/CONTRIBUTING.adoc index d96991c7..26fa4c89 100644 --- a/CONTRIBUTING.adoc +++ b/CONTRIBUTING.adoc @@ -34,3 +34,16 @@ Any change or fix needs to consider this and make sure that it follows Antora's The content's of the tutorial are written in Asciidoc (adoc file extension). New content or changes to existing content need comply with this format. If you are new to Asciidoc, the link:https://docs.antora.org/antora/latest/asciidoc/asciidoc/[Antora's Asciidoc primer] is a good starting point. Similar to other markdown languages, most development environments support live preview for Asciidoc. For Visual Studio Code, link:https://marketplace.visualstudio.com/items?itemName=asciidoctor.asciidoctor-vscode[this extension] is recommended. It's advised to set the `asciidoc.preview.useEditorStyle` extension setting to `false` to get a preview look similar to the Antora site and also enable the extension's `asciidoc.antora.enableAntoraSupport` option. + + +=== ClangFormat + +To ensure a consistent code style, we use a link:https://clang.llvm.org/docs/ClangFormat.html[ClangFormat] definition file. If you make changes to source code, make sure that file is used by your IDE or manually apply ClangFormat to the files you have changed. + +Example: + +[,bash] +---- +cd attachments +clang-format -i -- 15_hello_triangle.* +---- diff --git a/attachments/.clang-format b/attachments/.clang-format new file mode 100644 index 00000000..869b1dfd --- /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/00_base_code.cpp b/attachments/00_base_code.cpp index 59367983..3b685670 100644 --- a/attachments/00_base_code.cpp +++ b/attachments/00_base_code.cpp @@ -1,64 +1,74 @@ #include #if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) -#include +# include #else import vulkan_hpp; #endif #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 35133b7d..4686f1b6 100644 --- a/attachments/01_instance_creation.cpp +++ b/attachments/01_instance_creation.cpp @@ -1,100 +1,111 @@ #include -#include -#include #include +#include #include +#include #if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) -#include +# include #else import vulkan_hpp; #endif -#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 e6f0a121..11fb1e0d 100644 --- a/attachments/02_validation_layers.cpp +++ b/attachments/02_validation_layers.cpp @@ -1,26 +1,25 @@ +#include +#include +#include #include +#include #include #include -#include -#include -#include -#include #if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) -#include +# include #else import vulkan_hpp; #endif -#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" -}; +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -28,139 +27,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 6c6228ba..5cc61133 100644 --- a/attachments/03_physical_device_selection.cpp +++ b/attachments/03_physical_device_selection.cpp @@ -1,26 +1,25 @@ +#include +#include +#include #include +#include #include #include -#include -#include -#include -#include #if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) -#include +# include #else import vulkan_hpp; #endif -#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" -}; +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -28,190 +27,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 1e7f261c..d4f24772 100644 --- a/attachments/04_logical_device.cpp +++ b/attachments/04_logical_device.cpp @@ -1,27 +1,26 @@ +#include #include +#include +#include #include +#include #include #include -#include -#include -#include -#include #if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) -#include +# include #else import vulkan_hpp; #endif -#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" -}; +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -29,225 +28,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 7fa00324..66b1c5a5 100644 --- a/attachments/05_window_surface.cpp +++ b/attachments/05_window_surface.cpp @@ -1,26 +1,25 @@ +#include +#include +#include #include +#include #include #include -#include -#include -#include -#include #if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) -#include +# include #else import vulkan_hpp; #endif -#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" -}; +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -28,241 +27,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 8e554389..d7e8c212 100644 --- a/attachments/06_swap_chain_creation.cpp +++ b/attachments/06_swap_chain_creation.cpp @@ -1,28 +1,27 @@ +#include #include +#include +#include #include +#include +#include #include #include -#include -#include -#include -#include -#include #if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) -#include +# include #else import vulkan_hpp; #endif -#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" -}; +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -30,303 +29,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 c45edceb..b60618d4 100644 --- a/attachments/07_image_views.cpp +++ b/attachments/07_image_views.cpp @@ -1,28 +1,27 @@ +#include +#include +#include +#include #include +#include +#include #include #include -#include -#include -#include -#include -#include -#include #if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) -#include +# include #else import vulkan_hpp; #endif -#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" -}; +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -30,316 +29,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() { - assert(swapChainImageViews.empty()); - - 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() + { + assert(swapChainImageViews.empty()); + + 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 c18e400c..4000ddb7 100644 --- a/attachments/08_graphics_pipeline.cpp +++ b/attachments/08_graphics_pipeline.cpp @@ -1,28 +1,27 @@ +#include +#include +#include +#include #include +#include +#include #include #include -#include -#include -#include -#include -#include -#include #if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) -#include +# include #else import vulkan_hpp; #endif -#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" -}; +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -30,321 +29,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() { - assert(swapChainImageViews.empty()); - - 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() + { + assert(swapChainImageViews.empty()); + + 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 bf7129e1..795263f9 100644 --- a/attachments/09_shader_modules.cpp +++ b/attachments/09_shader_modules.cpp @@ -1,29 +1,28 @@ -#include +#include +#include +#include +#include #include +#include +#include +#include #include #include -#include -#include -#include -#include -#include -#include #if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) -#include +# include #else import vulkan_hpp; #endif -#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" -}; +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -31,353 +30,381 @@ 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().shaderDrawParameters && - 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 - {.shaderDrawParameters = true }, // vk::PhysicalDeviceVulkan11Features - {.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() { - assert(swapChainImageViews.empty()); - - 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().shaderDrawParameters && + 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 + {.shaderDrawParameters = true}, // vk::PhysicalDeviceVulkan11Features + {.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() + { + assert(swapChainImageViews.empty()); + + 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 eb388088..7bdc9f56 100644 --- a/attachments/10_fixed_functions.cpp +++ b/attachments/10_fixed_functions.cpp @@ -1,29 +1,28 @@ -#include +#include +#include +#include +#include #include +#include +#include +#include #include #include -#include -#include -#include -#include -#include -#include #if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) -#include +# include #else import vulkan_hpp; #endif -#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" -}; +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -31,382 +30,405 @@ 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().shaderDrawParameters && - 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 - {.shaderDrawParameters = true }, // vk::PhysicalDeviceVulkan11Features - {.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() { - assert(swapChainImageViews.empty()); - - 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().shaderDrawParameters && + 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 + {.shaderDrawParameters = true}, // vk::PhysicalDeviceVulkan11Features + {.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() + { + assert(swapChainImageViews.empty()); + + 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 03dcf260..07830622 100644 --- a/attachments/12_graphics_pipeline_complete.cpp +++ b/attachments/12_graphics_pipeline_complete.cpp @@ -1,29 +1,28 @@ -#include +#include +#include +#include +#include #include +#include +#include +#include #include #include -#include -#include -#include -#include -#include -#include #if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) -#include +# include #else import vulkan_hpp; #endif -#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" -}; +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -31,400 +30,422 @@ 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().shaderDrawParameters && - 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 - {.shaderDrawParameters = true }, // vk::PhysicalDeviceVulkan11Features - {.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() { - assert(swapChainImageViews.empty()); - - 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::StructureChain pipelineCreateInfoChain = { - {.stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = pipelineLayout, - .renderPass = nullptr }, - {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format } - }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); - } - - [[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().shaderDrawParameters && + 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 + {.shaderDrawParameters = true}, // vk::PhysicalDeviceVulkan11Features + {.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() + { + assert(swapChainImageViews.empty()); + + 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::StructureChain pipelineCreateInfoChain = { + {.stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}, + {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format}}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); + } + + [[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 03a4f51a..02e42792 100644 --- a/attachments/14_command_buffers.cpp +++ b/attachments/14_command_buffers.cpp @@ -1,29 +1,28 @@ -#include +#include +#include +#include +#include #include +#include +#include +#include #include #include -#include -#include -#include -#include -#include -#include #if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) -#include +# include #else import vulkan_hpp; #endif -#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" -}; +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -31,416 +30,439 @@ 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().shaderDrawParameters && - 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 - {.shaderDrawParameters = true }, // vk::PhysicalDeviceVulkan11Features - {.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() { - assert(swapChainImageViews.empty()); - - 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::StructureChain pipelineCreateInfoChain = { - {.stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = pipelineLayout, - .renderPass = nullptr }, - {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format } - }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); - } - - 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()); - } - - [[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().shaderDrawParameters && + 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 + {.shaderDrawParameters = true}, // vk::PhysicalDeviceVulkan11Features + {.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() + { + assert(swapChainImageViews.empty()); + + 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::StructureChain pipelineCreateInfoChain = { + {.stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}, + {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format}}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); + } + + 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()); + } + + [[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 7ed23058..1848011e 100644 --- a/attachments/15_hello_triangle.cpp +++ b/attachments/15_hello_triangle.cpp @@ -1,29 +1,28 @@ -#include +#include +#include +#include +#include #include +#include +#include +#include #include #include -#include -#include -#include -#include -#include -#include #if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) -#include +# include #else import vulkan_hpp; #endif -#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" -}; +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -31,539 +30,561 @@ 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(); // wait for device to finish operations before destroying resources - } - - 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().shaderDrawParameters && - features.template get().synchronization2 && - 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 - {.shaderDrawParameters = true }, // vk::PhysicalDeviceVulkan11Features - {.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() { - assert(swapChainImageViews.empty()); - - 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::StructureChain pipelineCreateInfoChain = { - {.stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = pipelineLayout, - .renderPass = nullptr }, - {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format } - }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); - } - - 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::eColorAttachmentOutput, // 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(); // NOTE: for simplicity, wait for the queue to be idle before starting the frame - // In the next chapter you see how to use multiple frames in flight and fences to sync - - 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(); // wait for device to finish operations before destroying resources + } + + 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().shaderDrawParameters && + features.template get().synchronization2 && + 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 + {.shaderDrawParameters = true}, // vk::PhysicalDeviceVulkan11Features + {.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() + { + assert(swapChainImageViews.empty()); + + 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::StructureChain pipelineCreateInfoChain = { + {.stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}, + {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format}}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); + } + + 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::eColorAttachmentOutput, // 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(); // NOTE: for simplicity, wait for the queue to be idle before starting the frame + // In the next chapter you see how to use multiple frames in flight and fences to sync + + 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 ef5c1ad2..5e6725c2 100644 --- a/attachments/16_frames_in_flight.cpp +++ b/attachments/16_frames_in_flight.cpp @@ -1,30 +1,29 @@ -#include +#include +#include +#include +#include #include +#include +#include +#include #include #include -#include -#include -#include -#include -#include -#include #if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) -#include +# include #else import vulkan_hpp; #endif -#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" -}; +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -32,554 +31,575 @@ 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().shaderDrawParameters && - features.template get().synchronization2 && - 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 - {.shaderDrawParameters = true }, // vk::PhysicalDeviceVulkan11Features - {.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() { - assert(swapChainImageViews.empty()); - - 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::StructureChain pipelineCreateInfoChain = { - {.stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = pipelineLayout, - .renderPass = nullptr }, - {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format } - }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); - } - - 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::eColorAttachmentOutput, // 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().shaderDrawParameters && + features.template get().synchronization2 && + 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 + {.shaderDrawParameters = true}, // vk::PhysicalDeviceVulkan11Features + {.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() + { + assert(swapChainImageViews.empty()); + + 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::StructureChain pipelineCreateInfoChain = { + {.stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}, + {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format}}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); + } + + 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::eColorAttachmentOutput, // 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 5c52dd97..6f7f5b1b 100644 --- a/attachments/17_swap_chain_recreation.cpp +++ b/attachments/17_swap_chain_recreation.cpp @@ -1,30 +1,29 @@ -#include +#include +#include +#include +#include #include +#include +#include +#include #include #include -#include -#include -#include -#include -#include -#include #if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) -#include +# include #else import vulkan_hpp; #endif -#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" -}; +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -32,600 +31,633 @@ 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); - glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); - - 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().shaderDrawParameters && - features.template get().synchronization2 && - 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 - {.shaderDrawParameters = true }, // vk::PhysicalDeviceVulkan11Features - {.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() { - assert(swapChainImageViews.empty()); - - 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::StructureChain pipelineCreateInfoChain = { - {.stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = pipelineLayout, - .renderPass = nullptr }, - {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format } - }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); - } - - 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::eColorAttachmentOutput, // 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]); - - - try { - 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!"); - } - } catch (const vk::SystemError& e) { - if (e.code().value() == static_cast(vk::Result::eErrorOutOfDateKHR)) { - recreateSwapChain(); - return; - } else { - throw; - } - } - 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); + glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); + + 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().shaderDrawParameters && + features.template get().synchronization2 && + 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 + {.shaderDrawParameters = true}, // vk::PhysicalDeviceVulkan11Features + {.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() + { + assert(swapChainImageViews.empty()); + + 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::StructureChain pipelineCreateInfoChain = { + {.stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}, + {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format}}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); + } + + 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::eColorAttachmentOutput, // 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]); + + try + { + 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!"); + } + } + catch (const vk::SystemError &e) + { + if (e.code().value() == static_cast(vk::Result::eErrorOutOfDateKHR)) + { + recreateSwapChain(); + return; + } + else + { + throw; + } + } + 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 16632fd1..2aaa6406 100644 --- a/attachments/18_vertex_input.cpp +++ b/attachments/18_vertex_input.cpp @@ -1,32 +1,31 @@ -#include -#include -#include -#include -#include -#include -#include #include -#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include #if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) -#include +# include #else import vulkan_hpp; #endif -#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" -}; +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -34,617 +33,650 @@ 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); + glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); + + 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().shaderDrawParameters && + 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 required features (Vulkan 1.1 and 1.3) + vk::StructureChain featureChain = { + {}, // vk::PhysicalDeviceFeatures2 + {.shaderDrawParameters = true}, // vk::PhysicalDeviceVulkan11Features + {.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() + { + assert(swapChainImageViews.empty()); + + 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::StructureChain pipelineCreateInfoChain = { + {.stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}, + {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format}}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); + } + + 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::eColorAttachmentOutput, // 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]); + + try + { + 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!"); + } + } + catch (const vk::SystemError &e) + { + if (e.code().value() == static_cast(vk::Result::eErrorOutOfDateKHR)) + { + recreateSwapChain(); + return; + } + else + { + throw; + } + } + 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); - glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); - - 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().shaderDrawParameters && - 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 required features (Vulkan 1.1 and 1.3) - vk::StructureChain featureChain = { - {}, // vk::PhysicalDeviceFeatures2 - { .shaderDrawParameters = true }, // vk::PhysicalDeviceVulkan11Features - { .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() { - assert(swapChainImageViews.empty()); - - 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::StructureChain pipelineCreateInfoChain = { - {.stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = pipelineLayout, - .renderPass = nullptr }, - {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format } - }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); - } - - 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::eColorAttachmentOutput, // 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]); - - - try { - 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!"); - } - } catch (const vk::SystemError& e) { - if (e.code().value() == static_cast(vk::Result::eErrorOutOfDateKHR)) { - recreateSwapChain(); - return; - } else { - throw; - } - } - 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 69fdce01..169a5332 100644 --- a/attachments/19_vertex_buffer.cpp +++ b/attachments/19_vertex_buffer.cpp @@ -1,32 +1,31 @@ -#include -#include -#include -#include -#include -#include -#include #include -#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include #if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) -#include +# include #else import vulkan_hpp; #endif -#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" -}; +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -34,649 +33,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); + glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); + + 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().shaderDrawParameters && + 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 required features (Vulkan 1.1 and 1.3) + vk::StructureChain featureChain = { + {}, // vk::PhysicalDeviceFeatures2 + {.shaderDrawParameters = true}, // vk::PhysicalDeviceVulkan11Features + {.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() + { + assert(swapChainImageViews.empty()); + + 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::StructureChain pipelineCreateInfoChain = { + {.stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}, + {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format}}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); + } + + 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::eColorAttachmentOutput, // 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]); + + try + { + 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!"); + } + } + catch (const vk::SystemError &e) + { + if (e.code().value() == static_cast(vk::Result::eErrorOutOfDateKHR)) + { + recreateSwapChain(); + return; + } + else + { + throw; + } + } + 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); - glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); - - 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().shaderDrawParameters && - 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 required features (Vulkan 1.1 and 1.3) - vk::StructureChain featureChain = { - {}, // vk::PhysicalDeviceFeatures2 - { .shaderDrawParameters = true }, // vk::PhysicalDeviceVulkan11Features - { .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() { - assert(swapChainImageViews.empty()); - - 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::StructureChain pipelineCreateInfoChain = { - {.stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = pipelineLayout, - .renderPass = nullptr }, - {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format } - }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); - } - - 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::eColorAttachmentOutput, // 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]); - - - try { - 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!"); - } - } catch (const vk::SystemError& e) { - if (e.code().value() == static_cast(vk::Result::eErrorOutOfDateKHR)) { - recreateSwapChain(); - return; - } else { - throw; - } - } - 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 b48c46fb..0f8bd493 100644 --- a/attachments/20_staging_buffer.cpp +++ b/attachments/20_staging_buffer.cpp @@ -1,32 +1,31 @@ -#include -#include -#include -#include -#include -#include -#include #include -#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include #if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) -#include +# include #else import vulkan_hpp; #endif -#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" -}; +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -34,668 +33,706 @@ 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); + glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); + + 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().shaderDrawParameters && + 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 required features (Vulkan 1.1 and 1.3) + vk::StructureChain featureChain = { + {}, // vk::PhysicalDeviceFeatures2 + {.shaderDrawParameters = true}, // vk::PhysicalDeviceVulkan11Features + {.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() + { + assert(swapChainImageViews.empty()); + + 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::StructureChain pipelineCreateInfoChain = { + {.stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}, + {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format}}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); + } + + 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::eColorAttachmentOutput, // 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]); + + try + { + 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!"); + } + } + catch (const vk::SystemError &e) + { + if (e.code().value() == static_cast(vk::Result::eErrorOutOfDateKHR)) + { + recreateSwapChain(); + return; + } + else + { + throw; + } + } + 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); - glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); - - 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().shaderDrawParameters && - 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 required features (Vulkan 1.1 and 1.3) - vk::StructureChain featureChain = { - {}, // vk::PhysicalDeviceFeatures2 - { .shaderDrawParameters = true }, // vk::PhysicalDeviceVulkan11Features - { .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() { - assert(swapChainImageViews.empty()); - - 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::StructureChain pipelineCreateInfoChain = { - {.stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = pipelineLayout, - .renderPass = nullptr }, - {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format } - }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); - } - - 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::eColorAttachmentOutput, // 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]); - - - try { - 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!"); - } - } catch (const vk::SystemError& e) { - if (e.code().value() == static_cast(vk::Result::eErrorOutOfDateKHR)) { - recreateSwapChain(); - return; - } else { - throw; - } - } - 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 ddea42c3..1b589051 100644 --- a/attachments/21_index_buffer.cpp +++ b/attachments/21_index_buffer.cpp @@ -1,32 +1,31 @@ -#include -#include -#include -#include -#include -#include -#include #include -#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include #if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) -#include +# include #else import vulkan_hpp; #endif -#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" -}; +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -34,693 +33,732 @@ 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); + glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); + + 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().shaderDrawParameters && + 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 required features (Vulkan 1.1 and 1.3) + vk::StructureChain featureChain = { + {}, // vk::PhysicalDeviceFeatures2 + {.shaderDrawParameters = true}, // vk::PhysicalDeviceVulkan11Features + {.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() + { + assert(swapChainImageViews.empty()); + + 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::StructureChain pipelineCreateInfoChain = { + {.stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}, + {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format}}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); + } + + 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::eColorAttachmentOutput, // 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]); + + try + { + 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!"); + } + } + catch (const vk::SystemError &e) + { + if (e.code().value() == static_cast(vk::Result::eErrorOutOfDateKHR)) + { + recreateSwapChain(); + return; + } + else + { + throw; + } + } + 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); - glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); - - 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().shaderDrawParameters && - 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 required features (Vulkan 1.1 and 1.3) - vk::StructureChain featureChain = { - {}, // vk::PhysicalDeviceFeatures2 - { .shaderDrawParameters = true }, // vk::PhysicalDeviceVulkan11Features - { .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() { - assert(swapChainImageViews.empty()); - - 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::StructureChain pipelineCreateInfoChain = { - {.stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = pipelineLayout, - .renderPass = nullptr }, - {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format } - }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); - } - - 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::eColorAttachmentOutput, // 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]); - - - try { - 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!"); - } - } catch (const vk::SystemError& e) { - if (e.code().value() == static_cast(vk::Result::eErrorOutOfDateKHR)) { - recreateSwapChain(); - return; - } else { - throw; - } - } - 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 c3ca36fc..9e93618d 100644 --- a/attachments/22_descriptor_layout.cpp +++ b/attachments/22_descriptor_layout.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 +#include #if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) -#include +# include #else import vulkan_hpp; #endif -#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" -}; +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -38,744 +37,788 @@ 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); + glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); + + 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().shaderDrawParameters && + 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 required features (Vulkan 1.1 and 1.3) + vk::StructureChain featureChain = { + {}, // vk::PhysicalDeviceFeatures2 + {.shaderDrawParameters = true}, // vk::PhysicalDeviceVulkan11Features + {.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() + { + assert(swapChainImageViews.empty()); + + 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::StructureChain pipelineCreateInfoChain = { + {.stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}, + {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format}}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); + } + + 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::eColorAttachmentOutput, // 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]); + + try + { + 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!"); + } + } + catch (const vk::SystemError &e) + { + if (e.code().value() == static_cast(vk::Result::eErrorOutOfDateKHR)) + { + recreateSwapChain(); + return; + } + else + { + throw; + } + } + 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); - glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); - - 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().shaderDrawParameters && - 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 required features (Vulkan 1.1 and 1.3) - vk::StructureChain featureChain = { - {}, // vk::PhysicalDeviceFeatures2 - { .shaderDrawParameters = true }, // vk::PhysicalDeviceVulkan11Features - { .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() { - assert(swapChainImageViews.empty()); - - 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::StructureChain pipelineCreateInfoChain = { - {.stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = pipelineLayout, - .renderPass = nullptr }, - {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format } - }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); - } - - 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::eColorAttachmentOutput, // 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]); - - - try { - 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!"); - } - } catch (const vk::SystemError& e) { - if (e.code().value() == static_cast(vk::Result::eErrorOutOfDateKHR)) { - recreateSwapChain(); - return; - } else { - throw; - } - } - 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 2205dbb6..0bb63cd0 100644 --- a/attachments/23_descriptor_sets.cpp +++ b/attachments/23_descriptor_sets.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 +#include #if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) -#include +# include #else import vulkan_hpp; #endif -#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" -}; +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -38,769 +37,816 @@ 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::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); + glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); + + 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().shaderDrawParameters && + 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 required features (Vulkan 1.1 and 1.3) + vk::StructureChain featureChain = { + {}, // vk::PhysicalDeviceFeatures2 + {.shaderDrawParameters = true}, // vk::PhysicalDeviceVulkan11Features + {.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() + { + assert(swapChainImageViews.empty()); + + 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::StructureChain pipelineCreateInfoChain = { + {.stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}, + {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format}}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); + } + + 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::eColorAttachmentOutput, // 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]); + + try + { + 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!"); + } + } + catch (const vk::SystemError &e) + { + if (e.code().value() == static_cast(vk::Result::eErrorOutOfDateKHR)) + { + recreateSwapChain(); + return; + } + else + { + throw; + } + } + 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); - glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); - - 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().shaderDrawParameters && - 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 required features (Vulkan 1.1 and 1.3) - vk::StructureChain featureChain = { - {}, // vk::PhysicalDeviceFeatures2 - { .shaderDrawParameters = true }, // vk::PhysicalDeviceVulkan11Features - { .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() { - assert(swapChainImageViews.empty()); - - 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::StructureChain pipelineCreateInfoChain = { - {.stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = pipelineLayout, - .renderPass = nullptr }, - {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format } - }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); - } - - 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::eColorAttachmentOutput, // 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]); - - - try { - 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!"); - } - } catch (const vk::SystemError& e) { - if (e.code().value() == static_cast(vk::Result::eErrorOutOfDateKHR)) { - recreateSwapChain(); - return; - } else { - throw; - } - } - 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 f0e19e26..066e9e07 100644 --- a/attachments/24_texture_image.cpp +++ b/attachments/24_texture_image.cpp @@ -1,23 +1,23 @@ -#include -#include -#include -#include -#include -#include -#include #include -#include #include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include #if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) -#include +# include #else import vulkan_hpp; #endif -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include #define GLM_FORCE_RADIANS @@ -27,13 +27,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" -}; +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -41,870 +40,922 @@ 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::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); + glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); + + 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().shaderDrawParameters && + 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 required features (Vulkan 1.1 and 1.3) + vk::StructureChain featureChain = { + {}, // vk::PhysicalDeviceFeatures2 + {.shaderDrawParameters = true}, // vk::PhysicalDeviceVulkan11Features + {.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() + { + assert(swapChainImageViews.empty()); + + 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::StructureChain pipelineCreateInfoChain = { + {.stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}, + {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format}}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); + } + + 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::eColorAttachmentOutput, // 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]); + + try + { + 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!"); + } + } + catch (const vk::SystemError &e) + { + if (e.code().value() == static_cast(vk::Result::eErrorOutOfDateKHR)) + { + recreateSwapChain(); + return; + } + else + { + throw; + } + } + 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); - glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); - - 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().shaderDrawParameters && - 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 required features (Vulkan 1.1 and 1.3) - vk::StructureChain featureChain = { - {}, // vk::PhysicalDeviceFeatures2 - { .shaderDrawParameters = true }, // vk::PhysicalDeviceVulkan11Features - { .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() { - assert(swapChainImageViews.empty()); - - 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::StructureChain pipelineCreateInfoChain = { - {.stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = pipelineLayout, - .renderPass = nullptr }, - {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format } - }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); - } - - 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::eColorAttachmentOutput, // 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]); - - - try { - 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!"); - } - } catch (const vk::SystemError& e) { - if (e.code().value() == static_cast(vk::Result::eErrorOutOfDateKHR)) { - recreateSwapChain(); - return; - } else { - throw; - } - } - 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 6336f6cf..7e644e2d 100644 --- a/attachments/25_sampler.cpp +++ b/attachments/25_sampler.cpp @@ -1,23 +1,23 @@ -#include -#include -#include -#include -#include -#include -#include #include -#include #include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include #if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) -#include +# include #else import vulkan_hpp; #endif -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include #define GLM_FORCE_RADIANS @@ -27,13 +27,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" -}; +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -41,903 +40,958 @@ 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::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); + glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); + + 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() + { + assert(swapChainImageViews.empty()); + + 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::StructureChain pipelineCreateInfoChain = { + {.stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}, + {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format}}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); + } + + 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::eColorAttachmentOutput, // 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]); + + try + { + 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!"); + } + } + catch (const vk::SystemError &e) + { + if (e.code().value() == static_cast(vk::Result::eErrorOutOfDateKHR)) + { + recreateSwapChain(); + return; + } + else + { + throw; + } + } + 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); - glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); - - 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() { - assert(swapChainImageViews.empty()); - - 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::StructureChain pipelineCreateInfoChain = { - {.stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = pipelineLayout, - .renderPass = nullptr }, - {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format } - }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); - } - - 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::eColorAttachmentOutput, // 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]); - - - try { - 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!"); - } - } catch (const vk::SystemError& e) { - if (e.code().value() == static_cast(vk::Result::eErrorOutOfDateKHR)) { - recreateSwapChain(); - return; - } else { - throw; - } - } - 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 baef7d45..ea516cd1 100644 --- a/attachments/26_texture_mapping.cpp +++ b/attachments/26_texture_mapping.cpp @@ -1,23 +1,23 @@ -#include -#include -#include -#include -#include -#include -#include #include -#include #include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include #if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) -#include +# include #else import vulkan_hpp; #endif -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include #define GLM_FORCE_RADIANS @@ -27,13 +27,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" -}; +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -41,980 +40,1023 @@ 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 { - 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}, {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); + glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); + + 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() + { + assert(swapChainImageViews.empty()); + + 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::StructureChain pipelineCreateInfoChain = { + {.stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}, + {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format}}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); + } + + 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::eColorAttachmentOutput, // 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]); + + try + { + 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!"); + } + } + catch (const vk::SystemError &e) + { + if (e.code().value() == static_cast(vk::Result::eErrorOutOfDateKHR)) + { + recreateSwapChain(); + return; + } + else + { + throw; + } + } + 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); - glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); - - 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() { - assert(swapChainImageViews.empty()); - - 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::StructureChain pipelineCreateInfoChain = { - {.stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = pipelineLayout, - .renderPass = nullptr }, - {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format } - }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); - } - - 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::eColorAttachmentOutput, // 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]); - - - try { - 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!"); - } - } catch (const vk::SystemError& e) { - if (e.code().value() == static_cast(vk::Result::eErrorOutOfDateKHR)) { - recreateSwapChain(); - return; - } else { - throw; - } - } - 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 a1d0fda1..98a46715 100644 --- a/attachments/27_depth_buffering.cpp +++ b/attachments/27_depth_buffering.cpp @@ -1,23 +1,23 @@ -#include -#include -#include -#include -#include -#include -#include #include -#include #include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include #if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) -#include +# include #else import vulkan_hpp; #endif -#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" -}; +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -42,28 +41,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 { - glm::mat4 model; - glm::mat4 view; - glm::mat4 proj; +struct UniformBufferObject +{ + glm::mat4 model; + glm::mat4 view; + glm::mat4 proj; }; const std::vector vertices = { @@ -75,1053 +77,1089 @@ 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); + glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); + + 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() + { + assert(swapChainImageViews.empty()); + + 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::StructureChain pipelineCreateInfoChain = { + {.stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pDepthStencilState = &depthStencil, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}, + {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format, .depthAttachmentFormat = depthFormat}}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); + } + + 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( + swapChainImages[imageIndex], + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // dstStage + vk::ImageAspectFlagBits::eColor); + // Transition depth image to depth attachment optimal layout + transition_image_layout( + *depthImage, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eDepthAttachmentOptimal, + vk::AccessFlagBits2::eDepthStencilAttachmentWrite, + vk::AccessFlagBits2::eDepthStencilAttachmentWrite, + vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, + vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, + vk::ImageAspectFlagBits::eDepth); + + 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::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 = &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( + swapChainImages[imageIndex], + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe, // dstStage + vk::ImageAspectFlagBits::eColor); + commandBuffers[currentFrame].end(); + } + + void transition_image_layout( + vk::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 image_aspect_flags) + { + 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 = image_aspect_flags, + .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]); + + try + { + 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!"); + } + } + catch (const vk::SystemError &e) + { + if (e.code().value() == static_cast(vk::Result::eErrorOutOfDateKHR)) + { + recreateSwapChain(); + return; + } + else + { + throw; + } + } + 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); - glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); - - 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() { - assert(swapChainImageViews.empty()); - - 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::StructureChain pipelineCreateInfoChain = { - {.stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pDepthStencilState = &depthStencil, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = pipelineLayout, - .renderPass = nullptr }, - {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format, .depthAttachmentFormat = depthFormat } - }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); - } - - 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( - swapChainImages[imageIndex], - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // dstStage - vk::ImageAspectFlagBits::eColor - ); - // Transition depth image to depth attachment optimal layout - transition_image_layout( - *depthImage, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eDepthAttachmentOptimal, - vk::AccessFlagBits2::eDepthStencilAttachmentWrite, - vk::AccessFlagBits2::eDepthStencilAttachmentWrite, - vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, - vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, - vk::ImageAspectFlagBits::eDepth - ); - - 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::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 = &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( - swapChainImages[imageIndex], - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe, // dstStage - vk::ImageAspectFlagBits::eColor - ); - commandBuffers[currentFrame].end(); - } - - void transition_image_layout( - vk::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 image_aspect_flags - ) { - 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 = image_aspect_flags, - .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]); - - - try { - 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!"); - } - } catch (const vk::SystemError& e) { - if (e.code().value() == static_cast(vk::Result::eErrorOutOfDateKHR)) { - recreateSwapChain(); - return; - } else { - throw; - } - } - 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 df77c8a6..6a9f0ed1 100644 --- a/attachments/28_model_loading.cpp +++ b/attachments/28_model_loading.cpp @@ -1,23 +1,23 @@ -#include -#include -#include -#include -#include -#include -#include #include -#include #include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include #if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) -#include +# include #else import vulkan_hpp; #endif -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include #define GLM_FORCE_RADIANS @@ -33,15 +33,14 @@ import vulkan_hpp; #define TINYOBJLOADER_IMPLEMENTATION #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -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; +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" -}; +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -49,1123 +48,1174 @@ 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); - glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); - - 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() { - assert(swapChainImageViews.empty()); - - 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::StructureChain pipelineCreateInfoChain = { - {.stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pDepthStencilState = &depthStencil, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = pipelineLayout, - .renderPass = nullptr }, - {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format, .depthAttachmentFormat = depthFormat } - }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); - } - - 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( - swapChainImages[imageIndex], - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // dstStage - vk::ImageAspectFlagBits::eColor - ); - // Transition depth image to depth attachment optimal layout - transition_image_layout( - *depthImage, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eDepthAttachmentOptimal, - vk::AccessFlagBits2::eDepthStencilAttachmentWrite, - vk::AccessFlagBits2::eDepthStencilAttachmentWrite, - vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, - vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, - vk::ImageAspectFlagBits::eDepth - ); - - 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( - swapChainImages[imageIndex], - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe, // dstStage - vk::ImageAspectFlagBits::eColor - ); - commandBuffers[currentFrame].end(); - } - - void transition_image_layout( - vk::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 image_aspect_flags - ) { - 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 = image_aspect_flags, - .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]); - - - try { - 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!"); - } - } catch (const vk::SystemError& e) { - if (e.code().value() == static_cast(vk::Result::eErrorOutOfDateKHR)) { - recreateSwapChain(); - return; - } else { - throw; - } - } - 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); + glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); + + 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() + { + assert(swapChainImageViews.empty()); + + 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::StructureChain pipelineCreateInfoChain = { + {.stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pDepthStencilState = &depthStencil, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}, + {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format, .depthAttachmentFormat = depthFormat}}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); + } + + 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( + swapChainImages[imageIndex], + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // dstStage + vk::ImageAspectFlagBits::eColor); + // Transition depth image to depth attachment optimal layout + transition_image_layout( + *depthImage, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eDepthAttachmentOptimal, + vk::AccessFlagBits2::eDepthStencilAttachmentWrite, + vk::AccessFlagBits2::eDepthStencilAttachmentWrite, + vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, + vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, + vk::ImageAspectFlagBits::eDepth); + + 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( + swapChainImages[imageIndex], + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe, // dstStage + vk::ImageAspectFlagBits::eColor); + commandBuffers[currentFrame].end(); + } + + void transition_image_layout( + vk::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 image_aspect_flags) + { + 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 = image_aspect_flags, + .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]); + + try + { + 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!"); + } + } + catch (const vk::SystemError &e) + { + if (e.code().value() == static_cast(vk::Result::eErrorOutOfDateKHR)) + { + recreateSwapChain(); + return; + } + else + { + throw; + } + } + 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 d9d3264d..798ac1ba 100644 --- a/attachments/29_mipmapping.cpp +++ b/attachments/29_mipmapping.cpp @@ -1,23 +1,23 @@ -#include -#include -#include -#include -#include -#include -#include #include -#include #include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include #if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) -#include +# include #else import vulkan_hpp; #endif -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include #define GLM_FORCE_RADIANS @@ -33,15 +33,14 @@ import vulkan_hpp; #define TINYOBJLOADER_IMPLEMENTATION #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -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; +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" -}; +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -49,1189 +48,1242 @@ 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); - glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); - - 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); + glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); + + 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() { - assert(swapChainImageViews.empty()); - - 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::StructureChain pipelineCreateInfoChain = { - {.stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pDepthStencilState = &depthStencil, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = pipelineLayout, - .renderPass = nullptr }, - {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format, .depthAttachmentFormat = depthFormat } - }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); - } - - 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( - swapChainImages[imageIndex], - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // dstStage - vk::ImageAspectFlagBits::eColor - ); - // Transition depth image to depth attachment optimal layout - transition_image_layout( - *depthImage, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eDepthAttachmentOptimal, - vk::AccessFlagBits2::eDepthStencilAttachmentWrite, - vk::AccessFlagBits2::eDepthStencilAttachmentWrite, - vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, - vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, - vk::ImageAspectFlagBits::eDepth - ); - - 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( - swapChainImages[imageIndex], - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe, // dstStage - vk::ImageAspectFlagBits::eColor - ); - commandBuffers[currentFrame].end(); - } - - void transition_image_layout( - vk::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 image_aspect_flags - ) { - 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 = image_aspect_flags, - .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]); - - - try { - 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!"); - } - } catch (const vk::SystemError& e) { - if (e.code().value() == static_cast(vk::Result::eErrorOutOfDateKHR)) { - recreateSwapChain(); - return; - } else { - throw; - } - } - 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() + { + assert(swapChainImageViews.empty()); + + 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::StructureChain pipelineCreateInfoChain = { + {.stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pDepthStencilState = &depthStencil, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}, + {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format, .depthAttachmentFormat = depthFormat}}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); + } + + 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( + swapChainImages[imageIndex], + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // dstStage + vk::ImageAspectFlagBits::eColor); + // Transition depth image to depth attachment optimal layout + transition_image_layout( + *depthImage, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eDepthAttachmentOptimal, + vk::AccessFlagBits2::eDepthStencilAttachmentWrite, + vk::AccessFlagBits2::eDepthStencilAttachmentWrite, + vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, + vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, + vk::ImageAspectFlagBits::eDepth); + + 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( + swapChainImages[imageIndex], + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe, // dstStage + vk::ImageAspectFlagBits::eColor); + commandBuffers[currentFrame].end(); + } + + void transition_image_layout( + vk::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 image_aspect_flags) + { + 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 = image_aspect_flags, + .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]); + + try + { + 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!"); + } + } + catch (const vk::SystemError &e) + { + if (e.code().value() == static_cast(vk::Result::eErrorOutOfDateKHR)) + { + recreateSwapChain(); + return; + } + else + { + throw; + } + } + 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 c3f4da7f..6f5262c5 100644 --- a/attachments/30_multisampling.cpp +++ b/attachments/30_multisampling.cpp @@ -1,23 +1,23 @@ -#include -#include -#include -#include -#include -#include -#include #include -#include #include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include #if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) -#include +# include #else import vulkan_hpp; #endif -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include #define GLM_FORCE_RADIANS @@ -33,15 +33,14 @@ import vulkan_hpp; #define TINYOBJLOADER_IMPLEMENTATION #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -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; +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" -}; +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -49,1237 +48,1309 @@ 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); - glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); - - 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); + glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); + + 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() { - assert(swapChainImageViews.empty()); - - 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::StructureChain pipelineCreateInfoChain = { - {.stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pDepthStencilState = &depthStencil, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = pipelineLayout, - .renderPass = nullptr }, - {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format, .depthAttachmentFormat = depthFormat } - }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); - } - - 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 swapchain image to COLOR_ATTACHMENT_OPTIMAL - transition_image_layout( - swapChainImages[imageIndex], - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // dstStage - vk::ImageAspectFlagBits::eColor - ); - // Transition the multisampled color image to COLOR_ATTACHMENT_OPTIMAL - transition_image_layout( - *colorImage, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - vk::AccessFlagBits2::eColorAttachmentWrite, - vk::AccessFlagBits2::eColorAttachmentWrite, - vk::PipelineStageFlagBits2::eColorAttachmentOutput, - vk::PipelineStageFlagBits2::eColorAttachmentOutput, - vk::ImageAspectFlagBits::eColor - ); - // Transition the depth image to DEPTH_ATTACHMENT_OPTIMAL - transition_image_layout( - *depthImage, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eDepthAttachmentOptimal, - vk::AccessFlagBits2::eDepthStencilAttachmentWrite, - vk::AccessFlagBits2::eDepthStencilAttachmentWrite, - vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, - vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, - 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 swapchain image to PRESENT_SRC - transition_image_layout( - swapChainImages[imageIndex], - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe, // dstStage - vk::ImageAspectFlagBits::eColor - ); - commandBuffers[currentFrame].end(); - } - - void transition_image_layout( - vk::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 image_aspect_flags - ) { - 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 = image_aspect_flags, - .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]); - - - try { - 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!"); - } - } catch (const vk::SystemError& e) { - if (e.code().value() == static_cast(vk::Result::eErrorOutOfDateKHR)) { - recreateSwapChain(); - return; - } else { - throw; - } - } - 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() + { + assert(swapChainImageViews.empty()); + + 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::StructureChain pipelineCreateInfoChain = { + {.stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pDepthStencilState = &depthStencil, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}, + {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format, .depthAttachmentFormat = depthFormat}}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); + } + + 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 swapchain image to COLOR_ATTACHMENT_OPTIMAL + transition_image_layout( + swapChainImages[imageIndex], + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // dstStage + vk::ImageAspectFlagBits::eColor); + // Transition the multisampled color image to COLOR_ATTACHMENT_OPTIMAL + transition_image_layout( + *colorImage, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + vk::AccessFlagBits2::eColorAttachmentWrite, + vk::AccessFlagBits2::eColorAttachmentWrite, + vk::PipelineStageFlagBits2::eColorAttachmentOutput, + vk::PipelineStageFlagBits2::eColorAttachmentOutput, + vk::ImageAspectFlagBits::eColor); + // Transition the depth image to DEPTH_ATTACHMENT_OPTIMAL + transition_image_layout( + *depthImage, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eDepthAttachmentOptimal, + vk::AccessFlagBits2::eDepthStencilAttachmentWrite, + vk::AccessFlagBits2::eDepthStencilAttachmentWrite, + vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, + vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, + 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 swapchain image to PRESENT_SRC + transition_image_layout( + swapChainImages[imageIndex], + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe, // dstStage + vk::ImageAspectFlagBits::eColor); + commandBuffers[currentFrame].end(); + } + + void transition_image_layout( + vk::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 image_aspect_flags) + { + 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 = image_aspect_flags, + .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]); + + try + { + 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!"); + } + } + catch (const vk::SystemError &e) + { + if (e.code().value() == static_cast(vk::Result::eErrorOutOfDateKHR)) + { + recreateSwapChain(); + return; + } + else + { + throw; + } + } + 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 6abefadb..9afe1fa3 100644 --- a/attachments/31_compute_shader.cpp +++ b/attachments/31_compute_shader.cpp @@ -1,41 +1,40 @@ // 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 +#include +#include #if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) -#include +# include #else import vulkan_hpp; #endif -#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 uint32_t PARTICLE_COUNT = 8192; -constexpr int MAX_FRAMES_IN_FLIGHT = 2; +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; +constexpr uint32_t PARTICLE_COUNT = 8192; +constexpr int MAX_FRAMES_IN_FLIGHT = 2; -const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -43,929 +42,981 @@ 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); - glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); - - 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); + glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); + + 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() { - assert(swapChainImageViews.empty()); - - 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::StructureChain pipelineCreateInfoChain = { - {.stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = pipelineLayout, - .renderPass = nullptr }, - {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format } - }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); - } - - 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::eColorAttachmentOutput, // 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 + 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() + { + assert(swapChainImageViews.empty()); + + 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::StructureChain pipelineCreateInfoChain = { + {.stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}, + {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format}}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); + } + + 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}, }; - 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 - }; - - try { - 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!"); - } - } catch (const vk::SystemError& e) { - if (e.code().value() == static_cast(vk::Result::eErrorOutOfDateKHR)) { - recreateSwapChain(); - return; - } else { - throw; - } - } - } - 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::eColorAttachmentOutput, // 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}; + + try + { + 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!"); + } + } + catch (const vk::SystemError &e) + { + if (e.code().value() == static_cast(vk::Result::eErrorOutOfDateKHR)) + { + recreateSwapChain(); + return; + } + else + { + throw; + } + } + } + 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 7dd3c3cb..a57dfd55 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 +#include +#include #if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) -#include +# include #else import vulkan_hpp; #endif -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include #define GLM_FORCE_RADIANS @@ -34,1677 +34,1789 @@ import vulkan_hpp; #define TINYOBJLOADER_IMPLEMENTATION #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -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; +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); - glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); - - 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() { - assert(swapChainImageViews.empty()); - - 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::StructureChain pipelineCreateInfoChain = { - {.stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pDepthStencilState = &depthStencil, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = pipelineLayout, - .renderPass = nullptr }, - {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format, .depthAttachmentFormat = findDepthFormat() } - }; - - if (appInfo.dynamicRenderingSupported) { - std::cout << "Configuring pipeline for dynamic rendering\n"; - } - else - { - std::cout << "Configuring pipeline for traditional render pass\n"; - pipelineCreateInfoChain.unlink(); - pipelineCreateInfoChain.get().renderPass = *renderPass; - } - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); - } - - 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::eColorAttachmentOutput, - .srcAccessMask = vk::AccessFlagBits2::eColorAttachmentWrite, - .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::eDepthStencilAttachmentWrite, - .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::eColorAttachmentOutput, - .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, 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); - - 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]); - } - - try { - 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!"); - } - } catch (const vk::SystemError& e) { - if (e.code().value() == static_cast(vk::Result::eErrorOutOfDateKHR)) { - recreateSwapChain(); - return; - } else { - throw; - } - } - 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); + glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); + + 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() + { + assert(swapChainImageViews.empty()); + + 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::StructureChain pipelineCreateInfoChain = { + {.stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pDepthStencilState = &depthStencil, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}, + {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format, .depthAttachmentFormat = findDepthFormat()}}; + + if (appInfo.dynamicRenderingSupported) + { + std::cout << "Configuring pipeline for dynamic rendering\n"; + } + else + { + std::cout << "Configuring pipeline for traditional render pass\n"; + pipelineCreateInfoChain.unlink(); + pipelineCreateInfoChain.get().renderPass = *renderPass; + } + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); + } + + 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::eColorAttachmentOutput, + .srcAccessMask = vk::AccessFlagBits2::eColorAttachmentWrite, + .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::eDepthStencilAttachmentWrite, + .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::eColorAttachmentOutput, + .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, 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); + + 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]); + } + + try + { + 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!"); + } + } + catch (const vk::SystemError &e) + { + if (e.code().value() == static_cast(vk::Result::eErrorOutOfDateKHR)) + { + recreateSwapChain(); + return; + } + else + { + throw; + } + } + 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 e62a79a2..c2d3d60c 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 +#include +#include #if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) -#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,1699 +35,1765 @@ import vulkan_hpp; #define TINYOBJLOADER_IMPLEMENTATION #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -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; +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); - glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); - - 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() { - swapChainFramebuffers.clear(); - swapChainImageViews.clear(); - - // Semaphores tied to swapchain image indices need to be rebuilt on resize - presentCompleteSemaphore.clear(); - for (auto& imageView : swapChainImageViews) { - imageView = nullptr; - } - - 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(); - - // Recreate traditional render pass and framebuffers if not using profiles - if (!appInfo.profileSupported) { - createRenderPass(); - createFramebuffers(); - } - - createColorResources(); - createDepthResources(); - - // Recreate per-swapchain-image present semaphores after resize - presentCompleteSemaphore.reserve(swapChainImages.size()); - vk::SemaphoreCreateInfo semaphoreInfo{}; - for (size_t i = 0; i < swapChainImages.size(); ++i) { - presentCompleteSemaphore.push_back(device.createSemaphore(semaphoreInfo)); - } - } - - 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); + glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); + + 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() + { + swapChainFramebuffers.clear(); + swapChainImageViews.clear(); + + // Semaphores tied to swapchain image indices need to be rebuilt on resize + presentCompleteSemaphore.clear(); + for (auto &imageView : swapChainImageViews) + { + imageView = nullptr; + } + + 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(); + + // Recreate traditional render pass and framebuffers if not using profiles + if (!appInfo.profileSupported) + { + createRenderPass(); + createFramebuffers(); + } + + createColorResources(); + createDepthResources(); + + // Recreate per-swapchain-image present semaphores after resize + presentCompleteSemaphore.reserve(swapChainImages.size()); + vk::SemaphoreCreateInfo semaphoreInfo{}; + for (size_t i = 0; i < swapChainImages.size(); ++i) + { + presentCompleteSemaphore.push_back(device.createSemaphore(semaphoreInfo)); + } + } + + 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() { - assert(swapChainImageViews.empty()); - 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); - - // Configure pipeline based on whether we're using the KHR roadmap 2022 profile - // With the KHR roadmap 2022 profile, we can use dynamic rendering - vk::StructureChain pipelineCreateInfoChain = { - {.stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pDepthStencilState = &depthStencil, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = pipelineLayout, - .renderPass = nullptr }, - {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format, .depthAttachmentFormat = findDepthFormat() } - }; - - if (appInfo.profileSupported) { - std::cout << "Creating pipeline with dynamic rendering (KHR roadmap 2022 profile)" << std::endl; - } - else - { - std::cout << "Creating pipeline with traditional render pass (fallback)" << std::endl; - pipelineCreateInfoChain.unlink(); - pipelineCreateInfoChain.get().renderPass = *renderPass; - } - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); - } - - 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 attachments to the correct layouts for dynamic rendering - - // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL - // 1) Multisampled color attachment image -> ColorAttachmentOptimal - transition_image_layout( - *colorImage, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - vk::AccessFlagBits2::eColorAttachmentWrite, - vk::AccessFlagBits2::eColorAttachmentWrite, - vk::PipelineStageFlagBits2::eColorAttachmentOutput, - vk::PipelineStageFlagBits2::eColorAttachmentOutput, - vk::ImageAspectFlagBits::eColor - ); - // 2) Depth attachment image -> DepthStencilAttachmentOptimal - transition_image_layout( - *depthImage, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eDepthAttachmentOptimal, - vk::AccessFlagBits2::eDepthStencilAttachmentWrite, - vk::AccessFlagBits2::eDepthStencilAttachmentWrite, - vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, - vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, - vk::ImageAspectFlagBits::eDepth - ); - // 3) Resolve (swapchain) image -> ColorAttachmentOptimal - transition_image_layout( - swapChainImages[imageIndex], - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // dstStage - vk::ImageAspectFlagBits::eColor - ); - - // 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 - transition_image_layout( - swapChainImages[imageIndex], - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe, // dstStage - vk::ImageAspectFlagBits::eColor - ); - } else { - commandBuffers[currentFrame].endRenderPass(); - // Traditional render pass already transitions the image to the correct layout - } - - commandBuffers[currentFrame].end(); - } - - void transition_image_layout( - vk::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 image_aspect_flags - ) { - 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 = image_aspect_flags, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1 - } - }; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier - }; - commandBuffers[currentFrame].pipelineBarrier2(dependency_info); - } - - 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, UINT64_MAX)); - - uint32_t imageIndex; - try { - auto [result, idx] = swapChain.acquireNextImage(UINT64_MAX, *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]); - - try { - const vk::PresentInfoKHR presentInfoKHR{ - .waitSemaphoreCount = 1, - .pWaitSemaphores = &*presentCompleteSemaphore[imageIndex], - .swapchainCount = 1, - .pSwapchains = &*swapChain, - .pImageIndices = &imageIndex - }; - auto 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!"); - } - } catch (const vk::SystemError& e) { - if (e.code().value() == static_cast(vk::Result::eErrorOutOfDateKHR)) { - recreateSwapChain(); - return; - } else { - throw; - } - } - 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() + { + assert(swapChainImageViews.empty()); + 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); + + // Configure pipeline based on whether we're using the KHR roadmap 2022 profile + // With the KHR roadmap 2022 profile, we can use dynamic rendering + vk::StructureChain pipelineCreateInfoChain = { + {.stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pDepthStencilState = &depthStencil, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr}, + {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format, .depthAttachmentFormat = findDepthFormat()}}; + + if (appInfo.profileSupported) + { + std::cout << "Creating pipeline with dynamic rendering (KHR roadmap 2022 profile)" << std::endl; + } + else + { + std::cout << "Creating pipeline with traditional render pass (fallback)" << std::endl; + pipelineCreateInfoChain.unlink(); + pipelineCreateInfoChain.get().renderPass = *renderPass; + } + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); + } + + 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 attachments to the correct layouts for dynamic rendering + + // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL + // 1) Multisampled color attachment image -> ColorAttachmentOptimal + transition_image_layout( + *colorImage, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + vk::AccessFlagBits2::eColorAttachmentWrite, + vk::AccessFlagBits2::eColorAttachmentWrite, + vk::PipelineStageFlagBits2::eColorAttachmentOutput, + vk::PipelineStageFlagBits2::eColorAttachmentOutput, + vk::ImageAspectFlagBits::eColor); + // 2) Depth attachment image -> DepthStencilAttachmentOptimal + transition_image_layout( + *depthImage, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eDepthAttachmentOptimal, + vk::AccessFlagBits2::eDepthStencilAttachmentWrite, + vk::AccessFlagBits2::eDepthStencilAttachmentWrite, + vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, + vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, + vk::ImageAspectFlagBits::eDepth); + // 3) Resolve (swapchain) image -> ColorAttachmentOptimal + transition_image_layout( + swapChainImages[imageIndex], + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // dstStage + vk::ImageAspectFlagBits::eColor); + + // 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 + transition_image_layout( + swapChainImages[imageIndex], + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe, // dstStage + vk::ImageAspectFlagBits::eColor); + } + else + { + commandBuffers[currentFrame].endRenderPass(); + // Traditional render pass already transitions the image to the correct layout + } + + commandBuffers[currentFrame].end(); + } + + void transition_image_layout( + vk::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 image_aspect_flags) + { + 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 = image_aspect_flags, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; + vk::DependencyInfo dependency_info = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier}; + commandBuffers[currentFrame].pipelineBarrier2(dependency_info); + } + + 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, UINT64_MAX)); + + uint32_t imageIndex; + try + { + auto [result, idx] = swapChain.acquireNextImage(UINT64_MAX, *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]); + + try + { + const vk::PresentInfoKHR presentInfoKHR{ + .waitSemaphoreCount = 1, + .pWaitSemaphores = &*presentCompleteSemaphore[imageIndex], + .swapchainCount = 1, + .pSwapchains = &*swapChain, + .pImageIndices = &imageIndex}; + auto 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!"); + } + } + catch (const vk::SystemError &e) + { + if (e.code().value() == static_cast(vk::Result::eErrorOutOfDateKHR)) + { + recreateSwapChain(); + return; + } + else + { + throw; + } + } + 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 7db4d44b..76e67937 100644 --- a/attachments/34_android.cpp +++ b/attachments/34_android.cpp @@ -1,36 +1,37 @@ -#include -#include -#include -#include -#include -#include -#include #include -#include #include +#include #include +#include +#include +#include +#include +#include +#include #include -#include +#include +#include #if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) -#include +# include #else import vulkan_hpp; #endif +//clang-format off #if defined(__ANDROID__) -#include -#include +# include +# include #endif +//clang-format on #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 +40,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,1655 +91,1701 @@ import vulkan_hpp; #include #include -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -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; +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() { - assert(swapChainImageViews.empty()); - 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() + { + assert(swapChainImageViews.empty()); + 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(swapChainImages.size()); - 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)); - inFlightFences.push_back(device.createFence(fenceInfo)); - } - - for (size_t i = 0; i < swapChainImages.size(); i++) { - renderFinishedSemaphores.push_back(device.createSemaphore(semaphoreInfo)); - } - } - - // Clean up swap chain - void cleanupSwapChain() { - swapChainFramebuffers.clear(); - swapChainImageViews.clear(); - - // Semaphores tied to swapchain image indices need to be rebuilt on resize - renderFinishedSemaphores.clear(); - for (auto& imageView : swapChainImageViews) { - imageView = nullptr; - } - - swapChainImageViews.clear(); - 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, UINT64_MAX)); - - uint32_t imageIndex; - try { - auto [result, idx] = swapChain.acquireNextImage(UINT64_MAX, *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[imageIndex] - }; - queue.submit(submitInfo, *inFlightFences[currentFrame]); - - try { - const vk::PresentInfoKHR presentInfoKHR{ - .waitSemaphoreCount = 1, - .pWaitSemaphores = &*renderFinishedSemaphores[imageIndex], - .swapchainCount = 1, - .pSwapchains = &*swapChain, - .pImageIndices = &imageIndex - }; - - vk::Result 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"); - } - } catch (const vk::SystemError& e) { - if (e.code().value() == static_cast(vk::Result::eErrorOutOfDateKHR)) { - recreateSwapChain(); - return; - } else { - throw; - } - } - - currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; - } - - // Recreate swap chain - void recreateSwapChain() { + 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(swapChainImages.size()); + 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)); + inFlightFences.push_back(device.createFence(fenceInfo)); + } + + for (size_t i = 0; i < swapChainImages.size(); i++) + { + renderFinishedSemaphores.push_back(device.createSemaphore(semaphoreInfo)); + } + } + + // Clean up swap chain + void cleanupSwapChain() + { + swapChainFramebuffers.clear(); + swapChainImageViews.clear(); + + // Semaphores tied to swapchain image indices need to be rebuilt on resize + renderFinishedSemaphores.clear(); + for (auto &imageView : swapChainImageViews) + { + imageView = nullptr; + } + + swapChainImageViews.clear(); + 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, UINT64_MAX)); + + uint32_t imageIndex; + try + { + auto [result, idx] = swapChain.acquireNextImage(UINT64_MAX, *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[imageIndex]}; + queue.submit(submitInfo, *inFlightFences[currentFrame]); + + try + { + const vk::PresentInfoKHR presentInfoKHR{ + .waitSemaphoreCount = 1, + .pWaitSemaphores = &*renderFinishedSemaphores[imageIndex], + .swapchainCount = 1, + .pSwapchains = &*swapChain, + .pImageIndices = &imageIndex}; + + vk::Result 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"); + } + } + catch (const vk::SystemError &e) + { + if (e.code().value() == static_cast(vk::Result::eErrorOutOfDateKHR)) + { + recreateSwapChain(); + return; + } + else + { + throw; + } + } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + // Recreate swap chain + void recreateSwapChain() + { #if !PLATFORM_ANDROID - // On desktop, wait until the framebuffer has a non-zero size (e.g., when window is minimized) - int width = 0, height = 0; - if (window) { - glfwGetFramebufferSize(window, &width, &height); - while (width == 0 || height == 0) { - glfwGetFramebufferSize(window, &width, &height); - glfwWaitEvents(); - } - } + // On desktop, wait until the framebuffer has a non-zero size (e.g., when window is minimized) + int width = 0, height = 0; + if (window) + { + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) + { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + } #endif - // Wait for device to finish operations - device.waitIdle(); - - // Clean up old swap chain - cleanupSwapChain(); - - // Create new swap chain and dependent resources - createSwapChain(); - createImageViews(); - createFramebuffers(); - - // Recreate per-swapchain-image present semaphores for presenting - renderFinishedSemaphores.reserve(swapChainImages.size()); - vk::SemaphoreCreateInfo semaphoreInfo{}; - for (size_t i = 0; i < swapChainImages.size(); ++i) { - renderFinishedSemaphores.push_back(device.createSemaphore(semaphoreInfo)); - } - } - - // Get required extensions - std::vector getRequiredExtensions() { + // Wait for device to finish operations + device.waitIdle(); + + // Clean up old swap chain + cleanupSwapChain(); + + // Create new swap chain and dependent resources + createSwapChain(); + createImageViews(); + createFramebuffers(); + + // Recreate per-swapchain-image present semaphores for presenting + renderFinishedSemaphores.reserve(swapChainImages.size()); + vk::SemaphoreCreateInfo semaphoreInfo{}; + for (size_t i = 0; i < swapChainImages.size(); ++i) + { + renderFinishedSemaphores.push_back(device.createSemaphore(semaphoreInfo)); + } + } + + // 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 a8b98131..318d78e9 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 +#include +#include #if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) -#include +# include #else import vulkan_hpp; #endif #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,45 +83,48 @@ import vulkan_hpp; #include #include -constexpr uint32_t WIDTH = 800; +constexpr uint32_t WIDTH = 800; constexpr uint32_t HEIGHT = 600; // 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 @@ -124,1375 +134,1471 @@ 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); - glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); - - 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); + glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); + + 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() { - assert(swapChainImageViews.empty()); - - 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::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() { - // 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( - swapChainImages[imageIndex], - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // dstStage - vk::ImageAspectFlagBits::eColor - ); - // Transition depth image to depth attachment optimal layout - transition_image_layout( - *depthImage, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eDepthAttachmentOptimal, - vk::AccessFlagBits2::eDepthStencilAttachmentWrite, - vk::AccessFlagBits2::eDepthStencilAttachmentWrite, - vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, - vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, - vk::ImageAspectFlagBits::eDepth - ); - - 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::ClearValue clearDepth = vk::ClearDepthStencilValue{ 1.0f, 0 }; - 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 = &attachmentInfo, - .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( - swapChainImages[imageIndex], - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe, // dstStage - vk::ImageAspectFlagBits::eColor - ); - commandBuffers[currentFrame].end(); - } - - void transition_image_layout( - vk::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 image_aspect_flags - ) { - 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 = image_aspect_flags, - .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]); - - try { - 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!"); - } - } catch (const vk::SystemError& e) { - if (e.code().value() == static_cast(vk::Result::eErrorOutOfDateKHR)) { - recreateSwapChain(); - return; - } else { - throw; - } - } - 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() + { + assert(swapChainImageViews.empty()); + + 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::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() + { + // 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( + swapChainImages[imageIndex], + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // dstStage + vk::ImageAspectFlagBits::eColor); + // Transition depth image to depth attachment optimal layout + transition_image_layout( + *depthImage, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eDepthAttachmentOptimal, + vk::AccessFlagBits2::eDepthStencilAttachmentWrite, + vk::AccessFlagBits2::eDepthStencilAttachmentWrite, + vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, + vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, + vk::ImageAspectFlagBits::eDepth); + + 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::ClearValue clearDepth = vk::ClearDepthStencilValue{1.0f, 0}; + 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 = &attachmentInfo, + .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( + swapChainImages[imageIndex], + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe, // dstStage + vk::ImageAspectFlagBits::eColor); + commandBuffers[currentFrame].end(); + } + + void transition_image_layout( + vk::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 image_aspect_flags) + { + 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 = image_aspect_flags, + .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]); + + try + { + 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!"); + } + } + catch (const vk::SystemError &e) + { + if (e.code().value() == static_cast(vk::Result::eErrorOutOfDateKHR)) + { + recreateSwapChain(); + return; + } + else + { + throw; + } + } + 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 016700ce..2f248a0a 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 +#include +#include #if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) -#include +# include #else import vulkan_hpp; #endif #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,47 +83,50 @@ import vulkan_hpp; #include #include -constexpr uint32_t WIDTH = 800; +constexpr uint32_t WIDTH = 800; constexpr uint32_t HEIGHT = 600; // 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 @@ -126,1504 +136,1616 @@ 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); - glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); - - 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); + glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); + + 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() { - assert(swapChainImageViews.empty()); - - 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, - .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::StructureChain pipelineCreateInfoChain = { - {.stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pDepthStencilState = &depthStencil, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = *pipelineLayout, - .renderPass = nullptr }, - {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format, .depthAttachmentFormat = depthFormat } - }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); - } - - 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]); - 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, glm::radians(-90.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({}); - // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL - transition_image_layout( - swapChainImages[imageIndex], - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // dstStage - vk::ImageAspectFlagBits::eColor - ); - // Transition depth image to depth attachment optimal layout - transition_image_layout( - *depthImage, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eDepthAttachmentOptimal, - vk::AccessFlagBits2::eDepthStencilAttachmentWrite, - vk::AccessFlagBits2::eDepthStencilAttachmentWrite, - vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, - vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, - vk::ImageAspectFlagBits::eDepth - ); - - 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::ClearValue clearDepth = vk::ClearDepthStencilValue{ 1.0f, 0 }; - 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 = &attachmentInfo, - .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)); - - // 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(); - // After rendering, transition the swapchain image to PRESENT_SRC - transition_image_layout( - swapChainImages[imageIndex], - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe, // dstStage - vk::ImageAspectFlagBits::eColor - ); - commandBuffers[currentFrame].end(); - } - - void transition_image_layout( - vk::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 image_aspect_flags - ) { - 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 = image_aspect_flags, - .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); - proj[1][1] *= -1; - - // 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]); - - try { - 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!"); - } - } catch (const vk::SystemError& e) { - if (e.code().value() == static_cast(vk::Result::eErrorOutOfDateKHR)) { - recreateSwapChain(); - return; - } else { - throw; - } - } - 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() + { + assert(swapChainImageViews.empty()); + + 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, + .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::StructureChain pipelineCreateInfoChain = { + {.stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pDepthStencilState = &depthStencil, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = *pipelineLayout, + .renderPass = nullptr}, + {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format, .depthAttachmentFormat = depthFormat}}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); + } + + 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]); + 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, glm::radians(-90.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({}); + // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL + transition_image_layout( + swapChainImages[imageIndex], + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // dstStage + vk::ImageAspectFlagBits::eColor); + // Transition depth image to depth attachment optimal layout + transition_image_layout( + *depthImage, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eDepthAttachmentOptimal, + vk::AccessFlagBits2::eDepthStencilAttachmentWrite, + vk::AccessFlagBits2::eDepthStencilAttachmentWrite, + vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, + vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, + vk::ImageAspectFlagBits::eDepth); + + 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::ClearValue clearDepth = vk::ClearDepthStencilValue{1.0f, 0}; + 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 = &attachmentInfo, + .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)); + + // 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(); + // After rendering, transition the swapchain image to PRESENT_SRC + transition_image_layout( + swapChainImages[imageIndex], + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe, // dstStage + vk::ImageAspectFlagBits::eColor); + commandBuffers[currentFrame].end(); + } + + void transition_image_layout( + vk::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 image_aspect_flags) + { + 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 = image_aspect_flags, + .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); + proj[1][1] *= -1; + + // 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]); + + try + { + 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!"); + } + } + catch (const vk::SystemError &e) + { + if (e.code().value() == static_cast(vk::Result::eErrorOutOfDateKHR)) + { + recreateSwapChain(); + return; + } + else + { + throw; + } + } + 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 2dac43dd..fc254156 100644 --- a/attachments/37_multithreading.cpp +++ b/attachments/37_multithreading.cpp @@ -1,1268 +1,1356 @@ -#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 +#include +#include #if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) -#include +# include #else import vulkan_hpp; #endif -#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 uint32_t PARTICLE_COUNT = 8192; -constexpr int MAX_FRAMES_IN_FLIGHT = 2; +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; +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; - - bool framebufferResized = false; - - 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_TRUE); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan Multithreading", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); - - lastTime = glfwGetTime(); - } - - static void framebufferResizeCallback(GLFWwindow* window, int, int) { - auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); - if (app) { - app->framebufferResized = true; - } - } - - 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 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(); - createComputeDescriptorSetLayout(); - createGraphicsPipeline(); - createComputePipeline(); - createShaderStorageBuffers(); - createUniformBuffers(); - createDescriptorPool(); - createComputeDescriptorSets(); - } - - 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; + + bool framebufferResized = false; + + 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_TRUE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan Multithreading", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + + lastTime = glfwGetTime(); + } + + static void framebufferResizeCallback(GLFWwindow *window, int, int) + { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + if (app) + { + app->framebufferResized = true; + } + } + + 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 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(); + createComputeDescriptorSetLayout(); + createGraphicsPipeline(); + createComputePipeline(); + createShaderStorageBuffers(); + createUniformBuffers(); + createDescriptorPool(); + createComputeDescriptorSets(); + } + + 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() { - assert(swapChainImageViews.empty()); - - 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::StructureChain pipelineCreateInfoChain = { - {.stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = *pipelineLayout, - .renderPass = nullptr }, - {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format } - }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); - } - - 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() + { + assert(swapChainImageViews.empty()); + + 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::StructureChain pipelineCreateInfoChain = { + {.stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = *pipelineLayout, + .renderPass = nullptr}, + {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format}}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); + } + + 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); - - // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL - transition_image_layout( - swapChainImages[imageIndex], - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // dstStage - vk::ImageAspectFlagBits::eColor - ); - - 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(); - - // After rendering, transition the swapchain image to PRESENT_SRC - transition_image_layout( - swapChainImages[imageIndex], - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe, // dstStage - vk::ImageAspectFlagBits::eColor - ); - - graphicsCommandBuffers[currentFrame].end(); - } - - void transition_image_layout( - vk::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 image_aspect_flags - ) { - 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 = image_aspect_flags, - .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)) - ; - - // If the framebuffer was resized, rebuild the swap chain before acquiring a new image - if (framebufferResized) { - recreateSwapChain(); - framebufferResized = false; - return; - } - - // Acquire the next image - auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *imageAvailableSemaphores[currentFrame], nullptr); - if (result == vk::Result::eErrorOutOfDateKHR) { - recreateSwapChain(); - return; - } else if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) { - throw std::runtime_error("failed to acquire swap chain image!"); - } - - // 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); - device.resetFences(*inFlightFences[currentFrame]); - 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); - if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { - framebufferResized = false; - recreateSwapChain(); - return; - } else if (result != vk::Result::eSuccess) { - throw std::runtime_error("failed to present swap chain image!"); - } - - // 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); + + // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL + transition_image_layout( + swapChainImages[imageIndex], + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // dstStage + vk::ImageAspectFlagBits::eColor); + + 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(); + + // After rendering, transition the swapchain image to PRESENT_SRC + transition_image_layout( + swapChainImages[imageIndex], + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe, // dstStage + vk::ImageAspectFlagBits::eColor); + + graphicsCommandBuffers[currentFrame].end(); + } + + void transition_image_layout( + vk::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 image_aspect_flags) + { + 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 = image_aspect_flags, + .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)) + ; + + // If the framebuffer was resized, rebuild the swap chain before acquiring a new image + if (framebufferResized) + { + recreateSwapChain(); + framebufferResized = false; + return; + } + + // Acquire the next image + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *imageAvailableSemaphores[currentFrame], nullptr); + if (result == vk::Result::eErrorOutOfDateKHR) + { + recreateSwapChain(); + return; + } + else if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) + { + throw std::runtime_error("failed to acquire swap chain image!"); + } + + // 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); + device.resetFences(*inFlightFences[currentFrame]); + 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); + if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) + { + framebufferResized = false; + recreateSwapChain(); + return; + } + else if (result != vk::Result::eSuccess) + { + throw std::runtime_error("failed to present swap chain image!"); + } + + // 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; } diff --git a/attachments/38_ray_tracing.cpp b/attachments/38_ray_tracing.cpp index 746fa9bb..4ba405c3 100644 --- a/attachments/38_ray_tracing.cpp +++ b/attachments/38_ray_tracing.cpp @@ -1,22 +1,22 @@ -#include -#include -#include -#include -#include -#include -#include #include -#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include #if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) -#include +# include #else import vulkan_hpp; #endif -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. +#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include #define GLM_FORCE_RADIANS @@ -33,7 +33,7 @@ import vulkan_hpp; #include #ifndef LAB_TASK_LEVEL -#define LAB_TASK_LEVEL 1 +# define LAB_TASK_LEVEL 1 #endif #define LAB_TASK_AS_BUILD_AND_BIND 4 @@ -42,14 +42,13 @@ import vulkan_hpp; #define LAB_TASK_INSTANCE_LUT 9 #define LAB_TASK_REFLECTIONS 11 -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -const std::string MODEL_PATH = "models/plant_on_table.obj"; -constexpr int MAX_FRAMES_IN_FLIGHT = 2; +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; +const std::string MODEL_PATH = "models/plant_on_table.obj"; +constexpr int MAX_FRAMES_IN_FLIGHT = 2; -const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" -}; +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation"}; #ifdef NDEBUG constexpr bool enableValidationLayers = false; @@ -57,1895 +56,1908 @@ constexpr bool enableValidationLayers = false; constexpr bool enableValidationLayers = true; #endif -struct Vertex { - glm::vec3 pos; - glm::vec3 color; - glm::vec2 texCoord; - glm::vec3 normal; - - 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) ), - vk::VertexInputAttributeDescription( 3, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, normal) ) - }; - } - - bool operator==(const Vertex& other) const { - return pos == other.pos && color == other.color && texCoord == other.texCoord && normal == other.normal; - } +struct Vertex +{ + glm::vec3 pos; + glm::vec3 color; + glm::vec2 texCoord; + glm::vec3 normal; + + 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)), + vk::VertexInputAttributeDescription(3, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, normal))}; + } + + bool operator==(const Vertex &other) const + { + return pos == other.pos && color == other.color && texCoord == other.texCoord && normal == other.normal; + } }; -template<> struct std::hash { - size_t operator()(Vertex const& vertex) const noexcept { - auto h = std::hash()(vertex.pos) ^ (std::hash()(vertex.color) << 1); - h = (h >> 1) ^ (std::hash()(vertex.texCoord) << 1); - h = (h >> 1) ^ (std::hash()(vertex.normal) << 1); - return h; - } +template <> +struct std::hash +{ + size_t operator()(Vertex const &vertex) const noexcept + { + auto h = std::hash()(vertex.pos) ^ (std::hash()(vertex.color) << 1); + h = (h >> 1) ^ (std::hash()(vertex.texCoord) << 1); + h = (h >> 1) ^ (std::hash()(vertex.normal) << 1); + return h; + } }; -struct UniformBufferObject { - alignas(16) glm::mat4 model; - alignas(16) glm::mat4 view; - alignas(16) glm::mat4 proj; - alignas(16) glm::vec3 cameraPos; +struct UniformBufferObject +{ + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; + alignas(16) glm::vec3 cameraPos; }; -struct PushConstant { - uint32_t materialIndex; +struct PushConstant +{ + uint32_t materialIndex; #if LAB_TASK_LEVEL >= LAB_TASK_REFLECTIONS - // TASK11 - uint32_t reflective; -#endif // LAB_TASK_LEVEL >= LAB_TASK_REFLECTIONS + // TASK11 + uint32_t reflective; +#endif // LAB_TASK_LEVEL >= LAB_TASK_REFLECTIONS }; -class VulkanRaytracingApplication { -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 graphicsQueue = nullptr; - vk::raii::Queue presentQueue = nullptr; - - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::Format swapChainImageFormat = vk::Format::eUndefined; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::DescriptorSetLayout descriptorSetLayoutGlobal = nullptr; - vk::raii::DescriptorSetLayout descriptorSetLayoutMaterial = 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; - - std::vector textureImages; - std::vector textureImageMemories; - std::vector textureImageViews; - 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; - vk::raii::Buffer uvBuffer = nullptr; - vk::raii::DeviceMemory uvBufferMemory = nullptr; - - std::vector blasBuffers; - std::vector blasMemories; - std::vector blasHandles; - - std::vector instances; - vk::raii::Buffer instanceBuffer = nullptr; - vk::raii::DeviceMemory instanceMemory = nullptr; - - vk::raii::Buffer tlasBuffer = nullptr; - vk::raii::DeviceMemory tlasMemory = nullptr; - vk::raii::Buffer tlasScratchBuffer = nullptr; - vk::raii::DeviceMemory tlasScratchMemory = nullptr; - vk::raii::AccelerationStructureKHR tlas = nullptr; - - struct InstanceLUT { - uint32_t materialID; - uint32_t indexBufferOffset; - }; - std::vector instanceLUTs; - vk::raii::Buffer instanceLUTBuffer = nullptr; - vk::raii::DeviceMemory instanceLUTBufferMemory = nullptr; - - UniformBufferObject ubo{}; - - std::vector uniformBuffers; - std::vector uniformBuffersMemory; - std::vector uniformBuffersMapped; - - struct SubMesh { - uint32_t indexOffset; - uint32_t indexCount; - int materialID; - uint32_t firstVertex; - uint32_t maxVertex; - bool alphaCut; - bool reflective; - }; - std::vector submeshes; - std::vector materials; - - vk::raii::DescriptorPool descriptorPool = nullptr; - std::vector globalDescriptorSets; - std::vector materialDescriptorSets; - - vk::raii::CommandPool commandPool = nullptr; - std::vector commandBuffers; - uint32_t graphicsIndex = 0; - - 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, - vk::KHRAccelerationStructureExtensionName, - vk::KHRBufferDeviceAddressExtensionName, - vk::KHRDeferredHostOperationsExtensionName, - vk::KHRRayQueryExtensionName - }; - - 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(); - createCommandPool(); - loadModel(); - createDescriptorSetLayout(); - createGraphicsPipeline(); - createDepthResources(); - createTextureSampler(); - createVertexBuffer(); - createIndexBuffer(); - createUVBuffer(); - createAccelerationStructures(); - createInstanceLUTBuffer(); - 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 = "Vulkan Tutorial Ray Tracing", - .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().descriptorBindingSampledImageUpdateAfterBind && - features.template get().descriptorBindingPartiallyBound && - features.template get().descriptorBindingVariableDescriptorCount && - features.template get().runtimeDescriptorArray && - features.template get().shaderSampledImageArrayNonUniformIndexing && - features.template get().bufferDeviceAddress && - features.template get().accelerationStructure && - features.template get().rayQuery; - - 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); } ); - - graphicsIndex = static_cast( std::distance( queueFamilyProperties.begin(), graphicsQueueFamilyProperty ) ); - - // determine a queueFamilyIndex that supports present - // first check if the graphicsIndex is good enough - auto presentIndex = physicalDevice.getSurfaceSupportKHR( graphicsIndex, *surface ) - ? graphicsIndex - : ~0; - if ( presentIndex == queueFamilyProperties.size() ) - { - // the graphicsIndex doesn't support present -> look for another family index that supports both - // graphics and present - for ( size_t i = 0; i < queueFamilyProperties.size(); i++ ) - { - if ( ( queueFamilyProperties[i].queueFlags & vk::QueueFlagBits::eGraphics ) && - physicalDevice.getSurfaceSupportKHR( static_cast( i ), *surface ) ) - { - graphicsIndex = static_cast( i ); - presentIndex = graphicsIndex; - break; - } - } - if ( presentIndex == queueFamilyProperties.size() ) - { - // there's nothing like a single family index that supports both graphics and present -> look for another - // family index that supports present - for ( size_t i = 0; i < queueFamilyProperties.size(); i++ ) - { - if ( physicalDevice.getSurfaceSupportKHR( static_cast( i ), *surface ) ) - { - presentIndex = static_cast( i ); - break; - } - } - } - } - if ( ( graphicsIndex == queueFamilyProperties.size() ) || ( presentIndex == queueFamilyProperties.size() ) ) - { - throw std::runtime_error( "Could not find a queue for graphics or present -> terminating" ); - } - - // query for Vulkan 1.3 features - vk::StructureChain featureChain = { - {.features = {.samplerAnisotropy = true } }, // vk::PhysicalDeviceFeatures2 - {.shaderSampledImageArrayNonUniformIndexing = true, .descriptorBindingSampledImageUpdateAfterBind = true, - .descriptorBindingPartiallyBound = true, .descriptorBindingVariableDescriptorCount = true, - .runtimeDescriptorArray = true, .bufferDeviceAddress = true }, // vk::PhysicalDeviceVulkan12Features - {.synchronization2 = true, .dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true }, // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - {.accelerationStructure = true }, // vk::PhysicalDeviceAccelerationStructureFeaturesKHR - {.rayQuery = true } // vk::PhysicalDeviceRayQueryFeaturesKHR - }; - - // 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 ); - presentQueue = vk::raii::Queue( device, presentIndex, 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() { - // Use descriptor set 0 for global data - // TASK04: The acceleration structure uses binding 1 - std::array global_bindings = { - vk::DescriptorSetLayoutBinding( 0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment, nullptr), - vk::DescriptorSetLayoutBinding( 1, vk::DescriptorType::eAccelerationStructureKHR, 1, vk::ShaderStageFlagBits::eFragment, nullptr), - vk::DescriptorSetLayoutBinding( 2, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eFragment, nullptr), - vk::DescriptorSetLayoutBinding( 3, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eFragment, nullptr), - vk::DescriptorSetLayoutBinding( 4, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eFragment, nullptr) - }; - - vk::DescriptorSetLayoutCreateInfo globalLayoutInfo{ .bindingCount = static_cast(global_bindings.size()), .pBindings = global_bindings.data() }; - - descriptorSetLayoutGlobal = vk::raii::DescriptorSetLayout(device, globalLayoutInfo); - - // Use descriptor set 1 for bindless material data - uint32_t textureCount = static_cast(textureImageViews.size()); - - std::array material_bindings = { - vk::DescriptorSetLayoutBinding( 0, vk::DescriptorType::eSampler, 1, vk::ShaderStageFlagBits::eFragment, nullptr), - vk::DescriptorSetLayoutBinding( 1, vk::DescriptorType::eSampledImage, static_cast(textureCount), vk::ShaderStageFlagBits::eFragment, nullptr) - }; - - std::vector bindingFlags = { - vk::DescriptorBindingFlagBits::eUpdateAfterBind, - vk::DescriptorBindingFlagBits::ePartiallyBound | vk::DescriptorBindingFlagBits::eVariableDescriptorCount | vk::DescriptorBindingFlagBits::eUpdateAfterBind - }; - - vk::DescriptorSetLayoutBindingFlagsCreateInfo flagsCreateInfo{ - .bindingCount = static_cast(bindingFlags.size()), - .pBindingFlags = bindingFlags.data() - }; - - vk::DescriptorSetLayoutCreateInfo materialLayoutInfo{ - .pNext = &flagsCreateInfo, - .flags = vk::DescriptorSetLayoutCreateFlagBits::eUpdateAfterBindPool, - .bindingCount = static_cast(material_bindings.size()), - .pBindings = material_bindings.data(), - }; - - descriptorSetLayoutMaterial = vk::raii::DescriptorSetLayout(device, materialLayoutInfo); - } - - 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::DescriptorSetLayout setLayouts[] = {*descriptorSetLayoutGlobal, *descriptorSetLayoutMaterial}; - - vk::PushConstantRange pushConstantRange { - .stageFlags = vk::ShaderStageFlagBits::eFragment, - .offset = 0, - .size = sizeof(PushConstant) - }; - - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 2, .pSetLayouts = setLayouts, .pushConstantRangeCount = 1, .pPushConstantRanges = &pushConstantRange }; - - pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); - - vk::Format depthFormat = findDepthFormat(); - - /* TASK01: Check the setup for dynamic rendering - * - * This new struct replaces what previously was the render pass in the pipeline creation. - * Note how this structure is now linked in .pNext below, and .renderPass is not used. - */ - vk::StructureChain pipelineCreateInfoChain = { - {.stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pDepthStencilState = &depthStencil, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = *pipelineLayout, - .renderPass = nullptr }, - {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainImageFormat, .depthAttachmentFormat = depthFormat } - }; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); - } - - void createCommandPool() { - vk::CommandPoolCreateInfo poolInfo{ - .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = graphicsIndex - }; - 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; - } - - std::pair createTextureImage(const std::string& path) { - int texWidth, texHeight, texChannels; - stbi_uc* pixels = stbi_load(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); - - vk::raii::Image textureImage = nullptr; - vk::raii::DeviceMemory textureImageMemory = nullptr; - - 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); - - return std::make_pair(std::move(textureImage), std::move(textureImageMemory)); - } - - vk::raii::ImageView createTextureImageView(vk::raii::Image& textureImage) { - return 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 localMaterials; - std::string warn, err; - - if (!LoadObj(&attrib, &shapes, &localMaterials, &warn, &err, MODEL_PATH.c_str(), MODEL_PATH.substr(0, MODEL_PATH.find_last_of("/\\")).c_str())) { - throw std::runtime_error(warn + err); - } - - size_t materialOffset = materials.size(); - size_t oldTextureCount = textureImageViews.size(); - - materials.insert(materials.end(), localMaterials.begin(), localMaterials.end()); - - std::unordered_map uniqueVertices{}; - uint32_t indexOffset = 0; - - for (const auto& shape : shapes) { - std::cout << "Loading mesh: " << shape.name << ": " << shape.mesh.indices.size()/3 << " triangles\n"; - - uint32_t startOffset = indexOffset; - uint32_t localMaxV = 0; - - 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 (index.normal_index >= 0) { - vertex.normal = { - attrib.normals[3 * index.normal_index + 0], - attrib.normals[3 * index.normal_index + 1], - attrib.normals[3 * index.normal_index + 2] - }; - } else { - vertex.normal = {0.0f, 0.0f, 0.0f}; - } - - if (!uniqueVertices.contains(vertex)) { - uniqueVertices[vertex] = static_cast(vertices.size()); - vertices.push_back(vertex); - } - - indices.push_back(uniqueVertices[vertex]); - - indexOffset++; - - uint32_t vi; - auto it = uniqueVertices.find(vertex); - if (it != uniqueVertices.end()) { - vi = it->second; - } else { - vi = static_cast(vertices.size()); - uniqueVertices[vertex] = vi; - vertices.push_back(vertex); - } - - localMaxV = std::max(localMaxV, vi); - } - - int localMaterialID = shape.mesh.material_ids.empty() ? -1 : shape.mesh.material_ids[0]; - int globalMaterialID = (localMaterialID < 0) ? -1 : static_cast(materialOffset + localMaterialID); - - uint32_t indexCount = indexOffset - startOffset; - - // Note that this is only valid for this particular MODEL_PATH - bool alphaCut = (shape.name.find("nettle_plant") != std::string::npos); - bool reflective = (shape.name.find("table") != std::string::npos); - - submeshes.push_back({ - .indexOffset = startOffset, - .indexCount = indexCount, - .materialID = globalMaterialID, - .firstVertex = 0u, - .maxVertex = localMaxV + 1, - .alphaCut = alphaCut, - .reflective = reflective +class VulkanRaytracingApplication +{ + 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 graphicsQueue = nullptr; + vk::raii::Queue presentQueue = nullptr; + + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::Format swapChainImageFormat = vk::Format::eUndefined; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + + vk::raii::DescriptorSetLayout descriptorSetLayoutGlobal = nullptr; + vk::raii::DescriptorSetLayout descriptorSetLayoutMaterial = 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; + + std::vector textureImages; + std::vector textureImageMemories; + std::vector textureImageViews; + 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; + vk::raii::Buffer uvBuffer = nullptr; + vk::raii::DeviceMemory uvBufferMemory = nullptr; + + std::vector blasBuffers; + std::vector blasMemories; + std::vector blasHandles; + + std::vector instances; + vk::raii::Buffer instanceBuffer = nullptr; + vk::raii::DeviceMemory instanceMemory = nullptr; + + vk::raii::Buffer tlasBuffer = nullptr; + vk::raii::DeviceMemory tlasMemory = nullptr; + vk::raii::Buffer tlasScratchBuffer = nullptr; + vk::raii::DeviceMemory tlasScratchMemory = nullptr; + vk::raii::AccelerationStructureKHR tlas = nullptr; + + struct InstanceLUT + { + uint32_t materialID; + uint32_t indexBufferOffset; + }; + std::vector instanceLUTs; + vk::raii::Buffer instanceLUTBuffer = nullptr; + vk::raii::DeviceMemory instanceLUTBufferMemory = nullptr; + + UniformBufferObject ubo{}; + + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + std::vector uniformBuffersMapped; + + struct SubMesh + { + uint32_t indexOffset; + uint32_t indexCount; + int materialID; + uint32_t firstVertex; + uint32_t maxVertex; + bool alphaCut; + bool reflective; + }; + std::vector submeshes; + std::vector materials; + + vk::raii::DescriptorPool descriptorPool = nullptr; + std::vector globalDescriptorSets; + std::vector materialDescriptorSets; + + vk::raii::CommandPool commandPool = nullptr; + std::vector commandBuffers; + uint32_t graphicsIndex = 0; + + 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, + vk::KHRAccelerationStructureExtensionName, + vk::KHRBufferDeviceAddressExtensionName, + vk::KHRDeferredHostOperationsExtensionName, + vk::KHRRayQueryExtensionName}; + + 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(); + createCommandPool(); + loadModel(); + createDescriptorSetLayout(); + createGraphicsPipeline(); + createDepthResources(); + createTextureSampler(); + createVertexBuffer(); + createIndexBuffer(); + createUVBuffer(); + createAccelerationStructures(); + createInstanceLUTBuffer(); + 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 = "Vulkan Tutorial Ray Tracing", + .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().descriptorBindingSampledImageUpdateAfterBind && + features.template get().descriptorBindingPartiallyBound && + features.template get().descriptorBindingVariableDescriptorCount && + features.template get().runtimeDescriptorArray && + features.template get().shaderSampledImageArrayNonUniformIndexing && + features.template get().bufferDeviceAddress && + features.template get().accelerationStructure && + features.template get().rayQuery; + + return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; }); - } - - for (size_t i = 0; i < localMaterials.size(); ++i) { - const auto& material = localMaterials[i]; - - if (!material.diffuse_texname.empty()) { - std::string texturePath = MODEL_PATH.substr(0, MODEL_PATH.find_last_of("/\\")) + "/" + material.diffuse_texname; - auto [img, mem] = createTextureImage(texturePath); - textureImages.push_back(std::move(img)); - textureImageMemories.push_back(std::move(mem)); - textureImageViews.emplace_back(createTextureImageView(textureImages.back())); - } else { - std::cout << "No texture for material: " << material.name << std::endl; - } - } - } - - 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::BufferUsageFlagBits::eShaderDeviceAddress | - vk::BufferUsageFlagBits::eAccelerationStructureBuildInputReadOnlyKHR, 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::BufferUsageFlagBits::eShaderDeviceAddress | - vk::BufferUsageFlagBits::eAccelerationStructureBuildInputReadOnlyKHR | vk::BufferUsageFlagBits::eStorageBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); - - copyBuffer(stagingBuffer, indexBuffer, bufferSize); - } - - void createUVBuffer() { - // Extract all texCoords into a separate vector - std::vector uvs; - uvs.reserve(vertices.size()); - for (auto& v: vertices) { - uvs.push_back(v.texCoord); - } - - vk::DeviceSize bufferSize = sizeof(uvs[0]) * uvs.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, uvs.data(), bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eStorageBuffer, - vk::MemoryPropertyFlagBits::eDeviceLocal, uvBuffer, uvBufferMemory); - - copyBuffer(stagingBuffer, uvBuffer, bufferSize); - } + 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); }); + + graphicsIndex = static_cast(std::distance(queueFamilyProperties.begin(), graphicsQueueFamilyProperty)); + + // determine a queueFamilyIndex that supports present + // first check if the graphicsIndex is good enough + auto presentIndex = physicalDevice.getSurfaceSupportKHR(graphicsIndex, *surface) ? graphicsIndex : ~0; + if (presentIndex == queueFamilyProperties.size()) + { + // the graphicsIndex doesn't support present -> look for another family index that supports both + // graphics and present + for (size_t i = 0; i < queueFamilyProperties.size(); i++) + { + if ((queueFamilyProperties[i].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(static_cast(i), *surface)) + { + graphicsIndex = static_cast(i); + presentIndex = graphicsIndex; + break; + } + } + if (presentIndex == queueFamilyProperties.size()) + { + // there's nothing like a single family index that supports both graphics and present -> look for another + // family index that supports present + for (size_t i = 0; i < queueFamilyProperties.size(); i++) + { + if (physicalDevice.getSurfaceSupportKHR(static_cast(i), *surface)) + { + presentIndex = static_cast(i); + break; + } + } + } + } + if ((graphicsIndex == queueFamilyProperties.size()) || (presentIndex == queueFamilyProperties.size())) + { + throw std::runtime_error("Could not find a queue for graphics or present -> terminating"); + } + + // query for Vulkan 1.3 features + vk::StructureChain + featureChain = { + {.features = {.samplerAnisotropy = true}}, // vk::PhysicalDeviceFeatures2 + {.shaderSampledImageArrayNonUniformIndexing = true, .descriptorBindingSampledImageUpdateAfterBind = true, .descriptorBindingPartiallyBound = true, .descriptorBindingVariableDescriptorCount = true, .runtimeDescriptorArray = true, .bufferDeviceAddress = true}, // vk::PhysicalDeviceVulkan12Features + {.synchronization2 = true, .dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features + {.extendedDynamicState = true}, // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + {.accelerationStructure = true}, // vk::PhysicalDeviceAccelerationStructureFeaturesKHR + {.rayQuery = true} // vk::PhysicalDeviceRayQueryFeaturesKHR + }; + + // 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); + presentQueue = vk::raii::Queue(device, presentIndex, 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() + { + // Use descriptor set 0 for global data + // TASK04: The acceleration structure uses binding 1 + std::array global_bindings = { + vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment, nullptr), + vk::DescriptorSetLayoutBinding(1, vk::DescriptorType::eAccelerationStructureKHR, 1, vk::ShaderStageFlagBits::eFragment, nullptr), + vk::DescriptorSetLayoutBinding(2, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eFragment, nullptr), + vk::DescriptorSetLayoutBinding(3, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eFragment, nullptr), + vk::DescriptorSetLayoutBinding(4, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eFragment, nullptr)}; + + vk::DescriptorSetLayoutCreateInfo globalLayoutInfo{.bindingCount = static_cast(global_bindings.size()), .pBindings = global_bindings.data()}; + + descriptorSetLayoutGlobal = vk::raii::DescriptorSetLayout(device, globalLayoutInfo); + + // Use descriptor set 1 for bindless material data + uint32_t textureCount = static_cast(textureImageViews.size()); + + std::array material_bindings = { + vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eSampler, 1, vk::ShaderStageFlagBits::eFragment, nullptr), + vk::DescriptorSetLayoutBinding(1, vk::DescriptorType::eSampledImage, static_cast(textureCount), vk::ShaderStageFlagBits::eFragment, nullptr)}; + + std::vector bindingFlags = { + vk::DescriptorBindingFlagBits::eUpdateAfterBind, + vk::DescriptorBindingFlagBits::ePartiallyBound | vk::DescriptorBindingFlagBits::eVariableDescriptorCount | vk::DescriptorBindingFlagBits::eUpdateAfterBind}; + + vk::DescriptorSetLayoutBindingFlagsCreateInfo flagsCreateInfo{ + .bindingCount = static_cast(bindingFlags.size()), + .pBindingFlags = bindingFlags.data()}; + + vk::DescriptorSetLayoutCreateInfo materialLayoutInfo{ + .pNext = &flagsCreateInfo, + .flags = vk::DescriptorSetLayoutCreateFlagBits::eUpdateAfterBindPool, + .bindingCount = static_cast(material_bindings.size()), + .pBindings = material_bindings.data(), + }; - void createInstanceLUTBuffer() { + descriptorSetLayoutMaterial = vk::raii::DescriptorSetLayout(device, materialLayoutInfo); + } + + 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::DescriptorSetLayout setLayouts[] = {*descriptorSetLayoutGlobal, *descriptorSetLayoutMaterial}; + + vk::PushConstantRange pushConstantRange{ + .stageFlags = vk::ShaderStageFlagBits::eFragment, + .offset = 0, + .size = sizeof(PushConstant)}; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 2, .pSetLayouts = setLayouts, .pushConstantRangeCount = 1, .pPushConstantRanges = &pushConstantRange}; + + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::Format depthFormat = findDepthFormat(); + + /* TASK01: Check the setup for dynamic rendering + * + * This new struct replaces what previously was the render pass in the pipeline creation. + * Note how this structure is now linked in .pNext below, and .renderPass is not used. + */ + vk::StructureChain pipelineCreateInfoChain = { + {.stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pDepthStencilState = &depthStencil, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = *pipelineLayout, + .renderPass = nullptr}, + {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainImageFormat, .depthAttachmentFormat = depthFormat}}; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); + } + + void createCommandPool() + { + vk::CommandPoolCreateInfo poolInfo{ + .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = graphicsIndex}; + 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; + } + + std::pair createTextureImage(const std::string &path) + { + int texWidth, texHeight, texChannels; + stbi_uc *pixels = stbi_load(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); + + vk::raii::Image textureImage = nullptr; + vk::raii::DeviceMemory textureImageMemory = nullptr; + + 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); + + return std::make_pair(std::move(textureImage), std::move(textureImageMemory)); + } + + vk::raii::ImageView createTextureImageView(vk::raii::Image &textureImage) + { + return 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 localMaterials; + std::string warn, err; + + if (!LoadObj(&attrib, &shapes, &localMaterials, &warn, &err, MODEL_PATH.c_str(), MODEL_PATH.substr(0, MODEL_PATH.find_last_of("/\\")).c_str())) + { + throw std::runtime_error(warn + err); + } + + size_t materialOffset = materials.size(); + size_t oldTextureCount = textureImageViews.size(); + + materials.insert(materials.end(), localMaterials.begin(), localMaterials.end()); + + std::unordered_map uniqueVertices{}; + uint32_t indexOffset = 0; + + for (const auto &shape : shapes) + { + std::cout << "Loading mesh: " << shape.name << ": " << shape.mesh.indices.size() / 3 << " triangles\n"; + + uint32_t startOffset = indexOffset; + uint32_t localMaxV = 0; + + 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 (index.normal_index >= 0) + { + vertex.normal = { + attrib.normals[3 * index.normal_index + 0], + attrib.normals[3 * index.normal_index + 1], + attrib.normals[3 * index.normal_index + 2]}; + } + else + { + vertex.normal = {0.0f, 0.0f, 0.0f}; + } + + if (!uniqueVertices.contains(vertex)) + { + uniqueVertices[vertex] = static_cast(vertices.size()); + vertices.push_back(vertex); + } + + indices.push_back(uniqueVertices[vertex]); + + indexOffset++; + + uint32_t vi; + auto it = uniqueVertices.find(vertex); + if (it != uniqueVertices.end()) + { + vi = it->second; + } + else + { + vi = static_cast(vertices.size()); + uniqueVertices[vertex] = vi; + vertices.push_back(vertex); + } + + localMaxV = std::max(localMaxV, vi); + } + + int localMaterialID = shape.mesh.material_ids.empty() ? -1 : shape.mesh.material_ids[0]; + int globalMaterialID = (localMaterialID < 0) ? -1 : static_cast(materialOffset + localMaterialID); + + uint32_t indexCount = indexOffset - startOffset; + + // Note that this is only valid for this particular MODEL_PATH + bool alphaCut = (shape.name.find("nettle_plant") != std::string::npos); + bool reflective = (shape.name.find("table") != std::string::npos); + + submeshes.push_back({.indexOffset = startOffset, + .indexCount = indexCount, + .materialID = globalMaterialID, + .firstVertex = 0u, + .maxVertex = localMaxV + 1, + .alphaCut = alphaCut, + .reflective = reflective}); + } + + for (size_t i = 0; i < localMaterials.size(); ++i) + { + const auto &material = localMaterials[i]; + + if (!material.diffuse_texname.empty()) + { + std::string texturePath = MODEL_PATH.substr(0, MODEL_PATH.find_last_of("/\\")) + "/" + material.diffuse_texname; + auto [img, mem] = createTextureImage(texturePath); + textureImages.push_back(std::move(img)); + textureImageMemories.push_back(std::move(mem)); + textureImageViews.emplace_back(createTextureImageView(textureImages.back())); + } + else + { + std::cout << "No texture for material: " << material.name << std::endl; + } + } + } + + 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::BufferUsageFlagBits::eShaderDeviceAddress | vk::BufferUsageFlagBits::eAccelerationStructureBuildInputReadOnlyKHR, 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::BufferUsageFlagBits::eShaderDeviceAddress | vk::BufferUsageFlagBits::eAccelerationStructureBuildInputReadOnlyKHR | vk::BufferUsageFlagBits::eStorageBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); + + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + } + + void createUVBuffer() + { + // Extract all texCoords into a separate vector + std::vector uvs; + uvs.reserve(vertices.size()); + for (auto &v : vertices) + { + uvs.push_back(v.texCoord); + } + + vk::DeviceSize bufferSize = sizeof(uvs[0]) * uvs.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, uvs.data(), bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eStorageBuffer, + vk::MemoryPropertyFlagBits::eDeviceLocal, uvBuffer, uvBufferMemory); + + copyBuffer(stagingBuffer, uvBuffer, bufferSize); + } + + void createInstanceLUTBuffer() + { #if LAB_TASK_LEVEL >= LAB_TASK_INSTANCE_LUT - // TASK09: build a buffer to store the instance look-up table - vk::DeviceSize bufferSize = sizeof(InstanceLUT) * instanceLUTs.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, instanceLUTs.data(), bufferSize); - stagingBufferMemory.unmapMemory(); - - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eStorageBuffer, - vk::MemoryPropertyFlagBits::eDeviceLocal, instanceLUTBuffer, instanceLUTBufferMemory); - - copyBuffer(stagingBuffer, instanceLUTBuffer, bufferSize); -#endif // LAB_TASK_LEVEL >= LAB_TASK_INSTANCE_LUT - } - - 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 createAccelerationStructures() { + // TASK09: build a buffer to store the instance look-up table + vk::DeviceSize bufferSize = sizeof(InstanceLUT) * instanceLUTs.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, instanceLUTs.data(), bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eStorageBuffer, + vk::MemoryPropertyFlagBits::eDeviceLocal, instanceLUTBuffer, instanceLUTBufferMemory); + + copyBuffer(stagingBuffer, instanceLUTBuffer, bufferSize); +#endif // LAB_TASK_LEVEL >= LAB_TASK_INSTANCE_LUT + } + + 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 createAccelerationStructures() + { #if LAB_TASK_LEVEL >= LAB_TASK_AS_BUILD_AND_BIND - vk::BufferDeviceAddressInfo vai{ .buffer = *vertexBuffer }; - vk::DeviceAddress vertexAddr = device.getBufferAddressKHR(vai); - vk::BufferDeviceAddressInfo iai{ .buffer = *indexBuffer }; - vk::DeviceAddress indexAddr = device.getBufferAddressKHR(iai); - - instances.reserve(submeshes.size()); - blasBuffers.reserve(submeshes.size()); - blasMemories.reserve(submeshes.size()); - blasHandles.reserve(submeshes.size()); - - vk::TransformMatrixKHR identity{}; - identity.matrix = std::array,3>{{ - std::array{1.f, 0.f, 0.f, 0.f}, - std::array{0.f, 1.f, 0.f, 0.f}, - std::array{0.f, 0.f, 1.f, 0.f} - }}; - - // TASK02: Build a bottom level acceleration structure for each submesh - for (size_t i = 0; i < submeshes.size(); ++i) { - const auto& submesh = submeshes[i]; - - // Prepare the geometry data - auto trianglesData = vk::AccelerationStructureGeometryTrianglesDataKHR{ - .vertexFormat = vk::Format::eR32G32B32Sfloat, - .vertexData = vertexAddr, - .vertexStride = sizeof(Vertex), - .maxVertex = submesh.maxVertex, - .indexType = vk::IndexType::eUint32, - .indexData = indexAddr + submesh.indexOffset * sizeof(uint32_t) - }; - - vk::AccelerationStructureGeometryDataKHR geometryData(trianglesData); - - vk::AccelerationStructureGeometryKHR blasGeometry{ - .geometryType = vk::GeometryTypeKHR::eTriangles, - .geometry = geometryData, - .flags = vk::GeometryFlagBitsKHR::eOpaque - }; -#if LAB_TASK_LEVEL >= LAB_TASK_AS_OPAQUE_FLAG - // TASK07 - blasGeometry.flags = (submesh.alphaCut) ? vk::GeometryFlagsKHR(0) : vk::GeometryFlagBitsKHR::eOpaque; -#endif // LAB_TASK_LEVEL >= LAB_TASK_AS_OPAQUE_FLAG - - vk::AccelerationStructureBuildGeometryInfoKHR blasBuildGeometryInfo{ - .type = vk::AccelerationStructureTypeKHR::eBottomLevel, - .mode = vk::BuildAccelerationStructureModeKHR::eBuild, - .geometryCount = 1, - .pGeometries = &blasGeometry, - }; - - // Query the memory sizes that will be needed for this BLAS - auto primitiveCount = static_cast(submesh.indexCount / 3); - - vk::AccelerationStructureBuildSizesInfoKHR blasBuildSizes = - device.getAccelerationStructureBuildSizesKHR( - vk::AccelerationStructureBuildTypeKHR::eDevice, - blasBuildGeometryInfo, - { primitiveCount } - ); - - // Create a scratch buffer for the BLAS, this will hold temporary data - // during the build process - vk::raii::Buffer scratchBuffer = nullptr; - vk::raii::DeviceMemory scratchMemory = nullptr; - createBuffer(blasBuildSizes.buildScratchSize, - vk::BufferUsageFlagBits::eStorageBuffer | - vk::BufferUsageFlagBits::eShaderDeviceAddress, - vk::MemoryPropertyFlagBits::eDeviceLocal, - scratchBuffer, scratchMemory); - - // Save the scratch buffer address in the build info structure - vk::BufferDeviceAddressInfo scratchAddressInfo{ .buffer = *scratchBuffer }; - vk::DeviceAddress scratchAddr = device.getBufferAddressKHR(scratchAddressInfo); - blasBuildGeometryInfo.scratchData.deviceAddress = scratchAddr; - - // Create a buffer for the BLAS itself now that we now the required size - vk::raii::Buffer blasBuffer = nullptr; - vk::raii::DeviceMemory blasMemory = nullptr; - blasBuffers.emplace_back(std::move(blasBuffer)); - blasMemories.emplace_back(std::move(blasMemory)); - createBuffer(blasBuildSizes.accelerationStructureSize, - vk::BufferUsageFlagBits::eAccelerationStructureStorageKHR | - vk::BufferUsageFlagBits::eShaderDeviceAddress | - vk::BufferUsageFlagBits::eAccelerationStructureBuildInputReadOnlyKHR, - vk::MemoryPropertyFlagBits::eDeviceLocal, - blasBuffers[i], blasMemories[i]); - - // Create and store the BLAS handle - vk::AccelerationStructureCreateInfoKHR blasCreateInfo{ - .buffer = blasBuffers[i], - .offset = 0, - .size = blasBuildSizes.accelerationStructureSize, - .type = vk::AccelerationStructureTypeKHR::eBottomLevel, - }; - - blasHandles.emplace_back(device.createAccelerationStructureKHR(blasCreateInfo)); - - // Save the BLAS handle in the build info structure - blasBuildGeometryInfo.dstAccelerationStructure = blasHandles[i]; - - // Prepare the build range for the BLAS - vk::AccelerationStructureBuildRangeInfoKHR blasRangeInfo{ - .primitiveCount = primitiveCount, - .primitiveOffset = 0, - .firstVertex = submesh.firstVertex, - .transformOffset = 0 - }; - - // Build the BLAS - auto cmd = beginSingleTimeCommands(); - cmd->buildAccelerationStructuresKHR({ blasBuildGeometryInfo }, { &blasRangeInfo }); - endSingleTimeCommands(*cmd); - - // TASK03: Create a BLAS instance for the TLAS - vk::AccelerationStructureDeviceAddressInfoKHR addrInfo{ - .accelerationStructure = *blasHandles[i] - }; - vk::DeviceAddress blasDeviceAddr = device.getAccelerationStructureAddressKHR(addrInfo); - - vk::AccelerationStructureInstanceKHR instance{ - .transform = identity, - .mask = 0xFF, - .accelerationStructureReference = blasDeviceAddr - }; - - instances.push_back(instance); - -#if LAB_TASK_LEVEL >= LAB_TASK_INSTANCE_LUT - // TASK09: store the instance look-up table entry - instances[i].instanceCustomIndex = static_cast(i); - - instanceLUTs.push_back({ static_cast(submesh.materialID), submesh.indexOffset }); -#endif // LAB_TASK_LEVEL >= LAB_TASK_INSTANCE_LUT - } - - // TASK03: Prepare the instance data buffer - vk::DeviceSize instBufferSize = sizeof(instances[0]) * instances.size(); - createBuffer(instBufferSize, - vk::BufferUsageFlagBits::eShaderDeviceAddress | - vk::BufferUsageFlagBits::eTransferDst | - vk::BufferUsageFlagBits::eAccelerationStructureBuildInputReadOnlyKHR, - vk::MemoryPropertyFlagBits::eHostVisible | - vk::MemoryPropertyFlagBits::eHostCoherent, - instanceBuffer, instanceMemory); - - void *ptr = instanceMemory.mapMemory(0, instBufferSize); - memcpy(ptr, instances.data(), instBufferSize); - instanceMemory.unmapMemory(); - - vk::BufferDeviceAddressInfo instanceAddrInfo{ .buffer = instanceBuffer }; - vk::DeviceAddress instanceAddr = device.getBufferAddressKHR(instanceAddrInfo); - - // Prepare the geometry (instance) data - auto instancesData = vk::AccelerationStructureGeometryInstancesDataKHR{ - .arrayOfPointers = vk::False, - .data = instanceAddr - }; - - vk::AccelerationStructureGeometryDataKHR geometryData(instancesData); - - vk::AccelerationStructureGeometryKHR tlasGeometry{ - .geometryType = vk::GeometryTypeKHR::eInstances, - .geometry = geometryData - }; - - vk::AccelerationStructureBuildGeometryInfoKHR tlasBuildGeometryInfo{ - .type = vk::AccelerationStructureTypeKHR::eTopLevel, - .mode = vk::BuildAccelerationStructureModeKHR::eBuild, - .geometryCount = 1, - .pGeometries = &tlasGeometry - }; + vk::BufferDeviceAddressInfo vai{.buffer = *vertexBuffer}; + vk::DeviceAddress vertexAddr = device.getBufferAddressKHR(vai); + vk::BufferDeviceAddressInfo iai{.buffer = *indexBuffer}; + vk::DeviceAddress indexAddr = device.getBufferAddressKHR(iai); + + instances.reserve(submeshes.size()); + blasBuffers.reserve(submeshes.size()); + blasMemories.reserve(submeshes.size()); + blasHandles.reserve(submeshes.size()); + + vk::TransformMatrixKHR identity{}; + identity.matrix = std::array, 3>{{std::array{1.f, 0.f, 0.f, 0.f}, + std::array{0.f, 1.f, 0.f, 0.f}, + std::array{0.f, 0.f, 1.f, 0.f}}}; + + // TASK02: Build a bottom level acceleration structure for each submesh + for (size_t i = 0; i < submeshes.size(); ++i) + { + const auto &submesh = submeshes[i]; + + // Prepare the geometry data + auto trianglesData = vk::AccelerationStructureGeometryTrianglesDataKHR{ + .vertexFormat = vk::Format::eR32G32B32Sfloat, + .vertexData = vertexAddr, + .vertexStride = sizeof(Vertex), + .maxVertex = submesh.maxVertex, + .indexType = vk::IndexType::eUint32, + .indexData = indexAddr + submesh.indexOffset * sizeof(uint32_t)}; + + vk::AccelerationStructureGeometryDataKHR geometryData(trianglesData); + + vk::AccelerationStructureGeometryKHR blasGeometry{ + .geometryType = vk::GeometryTypeKHR::eTriangles, + .geometry = geometryData, + .flags = vk::GeometryFlagBitsKHR::eOpaque}; +# if LAB_TASK_LEVEL >= LAB_TASK_AS_OPAQUE_FLAG + // TASK07 + blasGeometry.flags = (submesh.alphaCut) ? vk::GeometryFlagsKHR(0) : vk::GeometryFlagBitsKHR::eOpaque; +# endif // LAB_TASK_LEVEL >= LAB_TASK_AS_OPAQUE_FLAG + + vk::AccelerationStructureBuildGeometryInfoKHR blasBuildGeometryInfo{ + .type = vk::AccelerationStructureTypeKHR::eBottomLevel, + .mode = vk::BuildAccelerationStructureModeKHR::eBuild, + .geometryCount = 1, + .pGeometries = &blasGeometry, + }; + + // Query the memory sizes that will be needed for this BLAS + auto primitiveCount = static_cast(submesh.indexCount / 3); + + vk::AccelerationStructureBuildSizesInfoKHR blasBuildSizes = + device.getAccelerationStructureBuildSizesKHR( + vk::AccelerationStructureBuildTypeKHR::eDevice, + blasBuildGeometryInfo, + {primitiveCount}); + + // Create a scratch buffer for the BLAS, this will hold temporary data + // during the build process + vk::raii::Buffer scratchBuffer = nullptr; + vk::raii::DeviceMemory scratchMemory = nullptr; + createBuffer(blasBuildSizes.buildScratchSize, + vk::BufferUsageFlagBits::eStorageBuffer | + vk::BufferUsageFlagBits::eShaderDeviceAddress, + vk::MemoryPropertyFlagBits::eDeviceLocal, + scratchBuffer, scratchMemory); + + // Save the scratch buffer address in the build info structure + vk::BufferDeviceAddressInfo scratchAddressInfo{.buffer = *scratchBuffer}; + vk::DeviceAddress scratchAddr = device.getBufferAddressKHR(scratchAddressInfo); + blasBuildGeometryInfo.scratchData.deviceAddress = scratchAddr; + + // Create a buffer for the BLAS itself now that we now the required size + vk::raii::Buffer blasBuffer = nullptr; + vk::raii::DeviceMemory blasMemory = nullptr; + blasBuffers.emplace_back(std::move(blasBuffer)); + blasMemories.emplace_back(std::move(blasMemory)); + createBuffer(blasBuildSizes.accelerationStructureSize, + vk::BufferUsageFlagBits::eAccelerationStructureStorageKHR | + vk::BufferUsageFlagBits::eShaderDeviceAddress | + vk::BufferUsageFlagBits::eAccelerationStructureBuildInputReadOnlyKHR, + vk::MemoryPropertyFlagBits::eDeviceLocal, + blasBuffers[i], blasMemories[i]); + + // Create and store the BLAS handle + vk::AccelerationStructureCreateInfoKHR blasCreateInfo{ + .buffer = blasBuffers[i], + .offset = 0, + .size = blasBuildSizes.accelerationStructureSize, + .type = vk::AccelerationStructureTypeKHR::eBottomLevel, + }; + + blasHandles.emplace_back(device.createAccelerationStructureKHR(blasCreateInfo)); + + // Save the BLAS handle in the build info structure + blasBuildGeometryInfo.dstAccelerationStructure = blasHandles[i]; + + // Prepare the build range for the BLAS + vk::AccelerationStructureBuildRangeInfoKHR blasRangeInfo{ + .primitiveCount = primitiveCount, + .primitiveOffset = 0, + .firstVertex = submesh.firstVertex, + .transformOffset = 0}; + + // Build the BLAS + auto cmd = beginSingleTimeCommands(); + cmd->buildAccelerationStructuresKHR({blasBuildGeometryInfo}, {&blasRangeInfo}); + endSingleTimeCommands(*cmd); + + // TASK03: Create a BLAS instance for the TLAS + vk::AccelerationStructureDeviceAddressInfoKHR addrInfo{ + .accelerationStructure = *blasHandles[i]}; + vk::DeviceAddress blasDeviceAddr = device.getAccelerationStructureAddressKHR(addrInfo); + + vk::AccelerationStructureInstanceKHR instance{ + .transform = identity, + .mask = 0xFF, + .accelerationStructureReference = blasDeviceAddr}; + + instances.push_back(instance); + +# if LAB_TASK_LEVEL >= LAB_TASK_INSTANCE_LUT + // TASK09: store the instance look-up table entry + instances[i].instanceCustomIndex = static_cast(i); + + instanceLUTs.push_back({static_cast(submesh.materialID), submesh.indexOffset}); +# endif // LAB_TASK_LEVEL >= LAB_TASK_INSTANCE_LUT + } + + // TASK03: Prepare the instance data buffer + vk::DeviceSize instBufferSize = sizeof(instances[0]) * instances.size(); + createBuffer(instBufferSize, + vk::BufferUsageFlagBits::eShaderDeviceAddress | + vk::BufferUsageFlagBits::eTransferDst | + vk::BufferUsageFlagBits::eAccelerationStructureBuildInputReadOnlyKHR, + vk::MemoryPropertyFlagBits::eHostVisible | + vk::MemoryPropertyFlagBits::eHostCoherent, + instanceBuffer, instanceMemory); + + void *ptr = instanceMemory.mapMemory(0, instBufferSize); + memcpy(ptr, instances.data(), instBufferSize); + instanceMemory.unmapMemory(); + + vk::BufferDeviceAddressInfo instanceAddrInfo{.buffer = instanceBuffer}; + vk::DeviceAddress instanceAddr = device.getBufferAddressKHR(instanceAddrInfo); + + // Prepare the geometry (instance) data + auto instancesData = vk::AccelerationStructureGeometryInstancesDataKHR{ + .arrayOfPointers = vk::False, + .data = instanceAddr}; + + vk::AccelerationStructureGeometryDataKHR geometryData(instancesData); + + vk::AccelerationStructureGeometryKHR tlasGeometry{ + .geometryType = vk::GeometryTypeKHR::eInstances, + .geometry = geometryData}; + + vk::AccelerationStructureBuildGeometryInfoKHR tlasBuildGeometryInfo{ + .type = vk::AccelerationStructureTypeKHR::eTopLevel, + .mode = vk::BuildAccelerationStructureModeKHR::eBuild, + .geometryCount = 1, + .pGeometries = &tlasGeometry}; + +# if LAB_TASK_LEVEL >= LAB_TASK_AS_ANIMATION + tlasBuildGeometryInfo.flags = vk::BuildAccelerationStructureFlagBitsKHR::eAllowUpdate; +# endif // LAB_TASK_LEVEL >= LAB_TASK_AS_ANIMATION + + // Query the memory sizes that will be needed for this TLAS + auto primitiveCount = static_cast(instances.size()); + + vk::AccelerationStructureBuildSizesInfoKHR tlasBuildSizes = + device.getAccelerationStructureBuildSizesKHR( + vk::AccelerationStructureBuildTypeKHR::eDevice, + tlasBuildGeometryInfo, + {primitiveCount}); + + // Create a scratch buffer for the TLAS, this will hold temporary data + // during the build process + createBuffer( + tlasBuildSizes.buildScratchSize, + vk::BufferUsageFlagBits::eStorageBuffer | + vk::BufferUsageFlagBits::eShaderDeviceAddress, + vk::MemoryPropertyFlagBits::eDeviceLocal, + tlasScratchBuffer, tlasScratchMemory); + + // Save the scratch buffer address in the build info structure + vk::BufferDeviceAddressInfo scratchAddressInfo{.buffer = *tlasScratchBuffer}; + vk::DeviceAddress scratchAddr = device.getBufferAddressKHR(scratchAddressInfo); + tlasBuildGeometryInfo.scratchData.deviceAddress = scratchAddr; + + // Create a buffer for the TLAS itself now that we now the required size + createBuffer( + tlasBuildSizes.accelerationStructureSize, + vk::BufferUsageFlagBits::eAccelerationStructureStorageKHR | + vk::BufferUsageFlagBits::eShaderDeviceAddress | + vk::BufferUsageFlagBits::eAccelerationStructureBuildInputReadOnlyKHR, + vk::MemoryPropertyFlagBits::eDeviceLocal, + tlasBuffer, tlasMemory); + + // Create and store the TLAS handle + vk::AccelerationStructureCreateInfoKHR tlasCreateInfo{ + .buffer = tlasBuffer, + .offset = 0, + .size = tlasBuildSizes.accelerationStructureSize, + .type = vk::AccelerationStructureTypeKHR::eTopLevel, + }; -#if LAB_TASK_LEVEL >= LAB_TASK_AS_ANIMATION - tlasBuildGeometryInfo.flags = vk::BuildAccelerationStructureFlagBitsKHR::eAllowUpdate; -#endif // LAB_TASK_LEVEL >= LAB_TASK_AS_ANIMATION - - // Query the memory sizes that will be needed for this TLAS - auto primitiveCount = static_cast(instances.size()); - - vk::AccelerationStructureBuildSizesInfoKHR tlasBuildSizes = - device.getAccelerationStructureBuildSizesKHR( - vk::AccelerationStructureBuildTypeKHR::eDevice, - tlasBuildGeometryInfo, - { primitiveCount } - ); - - // Create a scratch buffer for the TLAS, this will hold temporary data - // during the build process - createBuffer( - tlasBuildSizes.buildScratchSize, - vk::BufferUsageFlagBits::eStorageBuffer | - vk::BufferUsageFlagBits::eShaderDeviceAddress, - vk::MemoryPropertyFlagBits::eDeviceLocal, - tlasScratchBuffer, tlasScratchMemory - ); - - // Save the scratch buffer address in the build info structure - vk::BufferDeviceAddressInfo scratchAddressInfo{ .buffer = *tlasScratchBuffer }; - vk::DeviceAddress scratchAddr = device.getBufferAddressKHR(scratchAddressInfo); - tlasBuildGeometryInfo.scratchData.deviceAddress = scratchAddr; - - // Create a buffer for the TLAS itself now that we now the required size - createBuffer( - tlasBuildSizes.accelerationStructureSize, - vk::BufferUsageFlagBits::eAccelerationStructureStorageKHR | - vk::BufferUsageFlagBits::eShaderDeviceAddress | - vk::BufferUsageFlagBits::eAccelerationStructureBuildInputReadOnlyKHR, - vk::MemoryPropertyFlagBits::eDeviceLocal, - tlasBuffer, tlasMemory - ); - - // Create and store the TLAS handle - vk::AccelerationStructureCreateInfoKHR tlasCreateInfo{ - .buffer = tlasBuffer, - .offset = 0, - .size = tlasBuildSizes.accelerationStructureSize, - .type = vk::AccelerationStructureTypeKHR::eTopLevel, - }; - - tlas = device.createAccelerationStructureKHR(tlasCreateInfo); - - // Save the TLAS handle in the build info structure - tlasBuildGeometryInfo.dstAccelerationStructure = tlas; - - // Prepare the build range for the TLAS - vk::AccelerationStructureBuildRangeInfoKHR tlasRangeInfo{ - .primitiveCount = primitiveCount, - .primitiveOffset = 0, - .firstVertex = 0, - .transformOffset = 0 - }; - - // Build the TLAS - auto cmd = beginSingleTimeCommands(); - - cmd->buildAccelerationStructuresKHR({ tlasBuildGeometryInfo }, { &tlasRangeInfo }); - - endSingleTimeCommands(*cmd); -#endif // LAB_TASK_LEVEL >= LAB_TASK_AS_BUILD_AND_BIND - } - - void createDescriptorPool() { - std::array poolSize { - vk::DescriptorPoolSize( vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT), - vk::DescriptorPoolSize( vk::DescriptorType::eAccelerationStructureKHR, MAX_FRAMES_IN_FLIGHT), - vk::DescriptorPoolSize( vk::DescriptorType::eStorageBuffer, MAX_FRAMES_IN_FLIGHT * 3), // indices, UVs, instance LUT - vk::DescriptorPoolSize( vk::DescriptorType::eSampler, MAX_FRAMES_IN_FLIGHT), - vk::DescriptorPoolSize( vk::DescriptorType::eSampledImage, (uint32_t)materials.size()) - }; - vk::DescriptorPoolCreateInfo poolInfo{ - .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet | - vk::DescriptorPoolCreateFlagBits::eUpdateAfterBind, - .maxSets = MAX_FRAMES_IN_FLIGHT + 1, // + 1 for bindless materials - .poolSizeCount = static_cast(poolSize.size()), - .pPoolSizes = poolSize.data() - }; - descriptorPool = vk::raii::DescriptorPool(device, poolInfo); - } - - void createDescriptorSets() { - // Global descriptor sets (per frame) - std::vector globalLayouts(MAX_FRAMES_IN_FLIGHT, descriptorSetLayoutGlobal); - - vk::DescriptorSetAllocateInfo allocInfoGlobal{ - .descriptorPool = descriptorPool, - .descriptorSetCount = static_cast(globalLayouts.size()), - .pSetLayouts = globalLayouts.data() - }; - - globalDescriptorSets.clear(); - globalDescriptorSets = device.allocateDescriptorSets(allocInfoGlobal); - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - // Uniform buffer - vk::DescriptorBufferInfo bufferInfo{ - .buffer = uniformBuffers[i], - .offset = 0, - .range = sizeof(UniformBufferObject) - }; - - vk::WriteDescriptorSet bufferWrite{ - .dstSet = globalDescriptorSets[i], - .dstBinding = 0, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eUniformBuffer, - .pBufferInfo = &bufferInfo - }; + tlas = device.createAccelerationStructureKHR(tlasCreateInfo); + + // Save the TLAS handle in the build info structure + tlasBuildGeometryInfo.dstAccelerationStructure = tlas; + + // Prepare the build range for the TLAS + vk::AccelerationStructureBuildRangeInfoKHR tlasRangeInfo{ + .primitiveCount = primitiveCount, + .primitiveOffset = 0, + .firstVertex = 0, + .transformOffset = 0}; + + // Build the TLAS + auto cmd = beginSingleTimeCommands(); + + cmd->buildAccelerationStructuresKHR({tlasBuildGeometryInfo}, {&tlasRangeInfo}); + + endSingleTimeCommands(*cmd); +#endif // LAB_TASK_LEVEL >= LAB_TASK_AS_BUILD_AND_BIND + } + + void createDescriptorPool() + { + std::array poolSize{ + vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT), + vk::DescriptorPoolSize(vk::DescriptorType::eAccelerationStructureKHR, MAX_FRAMES_IN_FLIGHT), + vk::DescriptorPoolSize(vk::DescriptorType::eStorageBuffer, MAX_FRAMES_IN_FLIGHT * 3), // indices, UVs, instance LUT + vk::DescriptorPoolSize(vk::DescriptorType::eSampler, MAX_FRAMES_IN_FLIGHT), + vk::DescriptorPoolSize(vk::DescriptorType::eSampledImage, (uint32_t) materials.size())}; + vk::DescriptorPoolCreateInfo poolInfo{ + .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet | + vk::DescriptorPoolCreateFlagBits::eUpdateAfterBind, + .maxSets = MAX_FRAMES_IN_FLIGHT + 1, // + 1 for bindless materials + .poolSizeCount = static_cast(poolSize.size()), + .pPoolSizes = poolSize.data()}; + descriptorPool = vk::raii::DescriptorPool(device, poolInfo); + } + + void createDescriptorSets() + { + // Global descriptor sets (per frame) + std::vector globalLayouts(MAX_FRAMES_IN_FLIGHT, descriptorSetLayoutGlobal); + + vk::DescriptorSetAllocateInfo allocInfoGlobal{ + .descriptorPool = descriptorPool, + .descriptorSetCount = static_cast(globalLayouts.size()), + .pSetLayouts = globalLayouts.data()}; + + globalDescriptorSets.clear(); + globalDescriptorSets = device.allocateDescriptorSets(allocInfoGlobal); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + // Uniform buffer + vk::DescriptorBufferInfo bufferInfo{ + .buffer = uniformBuffers[i], + .offset = 0, + .range = sizeof(UniformBufferObject)}; + + vk::WriteDescriptorSet bufferWrite{ + .dstSet = globalDescriptorSets[i], + .dstBinding = 0, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eUniformBuffer, + .pBufferInfo = &bufferInfo}; #if LAB_TASK_LEVEL >= LAB_TASK_AS_BUILD_AND_BIND - // TASK04: define the acceleration structure descriptor. - vk::WriteDescriptorSetAccelerationStructureKHR asInfo{ - .accelerationStructureCount = 1, - .pAccelerationStructures = {&*tlas} - }; - - vk::WriteDescriptorSet asWrite{ - .pNext = &asInfo, - .dstSet = globalDescriptorSets[i], - .dstBinding = 1, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eAccelerationStructureKHR - }; -#endif // LAB_TASK_LEVEL >= LAB_TASK_AS_BUILD_AND_BIND - - // Indices SSBO - vk::DescriptorBufferInfo indexBufferInfo{ - .buffer = indexBuffer, - .offset = 0, - .range = sizeof(uint32_t) * indices.size() - }; - - vk::WriteDescriptorSet indexBufferWrite{ - .dstSet = globalDescriptorSets[i], - .dstBinding = 2, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eStorageBuffer, - .pBufferInfo = &indexBufferInfo - }; - - // UVs SSBO - vk::DescriptorBufferInfo uvBufferInfo{ - .buffer = uvBuffer, - .offset = 0, - .range = sizeof(glm::vec2) * vertices.size() - }; - - vk::WriteDescriptorSet uvBufferWrite{ - .dstSet = globalDescriptorSets[i], - .dstBinding = 3, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eStorageBuffer, - .pBufferInfo = &uvBufferInfo - }; + // TASK04: define the acceleration structure descriptor. + vk::WriteDescriptorSetAccelerationStructureKHR asInfo{ + .accelerationStructureCount = 1, + .pAccelerationStructures = {&*tlas}}; + + vk::WriteDescriptorSet asWrite{ + .pNext = &asInfo, + .dstSet = globalDescriptorSets[i], + .dstBinding = 1, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eAccelerationStructureKHR}; +#endif // LAB_TASK_LEVEL >= LAB_TASK_AS_BUILD_AND_BIND + + // Indices SSBO + vk::DescriptorBufferInfo indexBufferInfo{ + .buffer = indexBuffer, + .offset = 0, + .range = sizeof(uint32_t) * indices.size()}; + + vk::WriteDescriptorSet indexBufferWrite{ + .dstSet = globalDescriptorSets[i], + .dstBinding = 2, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eStorageBuffer, + .pBufferInfo = &indexBufferInfo}; + + // UVs SSBO + vk::DescriptorBufferInfo uvBufferInfo{ + .buffer = uvBuffer, + .offset = 0, + .range = sizeof(glm::vec2) * vertices.size()}; + + vk::WriteDescriptorSet uvBufferWrite{ + .dstSet = globalDescriptorSets[i], + .dstBinding = 3, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eStorageBuffer, + .pBufferInfo = &uvBufferInfo}; #if LAB_TASK_LEVEL >= LAB_TASK_INSTANCE_LUT - // TASK09: Instance LUT SSBO - vk::DescriptorBufferInfo instanceLUTBufferInfo{ - .buffer = instanceLUTBuffer, - .offset = 0, - .range = sizeof(InstanceLUT) * instanceLUTs.size() - }; - - vk::WriteDescriptorSet instanceLUTBufferWrite{ - .dstSet = globalDescriptorSets[i], - .dstBinding = 4, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eStorageBuffer, - .pBufferInfo = &instanceLUTBufferInfo - }; -#endif // LAB_TASK_LEVEL >= LAB_TASK_INSTANCE_LUT + // TASK09: Instance LUT SSBO + vk::DescriptorBufferInfo instanceLUTBufferInfo{ + .buffer = instanceLUTBuffer, + .offset = 0, + .range = sizeof(InstanceLUT) * instanceLUTs.size()}; + + vk::WriteDescriptorSet instanceLUTBufferWrite{ + .dstSet = globalDescriptorSets[i], + .dstBinding = 4, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eStorageBuffer, + .pBufferInfo = &instanceLUTBufferInfo}; +#endif // LAB_TASK_LEVEL >= LAB_TASK_INSTANCE_LUT #if LAB_TASK_LEVEL >= LAB_TASK_INSTANCE_LUT - // TASK09: Include the instance look-up table descriptor - std::array descriptorWrites{bufferWrite, asWrite, indexBufferWrite, uvBufferWrite, instanceLUTBufferWrite}; + // TASK09: Include the instance look-up table descriptor + std::array descriptorWrites{bufferWrite, asWrite, indexBufferWrite, uvBufferWrite, instanceLUTBufferWrite}; #elif LAB_TASK_LEVEL >= LAB_TASK_AS_BUILD_AND_BIND - // TASK04: Include the acceleration structure descriptor - std::array descriptorWrites{bufferWrite, asWrite, indexBufferWrite, uvBufferWrite}; + // TASK04: Include the acceleration structure descriptor + std::array descriptorWrites{bufferWrite, asWrite, indexBufferWrite, uvBufferWrite}; #else - std::array descriptorWrites{bufferWrite, indexBufferWrite, uvBufferWrite}; + std::array descriptorWrites{bufferWrite, indexBufferWrite, uvBufferWrite}; #endif - device.updateDescriptorSets(descriptorWrites, {}); - } - - // Material descriptor sets (per material) - std::vector variableCounts = { static_cast(textureImageViews.size()) }; - vk::DescriptorSetVariableDescriptorCountAllocateInfo variableCountInfo{ - .descriptorSetCount = 1, - .pDescriptorCounts = variableCounts.data() - }; - - std::vector layouts{ *descriptorSetLayoutMaterial }; - - vk::DescriptorSetAllocateInfo allocInfo { - .pNext = &variableCountInfo, - .descriptorPool = descriptorPool, - .descriptorSetCount = static_cast(layouts.size()), - .pSetLayouts = layouts.data() - }; - - materialDescriptorSets = device.allocateDescriptorSets(allocInfo); - - // Sampler - vk::DescriptorImageInfo samplerInfo{ - .sampler = textureSampler - }; - - vk::WriteDescriptorSet samplerWrite{ - .dstSet = materialDescriptorSets[0], - .dstBinding = 0, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eSampler, - .pImageInfo = &samplerInfo - }; - - device.updateDescriptorSets({samplerWrite}, {}); - - // Textures - std::vector imageInfos; - imageInfos.reserve(textureImageViews.size()); - for (auto& iv : textureImageViews) { - vk::DescriptorImageInfo imageInfo{ - .imageView = iv, - .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal - }; - imageInfos.push_back(imageInfo); - } - - vk::WriteDescriptorSet materialWrite{ - .dstSet = materialDescriptorSets[0], - .dstBinding = 1, - .dstArrayElement = 0, - .descriptorCount = static_cast(imageInfos.size()), - .descriptorType = vk::DescriptorType::eSampledImage, - .pImageInfo = imageInfos.data() - }; - - device.updateDescriptorSets({materialWrite}, {}); - } - - 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) - }; - vk::MemoryAllocateFlagsInfo allocFlagsInfo{}; - if (usage & vk::BufferUsageFlagBits::eShaderDeviceAddress) { - allocFlagsInfo.flags = vk::MemoryAllocateFlagBits::eDeviceAddress; - allocInfo.pNext = &allocFlagsInfo; - } - 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 }; - graphicsQueue.submit(submitInfo, nullptr); - graphicsQueue.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(); - graphicsQueue.submit(vk::SubmitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer }, nullptr); - graphicsQueue.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( - swapChainImages[imageIndex], - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // dstStage - vk::ImageAspectFlagBits::eColor - ); - // Transition depth image to depth attachment optimal layout - transition_image_layout( - *depthImage, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eDepthAttachmentOptimal, - vk::AccessFlagBits2::eDepthStencilAttachmentWrite, - vk::AccessFlagBits2::eDepthStencilAttachmentWrite, - vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, - vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, - vk::ImageAspectFlagBits::eDepth - ); - - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::ClearValue clearDepth = vk::ClearDepthStencilValue(1.0f, 0); - - /* TASK01: Check the setup for dynamic rendering - * - * With dynamic rendering, we specify the image view and load/store operations directly - * in the vk::RenderingAttachmentInfo structure. - * This approach eliminates the need for explicit render pass and framebuffer objects, - * simplifying the code and providing flexibility to change attachments at runtime. - */ - - 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 - }; - - // The vk::RenderingInfo structure combines these attachments with other rendering parameters. - vk::RenderingInfo renderingInfo = { - .renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent }, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &colorAttachmentInfo, - .pDepthAttachment = &depthAttachmentInfo - }; - - // Note: .beginRendering replaces the previous .beginRenderPass call. - 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, *globalDescriptorSets[currentFrame], nullptr); - commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 1, *materialDescriptorSets[0], nullptr); - - for (auto& sub : submeshes) { - // TASK09: Bindless resources - PushConstant pushConstant = { - .materialIndex = sub.materialID < 0 ? 0u : static_cast(sub.materialID), + device.updateDescriptorSets(descriptorWrites, {}); + } + + // Material descriptor sets (per material) + std::vector variableCounts = {static_cast(textureImageViews.size())}; + vk::DescriptorSetVariableDescriptorCountAllocateInfo variableCountInfo{ + .descriptorSetCount = 1, + .pDescriptorCounts = variableCounts.data()}; + + std::vector layouts{*descriptorSetLayoutMaterial}; + + vk::DescriptorSetAllocateInfo allocInfo{ + .pNext = &variableCountInfo, + .descriptorPool = descriptorPool, + .descriptorSetCount = static_cast(layouts.size()), + .pSetLayouts = layouts.data()}; + + materialDescriptorSets = device.allocateDescriptorSets(allocInfo); + + // Sampler + vk::DescriptorImageInfo samplerInfo{ + .sampler = textureSampler}; + + vk::WriteDescriptorSet samplerWrite{ + .dstSet = materialDescriptorSets[0], + .dstBinding = 0, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eSampler, + .pImageInfo = &samplerInfo}; + + device.updateDescriptorSets({samplerWrite}, {}); + + // Textures + std::vector imageInfos; + imageInfos.reserve(textureImageViews.size()); + for (auto &iv : textureImageViews) + { + vk::DescriptorImageInfo imageInfo{ + .imageView = iv, + .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal}; + imageInfos.push_back(imageInfo); + } + + vk::WriteDescriptorSet materialWrite{ + .dstSet = materialDescriptorSets[0], + .dstBinding = 1, + .dstArrayElement = 0, + .descriptorCount = static_cast(imageInfos.size()), + .descriptorType = vk::DescriptorType::eSampledImage, + .pImageInfo = imageInfos.data()}; + + device.updateDescriptorSets({materialWrite}, {}); + } + + 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)}; + vk::MemoryAllocateFlagsInfo allocFlagsInfo{}; + if (usage & vk::BufferUsageFlagBits::eShaderDeviceAddress) + { + allocFlagsInfo.flags = vk::MemoryAllocateFlagBits::eDeviceAddress; + allocInfo.pNext = &allocFlagsInfo; + } + 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}; + graphicsQueue.submit(submitInfo, nullptr); + graphicsQueue.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(); + graphicsQueue.submit(vk::SubmitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer}, nullptr); + graphicsQueue.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( + swapChainImages[imageIndex], + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // dstStage + vk::ImageAspectFlagBits::eColor); + // Transition depth image to depth attachment optimal layout + transition_image_layout( + *depthImage, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eDepthAttachmentOptimal, + vk::AccessFlagBits2::eDepthStencilAttachmentWrite, + vk::AccessFlagBits2::eDepthStencilAttachmentWrite, + vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, + vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, + vk::ImageAspectFlagBits::eDepth); + + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::ClearValue clearDepth = vk::ClearDepthStencilValue(1.0f, 0); + + /* TASK01: Check the setup for dynamic rendering + * + * With dynamic rendering, we specify the image view and load/store operations directly + * in the vk::RenderingAttachmentInfo structure. + * This approach eliminates the need for explicit render pass and framebuffer objects, + * simplifying the code and providing flexibility to change attachments at runtime. + */ + + 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}; + + // The vk::RenderingInfo structure combines these attachments with other rendering parameters. + vk::RenderingInfo renderingInfo = { + .renderArea = {.offset = {0, 0}, .extent = swapChainExtent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &colorAttachmentInfo, + .pDepthAttachment = &depthAttachmentInfo}; + + // Note: .beginRendering replaces the previous .beginRenderPass call. + 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, *globalDescriptorSets[currentFrame], nullptr); + commandBuffers[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 1, *materialDescriptorSets[0], nullptr); + + for (auto &sub : submeshes) + { + // TASK09: Bindless resources + PushConstant pushConstant = { + .materialIndex = sub.materialID < 0 ? 0u : static_cast(sub.materialID), #if LAB_TASK_LEVEL >= LAB_TASK_REFLECTIONS - .reflective = sub.reflective -#endif // LAB_TASK_LEVEL >= LAB_TASK_REFLECTIONS - }; - commandBuffers[currentFrame].pushConstants(pipelineLayout, vk::ShaderStageFlagBits::eFragment, 0, pushConstant); - - commandBuffers[currentFrame].drawIndexed(sub.indexCount, 1, sub.indexOffset, 0, 0); - } - - commandBuffers[currentFrame].endRendering(); - - // After rendering, transition the swapchain image to PRESENT_SRC - transition_image_layout( - swapChainImages[imageIndex], - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe, // dstStage - vk::ImageAspectFlagBits::eColor - ); - - commandBuffers[currentFrame].end(); - } - - void transition_image_layout( - vk::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 image_aspect_flags - ) { - 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 = image_aspect_flags, - .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(); - - auto eye = glm::vec3(2.0f, 2.0f, 2.0f); - - ubo.model = rotate(glm::mat4(1.0f), time * 0.1f * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.view = lookAt(eye, 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; - ubo.cameraPos = eye; - - memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); - } + .reflective = sub.reflective +#endif // LAB_TASK_LEVEL >= LAB_TASK_REFLECTIONS + }; + commandBuffers[currentFrame].pushConstants(pipelineLayout, vk::ShaderStageFlagBits::eFragment, 0, pushConstant); + + commandBuffers[currentFrame].drawIndexed(sub.indexCount, 1, sub.indexOffset, 0, 0); + } + + commandBuffers[currentFrame].endRendering(); + + // After rendering, transition the swapchain image to PRESENT_SRC + transition_image_layout( + swapChainImages[imageIndex], + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe, // dstStage + vk::ImageAspectFlagBits::eColor); + + commandBuffers[currentFrame].end(); + } + + void transition_image_layout( + vk::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 image_aspect_flags) + { + 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 = image_aspect_flags, + .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(); + + auto eye = glm::vec3(2.0f, 2.0f, 2.0f); + + ubo.model = rotate(glm::mat4(1.0f), time * 0.1f * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.view = lookAt(eye, 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; + ubo.cameraPos = eye; + + memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); + } #if LAB_TASK_LEVEL >= LAB_TASK_AS_ANIMATION - void updateTopLevelAS(const glm::mat4 & model) { - vk::TransformMatrixKHR tm{}; - auto &M = model; - tm.matrix = std::array,3>{{ - std::array{M[0][0], M[1][0], M[2][0], M[3][0]}, - std::array{M[0][1], M[1][1], M[2][1], M[3][1]}, - std::array{M[0][2], M[1][2], M[2][2], M[3][2]} - }}; - - // TASK06: update the instances to use the new transform matrix. - for (auto & instance : instances) { - instance.setTransform(tm); - } - - auto primitiveCount = static_cast(instances.size()); - vk::DeviceSize instBufferSize = sizeof(instances[0]) * primitiveCount; - - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(instBufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); - - void* dataStaging = stagingBufferMemory.mapMemory(0, instBufferSize); - memcpy(dataStaging, instances.data(), instBufferSize); - stagingBufferMemory.unmapMemory(); - - copyBuffer(stagingBuffer, instanceBuffer, instBufferSize); - - vk::BufferDeviceAddressInfo instanceAddrInfo{ .buffer = instanceBuffer }; - vk::DeviceAddress instanceAddr = device.getBufferAddressKHR(instanceAddrInfo); - - // Prepare the geometry (instance) data - auto instancesData = vk::AccelerationStructureGeometryInstancesDataKHR{ - .arrayOfPointers = vk::False, - .data = instanceAddr - }; - - vk::AccelerationStructureGeometryDataKHR geometryData(instancesData); - - vk::AccelerationStructureGeometryKHR tlasGeometry{ - .geometryType = vk::GeometryTypeKHR::eInstances, - .geometry = geometryData - }; - - // TASK06: Note the new parameters to re-build the TLAS in-place - vk::AccelerationStructureBuildGeometryInfoKHR tlasBuildGeometryInfo{ - .type = vk::AccelerationStructureTypeKHR::eTopLevel, - .flags = vk::BuildAccelerationStructureFlagBitsKHR::eAllowUpdate, - .mode = vk::BuildAccelerationStructureModeKHR::eUpdate, - .srcAccelerationStructure = tlas, - .dstAccelerationStructure = tlas, - .geometryCount = 1, - .pGeometries = &tlasGeometry - }; - - vk::BufferDeviceAddressInfo scratchAddressInfo{ .buffer = *tlasScratchBuffer }; - vk::DeviceAddress scratchAddr = device.getBufferAddressKHR(scratchAddressInfo); - tlasBuildGeometryInfo.scratchData.deviceAddress = scratchAddr; - - // Prepare the build range for the TLAS - vk::AccelerationStructureBuildRangeInfoKHR tlasRangeInfo{ - .primitiveCount = primitiveCount, - .primitiveOffset = 0, - .firstVertex = 0, - .transformOffset = 0 - }; - - // Re-build the TLAS - auto cmd = beginSingleTimeCommands(); - - // Pre-build barrier - vk::MemoryBarrier preBarrier { - .srcAccessMask = vk::AccessFlagBits::eAccelerationStructureWriteKHR | vk::AccessFlagBits::eTransferWrite | vk::AccessFlagBits::eShaderRead, - .dstAccessMask = vk::AccessFlagBits::eAccelerationStructureReadKHR | vk::AccessFlagBits::eAccelerationStructureWriteKHR - }; - - cmd->pipelineBarrier( - vk::PipelineStageFlagBits::eAccelerationStructureBuildKHR | vk::PipelineStageFlagBits::eTransfer | vk::PipelineStageFlagBits::eFragmentShader, // srcStageMask - vk::PipelineStageFlagBits::eAccelerationStructureBuildKHR, // dstStageMask - {}, // dependencyFlags - preBarrier, // memoryBarriers - {}, // bufferMemoryBarriers - {} // imageMemoryBarriers - ); - - cmd->buildAccelerationStructuresKHR({ tlasBuildGeometryInfo }, { &tlasRangeInfo }); - - // Post-build barrier - vk::MemoryBarrier postBarrier { - .srcAccessMask = vk::AccessFlagBits::eAccelerationStructureWriteKHR, - .dstAccessMask = vk::AccessFlagBits::eAccelerationStructureReadKHR | vk::AccessFlagBits::eShaderRead - }; - - cmd->pipelineBarrier( - vk::PipelineStageFlagBits::eAccelerationStructureBuildKHR, // srcStageMask - vk::PipelineStageFlagBits::eAccelerationStructureBuildKHR | vk::PipelineStageFlagBits::eFragmentShader, // dstStageMask - {}, // dependencyFlags - postBarrier, // memoryBarriers - {}, // bufferMemoryBarriers - {} // imageMemoryBarriers - ); - - endSingleTimeCommands(*cmd); - } -#endif // LAB_TASK_LEVEL >= LAB_TASK_AS_ANIMATION - - 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); + void updateTopLevelAS(const glm::mat4 &model) + { + vk::TransformMatrixKHR tm{}; + auto &M = model; + tm.matrix = std::array, 3>{{std::array{M[0][0], M[1][0], M[2][0], M[3][0]}, + std::array{M[0][1], M[1][1], M[2][1], M[3][1]}, + std::array{M[0][2], M[1][2], M[2][2], M[3][2]}}}; + + // TASK06: update the instances to use the new transform matrix. + for (auto &instance : instances) + { + instance.setTransform(tm); + } + + auto primitiveCount = static_cast(instances.size()); + vk::DeviceSize instBufferSize = sizeof(instances[0]) * primitiveCount; + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(instBufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + + void *dataStaging = stagingBufferMemory.mapMemory(0, instBufferSize); + memcpy(dataStaging, instances.data(), instBufferSize); + stagingBufferMemory.unmapMemory(); + + copyBuffer(stagingBuffer, instanceBuffer, instBufferSize); + + vk::BufferDeviceAddressInfo instanceAddrInfo{.buffer = instanceBuffer}; + vk::DeviceAddress instanceAddr = device.getBufferAddressKHR(instanceAddrInfo); + + // Prepare the geometry (instance) data + auto instancesData = vk::AccelerationStructureGeometryInstancesDataKHR{ + .arrayOfPointers = vk::False, + .data = instanceAddr}; + + vk::AccelerationStructureGeometryDataKHR geometryData(instancesData); + + vk::AccelerationStructureGeometryKHR tlasGeometry{ + .geometryType = vk::GeometryTypeKHR::eInstances, + .geometry = geometryData}; + + // TASK06: Note the new parameters to re-build the TLAS in-place + vk::AccelerationStructureBuildGeometryInfoKHR tlasBuildGeometryInfo{ + .type = vk::AccelerationStructureTypeKHR::eTopLevel, + .flags = vk::BuildAccelerationStructureFlagBitsKHR::eAllowUpdate, + .mode = vk::BuildAccelerationStructureModeKHR::eUpdate, + .srcAccelerationStructure = tlas, + .dstAccelerationStructure = tlas, + .geometryCount = 1, + .pGeometries = &tlasGeometry}; + + vk::BufferDeviceAddressInfo scratchAddressInfo{.buffer = *tlasScratchBuffer}; + vk::DeviceAddress scratchAddr = device.getBufferAddressKHR(scratchAddressInfo); + tlasBuildGeometryInfo.scratchData.deviceAddress = scratchAddr; + + // Prepare the build range for the TLAS + vk::AccelerationStructureBuildRangeInfoKHR tlasRangeInfo{ + .primitiveCount = primitiveCount, + .primitiveOffset = 0, + .firstVertex = 0, + .transformOffset = 0}; + + // Re-build the TLAS + auto cmd = beginSingleTimeCommands(); + + // Pre-build barrier + vk::MemoryBarrier preBarrier{ + .srcAccessMask = vk::AccessFlagBits::eAccelerationStructureWriteKHR | vk::AccessFlagBits::eTransferWrite | vk::AccessFlagBits::eShaderRead, + .dstAccessMask = vk::AccessFlagBits::eAccelerationStructureReadKHR | vk::AccessFlagBits::eAccelerationStructureWriteKHR}; + + cmd->pipelineBarrier( + vk::PipelineStageFlagBits::eAccelerationStructureBuildKHR | vk::PipelineStageFlagBits::eTransfer | vk::PipelineStageFlagBits::eFragmentShader, // srcStageMask + vk::PipelineStageFlagBits::eAccelerationStructureBuildKHR, // dstStageMask + {}, // dependencyFlags + preBarrier, // memoryBarriers + {}, // bufferMemoryBarriers + {} // imageMemoryBarriers + ); + + cmd->buildAccelerationStructuresKHR({tlasBuildGeometryInfo}, {&tlasRangeInfo}); + + // Post-build barrier + vk::MemoryBarrier postBarrier{ + .srcAccessMask = vk::AccessFlagBits::eAccelerationStructureWriteKHR, + .dstAccessMask = vk::AccessFlagBits::eAccelerationStructureReadKHR | vk::AccessFlagBits::eShaderRead}; + + cmd->pipelineBarrier( + vk::PipelineStageFlagBits::eAccelerationStructureBuildKHR, // srcStageMask + vk::PipelineStageFlagBits::eAccelerationStructureBuildKHR | vk::PipelineStageFlagBits::eFragmentShader, // dstStageMask + {}, // dependencyFlags + postBarrier, // memoryBarriers + {}, // bufferMemoryBarriers + {} // imageMemoryBarriers + ); + + endSingleTimeCommands(*cmd); + } +#endif // LAB_TASK_LEVEL >= LAB_TASK_AS_ANIMATION + + 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); #if LAB_TASK_LEVEL >= LAB_TASK_AS_ANIMATION - // TASK06: Update the TLAS with the current model matrix - updateTopLevelAS(ubo.model); -#endif // LAB_TASK_LEVEL >= LAB_TASK_AS_ANIMATION - - 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] }; - graphicsQueue.submit(submitInfo, *inFlightFences[currentFrame]); - - - const vk::PresentInfoKHR presentInfoKHR{ .waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], - .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex }; - result = presentQueue.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; - } + // TASK06: Update the TLAS with the current model matrix + updateTopLevelAS(ubo.model); +#endif // LAB_TASK_LEVEL >= LAB_TASK_AS_ANIMATION + + 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]}; + graphicsQueue.submit(submitInfo, *inFlightFences[currentFrame]); + + const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, .pWaitSemaphores = &*renderFinishedSemaphore[imageIndex], .swapchainCount = 1, .pSwapchains = &*swapChain, .pImageIndices = &imageIndex}; + result = presentQueue.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 { - VulkanRaytracingApplication app; - app.run(); - } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; +int main() +{ + try + { + VulkanRaytracingApplication app; + app.run(); + } + catch (const std::exception &e) + { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; }