diff --git a/00_Introduction.md b/00_Introduction.md index d507ce21..7c488b6c 100644 --- a/00_Introduction.md +++ b/00_Introduction.md @@ -38,7 +38,7 @@ tutorial: This tutorial will not assume knowledge of OpenGL or Direct3D concepts, but it does require you to know the basics of 3D computer graphics. It will not explain -the math behind perspective projection, for example. See [this online book](https://www.docdroid.net/UKocmTz/arcsynthesis.pdf.html) +the math behind perspective projection, for example. See [this online book](http://opengl.datenwolf.net/gltut/html/index.html) for a great introduction of computer graphics concepts. You can use C instead of C++ if you want, but you will have to use a different @@ -46,6 +46,14 @@ linear algebra library and you will be on your own in terms of code structuring. We will use C++ features like classes and RAII to organize logic and resource lifetimes. +## E-book + +If you prefer to read this tutorial as an e-book, then you can download an EPUB +or PDF version here: + +* [EPUB](https://raw.githubusercontent.com/Overv/VulkanTutorial/master/ebook/Vulkan%20Tutorial.epub) +* [PDF](https://raw.githubusercontent.com/Overv/VulkanTutorial/master/ebook/Vulkan%20Tutorial.pdf) + ## Tutorial structure We'll start with an overview of how Vulkan works and the work we'll have to do @@ -91,6 +99,7 @@ This tutorial is intended to be a community effort. Vulkan is still a very new API and best practices have not really been established yet. If you have any type of feedback on the tutorial and site itself, then please don't hesitate to submit an issue or pull request to the [GitHub repository](https://github.com/Overv/VulkanTutorial). +You can *watch* the repository to be notified of updates to the tutorial. After you've gone through the ritual of drawing your very first Vulkan powered triangle onscreen, we'll start expanding the program to include linear @@ -104,4 +113,9 @@ in mind that once you have that boring looking triangle, drawing fully textured 3D models does not take that much extra work, and each step beyond that point is much more rewarding. +If you encounter any problems while following the tutorial, then first check the +FAQ to see if your problem and its solution is already listed there. If you are +still stuck after that, then feel free to ask for help in the comment section of +the closest related chapter. + Ready to dive into the future of high performance graphics APIs? [Let's go!](!Overview) diff --git a/02_Development_environment.md b/02_Development_environment.md index be964b44..50204559 100644 --- a/02_Development_environment.md +++ b/02_Development_environment.md @@ -1,6 +1,6 @@ In this chapter we'll set up your environment for developing Vulkan applications and install some useful libraries. All of the tools we'll use, with the -exception of the compiler, are compatible with both Windows and Linux, but the +exception of the compiler, are compatible with Windows, Linux and MacOS, but the steps for installing them differ a bit, which is why they're described separately here. @@ -62,7 +62,7 @@ As mentioned before, Vulkan by itself is a platform agnostic API and does not include tools for creating a window to display the rendered results. To benefit from the cross-platform advantages of Vulkan and to avoid the horrors of Win32, we'll use the [GLFW library](http://www.glfw.org/) to create a window, which -supports both Windows and Linux. There are other libraries available for this +supports Windows, Linux and MacOS. There are other libraries available for this purpose, like [SDL](https://www.libsdl.org/), but the advantage of GLFW is that it also abstracts away some of the other platform-specific things in Vulkan besides just window creation. @@ -196,21 +196,7 @@ and a window pop up like this: ![](/images/vs_test_window.png) The number of extensions should be non-zero. Congratulations, you're all set for -playing with Vulkan! - -To avoid having to repeat this work all over again every time, you can create a -template from it. Select `File -> Export Template...`. Select `Project template` -and fill in a nice name and description for the template. - -![](/images/vs_export_template.png) - -Press `Finish` and you should now have a handy template in the `New Project` -dialog! Use it to create a `Hello Triangle` project as preparation for the next -chapter. - -![](/images/vs_template.png) - -You are now all set for [the real adventure](!Drawing_a_triangle/Setup/Base_code). +[playing with Vulkan](!Drawing_a_triangle/Setup/Base_code)! ## Linux @@ -280,7 +266,7 @@ As mentioned before, Vulkan by itself is a platform agnostic API and does not include tools for creation a window to display the rendered results. To benefit from the cross-platform advantages of Vulkan and to avoid the horrors of X11, we'll use the [GLFW library](http://www.glfw.org/) to create a window, which -supports both Windows and Linux. There are other libraries available for this +supports Windows, Linux and MacOS. There are other libraries available for this purpose, like [SDL](https://www.libsdl.org/), but the advantage of GLFW is that it also abstracts away some of the other platform-specific things in Vulkan besides just window creation. @@ -501,3 +487,132 @@ offline version of the entire Vulkan specification. Feel free to explore the other files, but we won't need them for this tutorial. You are now all set for [the real adventure](!Drawing_a_triangle/Setup/Base_code). + +## MacOS + +These instructions will assume you are using Xcode and the [Homebrew package manager](https://brew.sh/). Also, keep in mind that you will need at least MacOS version 10.11, and your device needs to support the [Metal API](https://en.wikipedia.org/wiki/Metal_(API)#Supported_GPUs). + +### Vulkan SDK + +The most important component you'll need for developing Vulkan applications is the SDK. It includes the headers, standard validation layers, debugging tools and a loader for the Vulkan functions. The loader looks up the functions in the driver at runtime, similarly to GLEW for OpenGL - if you're familiar with that. + +The SDK can be downloaded from [the LunarG website](https://vulkan.lunarg.com/) using the buttons at the bottom of the page. You don't have to create an account, but it will give you access to some additional documentation that may be useful to you. + +![](/images/vulkan_sdk_download_buttons.png) + +The SDK version for MacOS internally uses [MoltenVK](https://moltengl.com/). There is no native support for Vulkan on MacOS, so what MoltenVK does is actually act as a layer that translates Vulkan API calls to Apple's Metal graphics framework. With this you can take advantage of debugging and performance benefits of Apple's Metal framework. + +After downloading it, simply extract the contents to a folder of your choice (keep in mind you will need to reference it when creating your projects on Xcode). Inside the extracted folder, in the `Applications` folder you should have some executable files that will run a few demos using the SDK. Run the `cube` executable and you will see the following: + +![](/images/cube_demo_mac.png) + +### GLFW + +As mentioned before, Vulkan by itself is a platform agnostic API and does not include tools for creation a window to display the rendered results. We'll use the [GLFW library](http://www.glfw.org/) to create a window, which supports Windows, Linux and MacOS. There are other libraries available for this purpose, like [SDL](https://www.libsdl.org/), but the advantage of GLFW is that it also abstracts away some of the other platform-specific things in Vulkan besides just window creation. + +To install GLFW on MacOS we will use the Homebrew package manager. Vulkan support for MacOS is still not fully available on the current (at the time of this writing) stable version 3.2.1. Therefore we will install the latest version of the `glfw3` package using: + +```bash +brew install glfw3 --HEAD +``` + +### GLM + +Vulkan does not include a library for linear algebra operations, so we'll have to download one. [GLM](http://glm.g-truc.net/) is a nice library that is designed for use with graphics APIs and is also commonly used with OpenGL. + +It is a header-only library that can be installed from the `glm` package: + +```bash +brew install glm +``` + +### Setting up Xcode + +Now that all the dependencies are installed we can set up a basic Xcode project for Vulkan. Most of the instructions here are essentially a lot of "plumbing" so we can get all the dependencies linked to the project. Also, keep in mind that during the following instructions whenever we mention the folder `vulkansdk` we are refering to the folder where you extracted the Vulkan SDK. + +Start Xcode and create a new Xcode project. On the window that will open select Application > Command Line Tool. + +![](/images/xcode_new_project.png) + +Select `Next`, write a name for the project and for `Language` select `C++`. + +![](/images/xcode_new_project_2.png) + +Press `Next` and the project should have been created. Now, let's change the code in the generated `main.cpp` file to the following code: + +```c++ +#define GLFW_INCLUDE_VULKAN +#include + +#define GLM_FORCE_RADIANS +#define GLM_FORCE_DEPTH_ZERO_TO_ONE +#include +#include + +#include + +int main() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + GLFWwindow* window = glfwCreateWindow(800, 600, "Vulkan window", nullptr, nullptr); + + uint32_t extensionCount = 0; + vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr); + + std::cout << extensionCount << " extensions supported" << std::endl; + + glm::mat4 matrix; + glm::vec4 vec; + auto test = matrix * vec; + + while(!glfwWindowShouldClose(window)) { + glfwPollEvents(); + } + + glfwDestroyWindow(window); + + glfwTerminate(); + + return 0; +} +``` + +Keep in mind you are not required to understand all this code is doing yet, we are just setting up some API calls to make sure everything is working. + +Xcode should already be showing some errors such as libraries it cannot find. We will now start configuring the project to get rid of those errors. On the *Project Navigator* panel select your project. Open the *Build Settings* tab and then: + +* Find the **Header Search Paths** field and add a link to `/usr/local/include` (this is where Homebrew installs headers, so the glm and glfw3 header files should be there) and a link to `vulkansdk/macOS/include` for the Vulkan headers. +* Find the **Library Search Paths** field and add a link to `/usr/local/lib` (again, this is where Homebrew installs libraries, so the glm and glfw3 lib files should be there) and a link to `vulkansdk/macOS/lib`. + +It should look like so (obviously, paths will be different depending on where you placed on your files): + +![](/images/xcode_paths.png) + +Now, in the *Build Phases* tab, on **Link Binary With Libraries** we will add both the `glfw3` and the `vulkan` frameworks. To make things easier we will be adding he dynamic libraries in the project (you can check the documentation of these libraries if you want to use the static frameworks). + +* For glfw open the folder `/usr/local/lib` and there you will find a file name like `libglfw.3.x.dylib` ("x" is the library's version number, it might be different depending on when you downloaded the package from Homebrew). Simply drag that file to the Linked Frameworks and Libraries tab on Xcode. +* For vulkan, go to `vulkansdk/macOS/lib`. Do the same for the file both files `libvulkan.1.dylib` and `libvulkan.1.x.xx.dylib` (where "x" will be the version number of the the SDK you downloaded). + +After adding those libraries, in the same tab on **Copy Files** change `Destination` to "Frameworks", clear the subpath and deselect "Copy only when installing". Click on the "+" sign and add all those three frameworks here aswell. + +Your Xcode configuration should look like: + +![](/images/xcode_frameworks.png) + +The last thing you need to setup are a couple of environment variables. On Xcode toolbar go to `Product` > `Scheme` > `Edit Scheme...`, and in the `Arguments` tab add the two following environment variables: + +* VK_ICD_FILENAMES = `vulkansdk/macOS/etc/vulkan/icd.d/MoltenVK_icd.json` +* VK_LAYER_PATH = `vulkansdk/macOS/etc/vulkan/explicit_layer.d` + +It should look like so: + +![](/images/xcode_variables.png) + +Finally, you should be all set! Now if you run the project (remembering to setting the build configuration to Debug or Release depending on the configuration you chose) you should see the following: + +![](/images/xcode_output.png) + +The number of extensions should be non-zero. The other logs are from the libraries, you might get different messages from those depending on your configuration. + +You are now all set for [the real thing](!Drawing_a_triangle/Setup/Base_code). diff --git a/03_Drawing_a_triangle/00_Setup/00_Base_code.md b/03_Drawing_a_triangle/00_Setup/00_Base_code.md index c5e6d59f..9bce1e78 100644 --- a/03_Drawing_a_triangle/00_Setup/00_Base_code.md +++ b/03_Drawing_a_triangle/00_Setup/00_Base_code.md @@ -10,12 +10,14 @@ from scratch with the following code: #include #include #include +#include class HelloTriangleApplication { public: void run() { initVulkan(); mainLoop(); + cleanup(); } private: @@ -26,6 +28,10 @@ private: void mainLoop() { } + + void cleanup() { + + } }; int main() { @@ -33,7 +39,7 @@ int main() { try { app.run(); - } catch (const std::runtime_error& e) { + } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; } @@ -45,173 +51,52 @@ int main() { We first include the Vulkan header from the LunarG SDK, which provides the functions, structures and enumerations. The `stdexcept` and `iostream` headers are included for reporting and propagating errors. The `functional` headers will -be used for a lambda functions in the resource management section. +be used for a lambda functions in the resource management section. The `cstdlib` +header provides the `EXIT_SUCCESS` and `EXIT_FAILURE` macros. The program itself is wrapped into a class where we'll store the Vulkan objects as private class members and add functions to initiate each of them, which will be called from the `initVulkan` function. Once everything has been prepared, we enter the main loop to start rendering frames. We'll fill in the `mainLoop` function to include a loop that iterates until the window is closed in a moment. +Once the window is closed and `mainLoop` returns, we'll make sure to deallocate +the resources we've used in the `cleanup` function. If any kind of fatal error occurs during execution then we'll throw a `std::runtime_error` exception with a descriptive message, which will propagate -back to the `main` function and be printed to the command prompt. One example of -an error that we will deal with soon is finding out that a certain required -extension is not supported. +back to the `main` function and be printed to the command prompt. To handle +a variety of standard exception types as well, we catch the more general `std::exception`. One example of an error that we will deal with soon is finding +out that a certain required extension is not supported. Roughly every chapter that follows after this one will add one new function that will be called from `initVulkan` and one or more new Vulkan objects to the -private class members. +private class members that need to be freed at the end in `cleanup`. ## Resource management -You may have noticed that there's no cleanup function anywhere to be seen and -that is intentional. Every Vulkan object needs to be destroyed with a function -call when it's no longer needed, just like each chunk of memory allocated with -`malloc` requires a call to `free`. Doing that manually is a lot of work and is -very error-prone, but we can completely avoid that by taking advantage of the -C++ [RAII](https://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization) -principle. To do that, we're going to create a class that wraps Vulkan objects -and automatically cleans them up when it goes out of scope, for example because -the application was closed. - -First consider the interface we want from this `VDeleter` wrapper class. -Let's say we want to store a `VkInstance` object that should be destroyed with -`vkDestroyInstance` at some point. Then we would add the following class member: - -```c++ -VDeleter instance{vkDestroyInstance}; -``` - -The template argument specifies the type of Vulkan object we want to wrap and -the constructor argument specifies the function to use to clean up the object -when it goes out of scope. - -To assign an object to the wrapper, we would simply want to pass its pointer to -the creation function as if it was a normal `VkInstance` variable: - -```c++ -vkCreateInstance(&instanceCreateInfo, nullptr, &instance); -``` - -Unfortunately, taking the address of the handle in the wrapper doesn't -necessarily mean that we want to overwrite its existing value. A common pattern -is to simply use `&instance` as short-hand for an array of instances with 1 -item. If we intend to write a new handle, then the wrapper should clean up any -previous object to not leak memory. Therefore it would be better to have the `&` -operator return a constant pointer and have an explicit function to state that -we wish to replace the handle. The `replace` function calls clean up for any -existing handle and then gives you a non-const pointer to overwrite the handle: - -```c++ -vkCreateInstance(&instanceCreateInfo, nullptr, instance.replace()); -``` - -Just like that we can now use the `instance` variable wherever a `VkInstance` -would normally be accepted. We no longer have to worry about cleaning up -anymore, because that will automatically happen once the `instance` variable -becomes unreachable! That's pretty easy, right? - -The implementation of such a wrapper class is fairly straightforward. It just -requires a bit of lambda magic to shorten the syntax for specifying the cleanup -functions. - -```c++ -template -class VDeleter { -public: - VDeleter() : VDeleter([](T, VkAllocationCallbacks*) {}) {} - - VDeleter(std::function deletef) { - this->deleter = [=](T obj) { deletef(obj, nullptr); }; - } - - VDeleter(const VDeleter& instance, std::function deletef) { - this->deleter = [&instance, deletef](T obj) { deletef(instance, obj, nullptr); }; - } - - VDeleter(const VDeleter& device, std::function deletef) { - this->deleter = [&device, deletef](T obj) { deletef(device, obj, nullptr); }; - } - - ~VDeleter() { - cleanup(); - } - - const T* operator &() const { - return &object; - } - - T* replace() { - cleanup(); - return &object; - } - - operator T() const { - return object; - } - - void operator=(T rhs) { - if (rhs != object) { - cleanup(); - object = rhs; - } - } - - template - bool operator==(V rhs) { - return object == T(rhs); - } - -private: - T object{VK_NULL_HANDLE}; - std::function deleter; - - void cleanup() { - if (object != VK_NULL_HANDLE) { - deleter(object); - } - object = VK_NULL_HANDLE; - } -}; -``` - -The three non-default constructors allow you to specify all three types of -deletion functions used in Vulkan: - -* `vkDestroyXXX(object, callbacks)`: Only the object itself needs to be passed -to the cleanup function, so we can simply construct a `VDeleter` with just the -function as argument. -* `vkDestroyXXX(instance, object, callbacks)`: A `VkInstance` also -needs to be passed to the cleanup function, so we use the `VDeleter` constructor -that takes the `VkInstance` reference and cleanup function as parameters. -* `vkDestroyXXX(device, object, callbacks)`: Similar to the previous case, but a -`VkDevice` must be passed instead of a `VkInstance`. - -The `callbacks` parameter is optional and we always pass `nullptr` to it, as you -can see in the `VDeleter` definition. - -All of the constructors initialize the object handle with the equivalent of -`nullptr` in Vulkan: `VK_NULL_HANDLE`. Any extra arguments that are needed for -the deleter functions must also be passed, usually the parent object. It -overloads the address-of, assignment, comparison and casting operators to make -the wrapper as transparent as possible. When the wrapped object goes out of -scope, the destructor is invoked, which in turn calls the cleanup function we -specified. - -The address-of operator returns a constant pointer to make sure that the object -within the wrapper is not unexpectedly changed. If you want to replace the -handle within the wrapper through a pointer, then you should use the `replace()` -function instead. It will invoke the cleanup function for the existing handle so -that you can safely overwrite it afterwards. - -There is also a default constructor with a dummy deleter function that can be -used to initialize it later, which will be useful for lists of deleters. - -I've added the class code between the headers and the `HelloTriangleApplication` -class definition. You can also choose to put it in a separate header file. We'll -use it for the first time in the next chapter where we'll create the very first -Vulkan object! +Just like each chunk of memory allocated with `malloc` requires a call to +`free`, every Vulkan object that we create needs to be explicitly destroyed when +we no longer need it. In modern C++ code it is possible to do automatic resource +management through the utilities in the `` header, but I've chosen to be +explicit about allocation and deallocation of Vulkan objects in this tutorial. +After all, Vulkan's niche is to be explicit about every operation to avoid +mistakes, so it's good to be explicit about the lifetime of objects to learn how +the API works. + +After following this tutorial, you could implement automatic resource management +by overloading `std::shared_ptr` for example. Using [RAII](https://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization) +to your advantage is the recommended approach for larger Vulkan programs, but +for learning purposes it's always good to know what's going on behind the +scenes. + +Vulkan objects are either created directly with functions like `vkCreateXXX`, or +allocated through another object with functions like `vkAllocateXXX`. After +making sure that an object is no longer used anywhere, you need to destroy it +with the counterparts `vkDestroyXXX` and `vkFreeXXX`. The parameters for these +functions generally vary for different types of objects, but there is one +parameter that they all share: `pAllocator`. This is an optional parameter that +allows you to specify callbacks for a custom memory allocator. We will ignore +this parameter in the tutorial and always pass `nullptr` as argument. ## Integrating GLFW @@ -234,6 +119,7 @@ void run() { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: @@ -305,21 +191,26 @@ void mainLoop() { while (!glfwWindowShouldClose(window)) { glfwPollEvents(); } +} +``` +This code should be fairly self-explanatory. It loops and checks for events like +pressing the X button until the window has been closed by the user. This is also +the loop where we'll later call a function to render a single frame. + +Once the window is closed, we need to clean up resources by destroying it and +terminating GLFW itself. This will be our first `cleanup` code: + +```c++ +void cleanup() { glfwDestroyWindow(window); glfwTerminate(); } ``` -This code should be fairly self-explanatory. It loops and checks for events like -pressing the X button until the window has been closed by the user. This is also -the loop where we'll later call a function to render a single frame. Once the -window is closed, we need to clean up resources by destroying it and GLFW] -itself. - When you run the program now you should see a window titled `Vulkan` show up until the application is terminated by closing the window. Now that we have the skeleton for the Vulkan application, let's [create the first Vulkan object](!Drawing_a_triangle/Setup/Instance)! -[C++ code](/code/base_code.cpp) +[C++ code](/code/00_base_code.cpp) diff --git a/03_Drawing_a_triangle/00_Setup/01_Instance.md b/03_Drawing_a_triangle/00_Setup/01_Instance.md index 19c8a7e5..18da659a 100644 --- a/03_Drawing_a_triangle/00_Setup/01_Instance.md +++ b/03_Drawing_a_triangle/00_Setup/01_Instance.md @@ -14,20 +14,13 @@ void initVulkan() { } ``` -Additionally add a class member to hold the handle to the instance, like we saw -in the resource management section of the previous chapter. +Additionally add a class member to hold the handle to the instance: ```c++ private: -VDeleter instance {vkDestroyInstance}; +VkInstance instance; ``` -The `vkDestroyInstance` function, as you might imagine, will clean up the -instance that we'll create in a moment. The second parameter is optional and -allows you to specify callbacks for a custom allocator. You'll see that most of -the creation and destroy functions have such a callback parameter and we'll -always pass a `nullptr` as argument, as seen in the `VDeleter` definition. - Now, to create an instance we'll first have to fill in a struct with some information about our application. This data is technically optional, but it may provide some useful information to the driver to optimize for our specific @@ -69,7 +62,7 @@ the window system. GLFW has a handy built-in function that returns the extension(s) it needs to do that which we can pass to the struct: ```c++ -unsigned int glfwExtensionCount = 0; +uint32_t glfwExtensionCount = 0; const char** glfwExtensions; glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); @@ -90,7 +83,7 @@ We've now specified everything Vulkan needs to create an instance and we can finally issue the `vkCreateInstance` call: ```c++ -VkResult result = vkCreateInstance(&createInfo, nullptr, instance.replace()); +VkResult result = vkCreateInstance(&createInfo, nullptr, &instance); ``` As you'll see, the general pattern that object creation function parameters in @@ -101,12 +94,13 @@ Vulkan follow is: * Pointer to the variable that stores the handle to the new object If everything went well then the handle to the instance was stored in the -wrapped `VkInstance` class member. Nearly all Vulkan functions return a value of -type `VkResult` that is either `VK_SUCCESS` or an error code. To check if the -instance was created successfully, simply add a check for the success value: +`VkInstance` class member. Nearly all Vulkan functions return a value of type +`VkResult` that is either `VK_SUCCESS` or an error code. To check if the +instance was created successfully, we don't need to store the result and can +just use a check for the success value instead: ```c++ -if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { +if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } ``` @@ -167,7 +161,28 @@ that checks if all of the extensions returned by `glfwGetRequiredInstanceExtensions` are included in the supported extensions list. +## Cleaning up + +The `VkInstance` should be only destroyed right before the program exits. It can +be destroyed in `cleanup` with the `vkDestroyInstance` function: + +```c++ +void cleanup() { + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); +} +``` + +The parameters for the `vkDestroyInstance` function are straightforward. As +mentioned in the previous chapter, the allocation and deallocation functions +in Vulkan have an optional allocator callback that we'll ignore by passing +`nullptr` to it. All of the other Vulkan resources that we'll create in the +following chapters should be cleaned up before the instance is destroyed. + Before continuing with the more complex steps after instance creation, it's time to evaluate our debugging options by checking out [validation layers](!Drawing_a_triangle/Setup/Validation_layers). -[C++ code](/code/instance_creation.cpp) +[C++ code](/code/01_instance_creation.cpp) diff --git a/03_Drawing_a_triangle/00_Setup/02_Validation_layers.md b/03_Drawing_a_triangle/00_Setup/02_Validation_layers.md index 5a9fa21f..dab5e251 100644 --- a/03_Drawing_a_triangle/00_Setup/02_Validation_layers.md +++ b/03_Drawing_a_triangle/00_Setup/02_Validation_layers.md @@ -45,7 +45,7 @@ the best of both worlds! Vulkan does not come with any validation layers built-in, but the LunarG Vulkan SDK provides a nice set of layers that check for common errors. They're also -completely [open source](https://github.com/LunarG/VulkanTools/tree/master/layers), +completely [open source](https://github.com/KhronosGroup/Vulkan-LoaderAndValidationLayers/tree/master/layers), so you can check which kind of mistakes they check for and contribute. Using the validation layers is the best way to avoid your application breaking on different drivers by accidentally relying on undefined behavior. @@ -54,10 +54,10 @@ Validation layers can only be used if they have been installed onto the system. For example, the LunarG validation layers are only available on PCs with the Vulkan SDK installed. -There were formerly two different types of validation layers in Vulkan. Instance -and device specific layers. The idea was that instance layers would only check -calls related to global Vulkan objects like instances and device specific layers -only calls related to a specific GPU. Device specific layers have now been +There were formerly two different types of validation layers in Vulkan: instance +and device specific. The idea was that instance layers would only check +calls related to global Vulkan objects like instances, and device specific layers +would only check calls related to a specific GPU. Device specific layers have now been deprecated, which means that instance validation layers apply to all Vulkan calls. The specification document still recommends that you enable validation layers at device level as well for compatibility, which is required by some @@ -93,7 +93,7 @@ const std::vector validationLayers = { ``` We'll add a new function `checkValidationLayerSupport` that checks if all of -the requested layers are available. First list all of the available extensions +the requested layers are available. First list all of the available layers using the `vkEnumerateInstanceLayerProperties` function. Its usage is identical to that of `vkEnumerateInstanceExtensionProperties` which was discussed in the instance creation chapter. @@ -155,7 +155,7 @@ validation layer names if they are enabled: ```c++ if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; @@ -178,15 +178,11 @@ not: ```c++ std::vector getRequiredExtensions() { - std::vector extensions; - - unsigned int glfwExtensionCount = 0; + uint32_t glfwExtensionCount = 0; const char** glfwExtensions; glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - for (unsigned int i = 0; i < glfwExtensionCount; i++) { - extensions.push_back(glfwExtensions[i]); - } + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); if (enableValidationLayers) { extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); @@ -205,7 +201,7 @@ We can now use this function in `createInstance`: ```c++ auto extensions = getRequiredExtensions(); -createInfo.enabledExtensionCount = extensions.size(); +createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); ``` @@ -248,10 +244,15 @@ any of the following bit flags: The `objType` parameter specifies the type of object that is the subject of the message. For example if `obj` is a `VkPhysicalDevice` then `objType` would be `VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_EXT`. This works because internally all -Vulkan handles are typedef'd as `uint64_t`. +Vulkan handles are typedef'd as `uint64_t`. The `msg` parameter contains the +pointer to the message itself. Finally, there's a `userData` parameter to pass +your own data to the callback. -The `msg` parameter contains the pointer to the message itself. Finally, there's -a `userData` parameter to pass your own data to the callback. +The callback returns a boolean that indicates if the Vulkan call that triggered +the validation layer message should be aborted. If the callback returns true, +then the call is aborted with the `VK_ERROR_VALIDATION_FAILED_EXT` error. This +is normally only used to test the validation layers themselves, so you should +always return `VK_FALSE`. All that remains now is telling Vulkan about the callback function. Perhaps somewhat surprisingly, even the debug callback in Vulkan is managed with a @@ -297,7 +298,7 @@ create the `VkDebugReportCallbackEXT` object. Unfortunately, because this function is an extension function, it is not automatically loaded. We have to look up its address ourselves using `vkGetInstanceProcAddr`. We're going to create our own proxy function that handles this in the background. I've added it -right above the `VDeleter` definition. +right above the `HelloTriangleApplication` class definition. ```c++ VkResult CreateDebugReportCallbackEXT(VkInstance instance, const VkDebugReportCallbackCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugReportCallbackEXT* pCallback) { @@ -320,18 +321,21 @@ if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, &callback) != V } ``` -Let's see if it works... Run the program and close the window once you're fed up -with staring at the blank window. You'll see that the following message is -printed to the command prompt: +The second to last parameter is again the optional allocator callback that we +set to `nullptr`, other than that the parameters are fairly straightforward. +Since the debug callback is specific to our Vulkan instance and its layers, it +needs to be explicitly specified as first argument. You will also see this +pattern with other *child* objects later on. Let's see if it works... Run the +program and close the window once you're fed up with staring at the blank +window. You'll see that the following message is printed to the command prompt: ![](/images/validation_layer_test.png) Oops, it has already spotted a bug in our program! The `VkDebugReportCallbackEXT` object needs to be cleaned up with a call to -`vkDestroyDebugReportCallbackEXT`. Change the `callback` variable to use our -deleter wrapper. Similarly to `vkCreateDebugReportCallbackEXT` the function -needs to be explicitly loaded. Create another proxy function right below -`CreateDebugReportCallbackEXT`: +`vkDestroyDebugReportCallbackEXT`. Similarly to `vkCreateDebugReportCallbackEXT` +the function needs to be explicitly loaded. Create another proxy function right +below `CreateDebugReportCallbackEXT`: ```c++ void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT callback, const VkAllocationCallbacks* pAllocator) { @@ -343,17 +347,20 @@ void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT ``` Make sure that this function is either a static class function or a function -outside the class. We can then specify it as cleanup function: +outside the class. We can then call it in the `cleanup` function: ```c++ -VDeleter callback{instance, DestroyDebugReportCallbackEXT}; -``` +void cleanup() { + if (enableValidationLayers) { + DestroyDebugReportCallbackEXT(instance, callback, nullptr); + } -Make sure to change the line that creates the debug report callback to use the -`replace()` method of the wrapper: + vkDestroyInstance(instance, nullptr); -```c++ -if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { + glfwDestroyWindow(window); + + glfwTerminate(); +} ``` When you run the program again you'll see that the error message has @@ -377,4 +384,4 @@ you how helpful the validation layers are with catching them and to teach you how important it is to know exactly what you're doing with Vulkan. Now it's time to look at [Vulkan devices in the system](!Drawing_a_triangle/Setup/Physical_devices_and_queue_families). -[C++ code](/code/validation_layers.cpp) +[C++ code](/code/02_validation_layers.cpp) diff --git a/03_Drawing_a_triangle/00_Setup/03_Physical_devices_and_queue_families.md b/03_Drawing_a_triangle/00_Setup/03_Physical_devices_and_queue_families.md index 4f090b7f..452fb5b7 100644 --- a/03_Drawing_a_triangle/00_Setup/03_Physical_devices_and_queue_families.md +++ b/03_Drawing_a_triangle/00_Setup/03_Physical_devices_and_queue_families.md @@ -22,8 +22,8 @@ void pickPhysicalDevice() { The graphics card that we'll end up selecting will be stored in a VkPhysicalDevice handle that is added as a new class member. This object will be -implicitly destroyed when the VkInstance is destroyed, so we don't need to add a -delete wrapper. +implicitly destroyed when the VkInstance is destroyed, so we won't need to do +anything new in the `cleanup` function. ```c++ VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; @@ -277,4 +277,4 @@ Great, that's all we need for now to find the right physical device! The next step is to [create a logical device](!Drawing_a_triangle/Setup/Logical_device_and_queues) to interface with it. -[C++ code](/code/physical_device_selection.cpp) +[C++ code](/code/03_physical_device_selection.cpp) diff --git a/03_Drawing_a_triangle/00_Setup/04_Logical_device_and_queues.md b/03_Drawing_a_triangle/00_Setup/04_Logical_device_and_queues.md index 9bfd17c7..32f6c854 100644 --- a/03_Drawing_a_triangle/00_Setup/04_Logical_device_and_queues.md +++ b/03_Drawing_a_triangle/00_Setup/04_Logical_device_and_queues.md @@ -7,13 +7,10 @@ need to specify which queues to create now that we've queried which queue families are available. You can even create multiple logical devices from the same physical device if you have varying requirements. -Start by adding a new class member to store the logical device handle in. Make -sure to place the declaration below the `VkInstance` member, because it needs to -be cleaned up before the instance is cleaned up. See [C++ destruction order](https://msdn.microsoft.com/en-us/library/6t4fe76c.aspx). -Logical devices are cleaned up with the `vkDestroyDevice` function. +Start by adding a new class member to store the logical device handle in. ```c++ -VDeleter device{vkDestroyDevice}; +VkDevice device; ``` Next, add a `createLogicalDevice` function that is called from `initVulkan`. @@ -47,8 +44,8 @@ queueCreateInfo.queueFamilyIndex = indices.graphicsFamily; queueCreateInfo.queueCount = 1; ``` -The currently available drivers will only allow you to create a low number of -queues for each family queue and you don't really need more than one. That's +The currently available drivers will only allow you to create a small number of +queues for each queue family and you don't really need more than one. That's because you can create all of the command buffers on multiple threads and then submit them all at once on the main thread with a single low-overhead call. @@ -111,7 +108,7 @@ device specific extensions for now. createInfo.enabledExtensionCount = 0; if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; @@ -122,7 +119,7 @@ That's it, we're now ready to instantiate the logical device with a call to the appropriately named `vkCreateDevice` function. ```c++ -if (vkCreateDevice(physicalDevice, &createInfo, nullptr, device.replace()) != VK_SUCCESS) { +if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { throw std::runtime_error("failed to create logical device!"); } ``` @@ -133,6 +130,18 @@ to a variable to store the logical device handle in. Similarly to the instance creation function, this call can return errors based on enabling non-existent extensions or specifying the desired usage of unsupported features. +The device should be destroyed in `cleanup` with the `vkDestroyDevice` function: + +```c++ +void cleanup() { + vkDestroyDevice(device, nullptr); + ... +} +``` + +Logical devices don't interact directly with instances, which is why it's not +included as a parameter. + ## Retrieving queue handles The queues are automatically created along with the logical device, but we don't @@ -144,7 +153,7 @@ VkQueue graphicsQueue; ``` Device queues are implicitly cleaned up when the device is destroyed, so we -don't need to wrap it in a deleter object. +don't need to do anything in `cleanup`. We can use the `vkGetDeviceQueue` function to retrieve queue handles for each queue family. The parameters are the logical device, queue family, queue index @@ -159,4 +168,4 @@ With the logical device and queue handles we can now actually start using the graphics card to do things! In the next few chapters we'll set up the resources to present results to the window system. -[C++ code](/code/logical_device.cpp) +[C++ code](/code/04_logical_device.cpp) diff --git a/03_Drawing_a_triangle/01_Presentation/00_Window_surface.md b/03_Drawing_a_triangle/01_Presentation/00_Window_surface.md index b0480c57..5bd98c7d 100644 --- a/03_Drawing_a_triangle/01_Presentation/00_Window_surface.md +++ b/03_Drawing_a_triangle/01_Presentation/00_Window_surface.md @@ -23,10 +23,9 @@ allows you to do that without hacks like creating an invisible window ## Window surface creation Start by adding a `surface` class member right below the debug callback. -Surfaces are destroyed using the `vkDestroySurfaceKHR` call. ```c++ -VDeleter surface{instance, vkDestroySurfaceKHR}; +VkSurfaceKHR surface; ``` Although the `VkSurfaceKHR` object and its usage is platform agnostic, its @@ -49,7 +48,7 @@ important parameters: `hwnd` and `hinstance`. These are the handles to the window and the process. ```c++ -VkWin32SurfaceCreateInfoKHR createInfo; +VkWin32SurfaceCreateInfoKHR createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR; createInfo.hwnd = glfwGetWin32Window(window); createInfo.hinstance = GetModuleHandle(nullptr); @@ -67,8 +66,7 @@ allocators and the variable for the surface handle to be stored in. ```c++ auto CreateWin32SurfaceKHR = (PFN_vkCreateWin32SurfaceKHR) vkGetInstanceProcAddr(instance, "vkCreateWin32SurfaceKHR"); -if (!CreateWin32SurfaceKHR || CreateWin32SurfaceKHR(instance, &createInfo, - nullptr, surface.replace()) != VK_SUCCESS) { +if (!CreateWin32SurfaceKHR || CreateWin32SurfaceKHR(instance, &createInfo, nullptr, &surface) != VK_SUCCESS) { throw std::runtime_error("failed to create window surface!"); } ``` @@ -101,7 +99,7 @@ implementation of the function very straightforward: ```c++ void createSurface() { - if (glfwCreateWindowSurface(instance, window, nullptr, surface.replace()) != VK_SUCCESS) { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { throw std::runtime_error("failed to create window surface!"); } } @@ -109,7 +107,19 @@ void createSurface() { The parameters are the `VkInstance`, GLFW window pointer, custom allocators and pointer to `VkSurfaceKHR` variable. It simply passes through the `VkResult` from -the relevant platform call. +the relevant platform call. GLFW doesn't offer a special function for destroying +a surface, but that can easily be done through the original API: + +```c++ +void cleanup() { + ... + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + ... + } +``` + +Make sure that the surface is destroyed before the instance. ## Querying for presentation support @@ -200,8 +210,8 @@ for (int queueFamily : uniqueQueueFamilies) { And modify `VkDeviceCreateInfo` to point to the vector: ```c++ +createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); createInfo.pQueueCreateInfos = queueCreateInfos.data(); -createInfo.queueCreateInfoCount = (uint32_t) queueCreateInfos.size(); ``` If the queue families are the same, then we only need to pass its index once. @@ -215,4 +225,4 @@ In case the queue families are the same, the two handles will most likely have the same value now. In the next chapter we're going to look at swap chains and how they give us the ability to present images to the surface. -[C++ code](/code/window_surface.cpp) +[C++ code](/code/05_window_surface.cpp) diff --git a/03_Drawing_a_triangle/01_Presentation/01_Swap_chain.md b/03_Drawing_a_triangle/01_Presentation/01_Swap_chain.md index 67675070..4aed7a49 100644 --- a/03_Drawing_a_triangle/01_Presentation/01_Swap_chain.md +++ b/03_Drawing_a_triangle/01_Presentation/01_Swap_chain.md @@ -1,5 +1,4 @@ -In this chapter we will look at the infrastructure that gives you images to -render to that can be presented to the screen afterwards. This infrastructure is +Vulkan does not have the concept of a "default framebuffer", hence it requires an infrastructure that will own the buffers we will render to before we visualize them on the screen. This infrastructure is known as the *swap chain* and must be created explicitly in Vulkan. The swap chain is essentially a queue of images that are waiting to be presented to the screen. Our application will acquire such an image to draw to it, and then @@ -85,7 +84,7 @@ Enabling the extension just requires a small change to the logical device creation structure: ```c++ -createInfo.enabledExtensionCount = deviceExtensions.size(); +createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); createInfo.ppEnabledExtensionNames = deviceExtensions.data(); ``` @@ -216,7 +215,7 @@ VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector } ``` -Each `VkSurfaceFormatKHR` entry contains `format` and `colorSpace` member. The +Each `VkSurfaceFormatKHR` entry contains a `format` and a `colorSpace` member. The `format` member specifies the color channels and types. For example, `VK_FORMAT_B8G8R8A8_UNORM` means that we store the B, G, R and alpha channels in that order with an 8 bit unsigned integer for a total of 32 bits per pixel. The @@ -279,14 +278,16 @@ There are four possible modes available in Vulkan: * `VK_PRESENT_MODE_IMMEDIATE_KHR`: Images submitted by your application are transferred to the screen right away, which may result in tearing. * `VK_PRESENT_MODE_FIFO_KHR`: The swap chain is a queue where the display takes -an image from the front of the queue on a vertical blank and the program inserts -rendered images at the back of the queue. If the queue is full then the program -has to wait. This is most similar to vertical sync as found in modern games. -* `VK_PRESENT_MODE_FIFO_RELAXED_KHR`: This mode only differs from the first one -if the application is late and the queue was empty at the last vertical blank. -Instead of waiting for the next vertical blank, the image is transferred right -away when it finally arrives. This may result in visible tearing. -* `VK_PRESENT_MODE_MAILBOX_KHR`: This is another variation of the first mode. +an image from the front of the queue when the display is refreshed and the +program inserts rendered images at the back of the queue. If the queue is full +then the program has to wait. This is most similar to vertical sync as found in +modern games. The moment that the display is refreshed is known as "vertical +blank". +* `VK_PRESENT_MODE_FIFO_RELAXED_KHR`: This mode only differs from the previous +one if the application is late and the queue was empty at the last vertical +blank. Instead of waiting for the next vertical blank, the image is transferred +right away when it finally arrives. This may result in visible tearing. +* `VK_PRESENT_MODE_MAILBOX_KHR`: This is another variation of the second mode. Instead of blocking the application when the queue is full, the images that are already queued are simply replaced with the newer ones. This mode can be used to implement triple buffering, which allows you to avoid tearing with significantly @@ -523,34 +524,39 @@ you'll get the best performance by enabling clipping. createInfo.oldSwapchain = VK_NULL_HANDLE; ``` -That leaves one last field, `oldSwapChain`. With Vulkan it's possible that in -your swap chain becomes invalid or unoptimized while your application is +That leaves one last field, `oldSwapChain`. With Vulkan it's possible that your swap chain becomes invalid or unoptimized while your application is running, for example because the window was resized. In that case the swap chain actually needs to be recreated from scratch and a reference to the old one must be specified in this field. This is a complex topic that we'll learn more about in [a future chapter](!Drawing_a_triangle/Swap_chain_recreation). For now we'll assume that we'll only ever create one swap chain. -Now add a class member to store the `VkSwapchainKHR` object with a proper -deleter. Make sure to add it after `device` so that it gets cleaned up before -the logical device is. +Now add a class member to store the `VkSwapchainKHR` object: ```c++ -VDeleter swapChain{device, vkDestroySwapchainKHR}; +VkSwapchainKHR swapChain; ``` -Now creating the swap chain is as simple as calling `vkCreateSwapchainKHR`: +Creating the swap chain is now as simple as calling `vkCreateSwapchainKHR`: ```c++ -if (vkCreateSwapchainKHR(device, &createInfo, nullptr, swapChain.replace()) != VK_SUCCESS) { +if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { throw std::runtime_error("failed to create swap chain!"); } ``` The parameters are the logical device, swap chain creation info, optional custom allocators and a pointer to the variable to store the handle in. No surprises -there. Now run the application to ensure that the swap chain is created -successfully! +there. It should be cleaned up using `vkDestroySwapchainKHR` before the device: + +```c++ +void cleanup() { + vkDestroySwapchainKHR(device, swapChain, nullptr); + ... +} +``` + +Now run the application to ensure that the swap chain is created successfully! Try removing the `createInfo.imageExtent = extent;` line with validation layers enabled. You'll see that one of the validation layers immediately catches the @@ -570,7 +576,7 @@ std::vector swapChainImages; The images were created by the implementation for the swap chain and they will be automatically cleaned up once the swap chain has been destroyed, therefore we -don't need a deleter here. +don't need to add any cleanup code. I'm adding the code to retrieve the handles to the end of the `createSwapChain` function, right after the `vkCreateSwapchainKHR` call. Retrieving them is very @@ -593,7 +599,7 @@ One last thing, store the format and extent we've chosen for the swap chain images in member variables. We'll need them in future chapters. ```c++ -VDeleter swapChain{device, vkDestroySwapchainKHR}; +VkSwapchainKHR swapChain; std::vector swapChainImages; VkFormat swapChainImageFormat; VkExtent2D swapChainExtent; @@ -605,7 +611,8 @@ swapChainExtent = extent; ``` We now have a set of images that can be drawn onto and can be presented to the -window. The next two chapters will cover how we can set up the images as render -targets and then we start looking into the actual drawing commands! +window. The next chapter will begin to cover how we can set up the images as +render targets and then we start looking into the actual graphics pipeline and +drawing commands! -[C++ code](/code/swap_chain_creation.cpp) +[C++ code](/code/06_swap_chain_creation.cpp) diff --git a/03_Drawing_a_triangle/01_Presentation/02_Image_views.md b/03_Drawing_a_triangle/01_Presentation/02_Image_views.md index ede9da42..3e1fd649 100644 --- a/03_Drawing_a_triangle/01_Presentation/02_Image_views.md +++ b/03_Drawing_a_triangle/01_Presentation/02_Image_views.md @@ -8,12 +8,10 @@ In this chapter we'll write a `createImageViews` function that creates a basic image view for every image in the swap chain so that we can use them as color targets later on. -First add a class member to store the image views in. Unlike the `VkImage`s, the -`VkImageView` objects are created by us so we need to clean them up ourselves -later. +First add a class member to store the image views in: ```c++ -std::vector> swapChainImageViews; +std::vector swapChainImageViews; ``` Create the `createImageViews` function and call it right after swap chain @@ -36,21 +34,19 @@ void createImageViews() { ``` The first thing we need to do is resize the list to fit all of the image views -we'll be creating. This is also the place where we'll actually define the -deleter function. +we'll be creating: ```c++ void createImageViews() { - swapChainImageViews.resize(swapChainImages.size(), VDeleter{device, vkDestroyImageView}); + swapChainImageViews.resize(swapChainImages.size()); } ``` -The `resize` function initializes all of the list items with the right deleter. Next, set up the loop that iterates over all of the swap chain images. ```c++ -for (uint32_t i = 0; i < swapChainImages.size(); i++) { +for (size_t i = 0; i < swapChainImages.size(); i++) { } ``` @@ -105,18 +101,27 @@ different layers. Creating the image view is now a matter of calling `vkCreateImageView`: ```c++ -if (vkCreateImageView(device, &createInfo, nullptr, swapChainImageViews[i].replace()) != VK_SUCCESS) { +if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { throw std::runtime_error("failed to create image views!"); } ``` -That's it, now run the program to verify that the image views are created -properly and destroyed properly. Checking the latter requires enabling the -validation layers, or putting a print statement in the deleter function. +Unlike images, the image views were explicitly created by us, so we need to add +a similar loop to destroy them again at the end of the program: + +```c++ +void cleanup() { + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + ... +} +``` An image view is sufficient to start using an image as a texture, but it's not quite ready to be used as a render target just yet. That requires one more step of indirection, known as a framebuffer. But first we'll have to set up the graphics pipeline. -[C++ code](/code/image_views.cpp) +[C++ code](/code/07_image_views.cpp) diff --git a/03_Drawing_a_triangle/02_Graphics_pipeline_basics/00_Introduction.md b/03_Drawing_a_triangle/02_Graphics_pipeline_basics/00_Introduction.md index cc95308d..939057e0 100644 --- a/03_Drawing_a_triangle/02_Graphics_pipeline_basics/00_Introduction.md +++ b/03_Drawing_a_triangle/02_Graphics_pipeline_basics/00_Introduction.md @@ -96,4 +96,4 @@ void createGraphicsPipeline() { } ``` -[C++ code](/code/graphics_pipeline.cpp) +[C++ code](/code/08_graphics_pipeline.cpp) diff --git a/03_Drawing_a_triangle/02_Graphics_pipeline_basics/01_Shader_modules.md b/03_Drawing_a_triangle/02_Graphics_pipeline_basics/01_Shader_modules.md index 66440e6d..7af2c271 100644 --- a/03_Drawing_a_triangle/02_Graphics_pipeline_basics/01_Shader_modules.md +++ b/03_Drawing_a_triangle/02_Graphics_pipeline_basics/01_Shader_modules.md @@ -53,23 +53,31 @@ on to the fragment shader, like color and texture coordinates. These values will then be interpolated over the fragments by the rasterizer to produce a smooth gradient. -Clip coordinates are [homogeneous coordinates](https://en.wikipedia.org/wiki/Homogeneous_coordinates) +A *clip coordinate* is a four dimensional vector from the vertex shader that is +subsequently turned into a *normalized device coordinate* by dividing the whole +vector by its last component. These normalized device coordinates are +[homogeneous coordinates](https://en.wikipedia.org/wiki/Homogeneous_coordinates) that map the framebuffer to a [-1, 1] by [-1, 1] coordinate system that looks like the following: -![](/images/clip_coordinates.svg) +![](/images/normalized_device_coordinates.svg) -You should already be familiar with these if you have dabbed in computer +You should already be familiar with these if you have dabbled in computer graphics before. If you have used OpenGL before, then you'll notice that the sign of the Y coordinates is now flipped. The Z coordinate now uses the same range as it does in Direct3D, from 0 to 1. For our first triangle we won't be applying any transformations, we'll just -specify the positions of the three vertices directly in clip coordinates to -create the following shape: +specify the positions of the three vertices directly as normalized device +coordinates to create the following shape: ![](/images/triangle_coordinates.svg) +We can directly output normalized device coordinates by outputting them as clip +coordinates from the vertex shader with the last component set to `1`. That way +the division to transform clip coordinates to normalized device coordinates will +not change anything. + Normally these coordinates would be stored in a vertex buffer, but creating a vertex buffer in Vulkan and filling it with data is not trivial. Therefore I've decided to postpone that until after we've had the satisfaction of seeing a @@ -350,37 +358,36 @@ Before we can pass the code to the pipeline, we have to wrap it in a do that. ```c++ -void createShaderModule(const std::vector& code, VDeleter& shaderModule) { +VkShaderModule createShaderModule(const std::vector& code) { } ``` The function will take a buffer with the bytecode as parameter and create a -`VkShaderModule` from it. Instead of returning this handle directly, it's -written to the variable specified for the second parameter, which makes it -easier to wrap it in a deleter variable when calling `createShaderModule`. +`VkShaderModule` from it. Creating a shader module is simple, we only need to specify a pointer to the buffer with the bytecode and the length of it. This information is specified in a `VkShaderModuleCreateInfo` structure. The one catch is that the size of the bytecode is specified in bytes, but the bytecode pointer is a `uint32_t` pointer -rather than a `char` pointer. Therefore we need to temporarily copy the bytecode -to a container that has the right alignment for `uint32_t`: +rather than a `char` pointer. Therefore we will need to cast the pointer with +`reinterpret_cast` as shown below. When you perform a cast like this, you also +need to ensure that the data satisfies the alignment requirements of `uint32_t`. +Lucky for us, the data is stored in an `std::vector` where the default allocator +already ensures that the data satisfies the worst case alignment requirements. ```c++ VkShaderModuleCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; createInfo.codeSize = code.size(); - -std::vector codeAligned(code.size() / sizeof(uint32_t) + 1); -memcpy(codeAligned.data(), code.data(), code.size()); -createInfo.pCode = codeAligned.data(); +createInfo.pCode = reinterpret_cast(code.data()); ``` The `VkShaderModule` can then be created with a call to `vkCreateShaderModule`: ```c++ -if (vkCreateShaderModule(device, &createInfo, nullptr, shaderModule.replace()) != VK_SUCCESS) { +VkShaderModule shaderModule; +if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { throw std::runtime_error("failed to create shader module!"); } ``` @@ -388,24 +395,38 @@ if (vkCreateShaderModule(device, &createInfo, nullptr, shaderModule.replace()) ! The parameters are the same as those in previous object creation functions: the logical device, pointer to create info structure, optional pointer to custom allocators and handle output variable. The buffer with the code can be freed -immediately after creating the shader module. +immediately after creating the shader module. Don't forget to return the created +shader module: + +```c++ +return shaderModule; +``` The shader module objects are only required during the pipeline creation process, so instead of declaring them as class members, we'll make them local variables in the `createGraphicsPipeline` function: ```c++ -VDeleter vertShaderModule{device, vkDestroyShaderModule}; -VDeleter fragShaderModule{device, vkDestroyShaderModule}; +VkShaderModule vertShaderModule; +VkShaderModule fragShaderModule; ``` -They will be automatically cleaned up when the graphics pipeline has been -created and `createGraphicsPipeline` returns. Now just call the helper function -we created and we're done: +Call the helper function we created to load the shader modules: ```c++ -createShaderModule(vertShaderCode, vertShaderModule); -createShaderModule(fragShaderCode, fragShaderModule); +vertShaderModule = createShaderModule(vertShaderCode); +fragShaderModule = createShaderModule(fragShaderCode); +``` + +They should be cleaned up when the graphics pipeline has been created and +`createGraphicsPipeline` returns, so make sure that they are deleted at the end +of the function: + +```c++ + ... + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); +} ``` ## Shader stage creation @@ -470,6 +491,6 @@ VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShade That's all there is to describing the programmable stages of the pipeline. In the next chapter we'll look at the fixed-function stages. -[C++ code](/code/shader_modules.cpp) / -[Vertex shader](/code/shader_base.vert) / -[Fragment shader](/code/shader_base.frag) +[C++ code](/code/09_shader_modules.cpp) / +[Vertex shader](/code/09_shader_base.vert) / +[Fragment shader](/code/09_shader_base.frag) diff --git a/03_Drawing_a_triangle/02_Graphics_pipeline_basics/02_Fixed_functions.md b/03_Drawing_a_triangle/02_Graphics_pipeline_basics/02_Fixed_functions.md index f5df4293..59f6df85 100644 --- a/03_Drawing_a_triangle/02_Graphics_pipeline_basics/02_Fixed_functions.md +++ b/03_Drawing_a_triangle/02_Graphics_pipeline_basics/02_Fixed_functions.md @@ -41,12 +41,12 @@ like: * `VK_PRIMITIVE_TOPOLOGY_POINT_LIST`: points from vertices * `VK_PRIMITIVE_TOPOLOGY_LINE_LIST`: line from every 2 vertices without reuse -* `VK_PRIMITIVE_TOPOLOGY_LINE_STRIP`: every second vertex is used as start -vertex for the next line +* `VK_PRIMITIVE_TOPOLOGY_LINE_STRIP`: the end vertex of every line is used as +start vertex for the next line * `VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST`: triangle from every 3 vertices without reuse -* `VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP `: every third vertex is used as first -vertex for the next triangle +* `VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP `: the second and third vertex of every +triangle are used as first two vertices of the next triangle Normally, the vertices are loaded from the vertex buffer by index in sequential order, but with an *element buffer* you can specify the indices to use yourself. @@ -213,13 +213,12 @@ multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; multisampling.sampleShadingEnable = VK_FALSE; multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; multisampling.minSampleShading = 1.0f; // Optional -multisampling.pSampleMask = nullptr; /// Optional +multisampling.pSampleMask = nullptr; // Optional multisampling.alphaToCoverageEnable = VK_FALSE; // Optional multisampling.alphaToOneEnable = VK_FALSE; // Optional ``` -In this tutorial we'll not be using multisampling, but feel free to experiment -with it. See the specification for the meaning of each parameter. +We'll revisit multisampling in later chapter, for now let's keep it disabled. ## Depth and stencil testing @@ -301,7 +300,7 @@ You can find all of the possible operations in the `VkBlendFactor` and The second structure references the array of structures for all of the framebuffers and allows you to set blend constants that you can use as blend -factors in the aforementioned calculations. +factors in the aforementioned calculations. ```c++ VkPipelineColorBlendStateCreateInfo colorBlending = {}; @@ -365,7 +364,7 @@ Create a class member to hold this object, because we'll refer to it from other functions at a later point in time: ```c++ -VDeleter pipelineLayout{device, vkDestroyPipelineLayout}; +VkPipelineLayout pipelineLayout; ``` And then create the object in the `createGraphicsPipeline` function: @@ -376,16 +375,24 @@ pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; pipelineLayoutInfo.setLayoutCount = 0; // Optional pipelineLayoutInfo.pSetLayouts = nullptr; // Optional pipelineLayoutInfo.pushConstantRangeCount = 0; // Optional -pipelineLayoutInfo.pPushConstantRanges = 0; // Optional +pipelineLayoutInfo.pPushConstantRanges = nullptr; // Optional -if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, - pipelineLayout.replace()) != VK_SUCCESS) { +if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { throw std::runtime_error("failed to create pipeline layout!"); } ``` The structure also specifies *push constants*, which are another way of passing -dynamic values to shaders that we'll get into later. +dynamic values to shaders that we may get into in a future chapter. The pipeline +layout will be referenced throughout the program's lifetime, so it should be +destroyed at the end: + +```c++ +void cleanup() { + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + ... +} +``` ## Conclusion @@ -398,6 +405,6 @@ is not what you expect. There is however one more object to create before we can finally create the graphics pipeline and that is a [render pass](!Drawing_a_triangle/Graphics_pipeline_basics/Render_passes). -[C++ code](/code/fixed_functions.cpp) / -[Vertex shader](/code/shader_base.vert) / -[Fragment shader](/code/shader_base.frag) \ No newline at end of file +[C++ code](/code/10_fixed_functions.cpp) / +[Vertex shader](/code/09_shader_base.vert) / +[Fragment shader](/code/09_shader_base.frag) diff --git a/03_Drawing_a_triangle/02_Graphics_pipeline_basics/03_Render_passes.md b/03_Drawing_a_triangle/02_Graphics_pipeline_basics/03_Render_passes.md index 91506657..8bb06679 100644 --- a/03_Drawing_a_triangle/02_Graphics_pipeline_basics/03_Render_passes.md +++ b/03_Drawing_a_triangle/02_Graphics_pipeline_basics/03_Render_passes.md @@ -41,8 +41,9 @@ void createRenderPass() { } ``` -The `format` of the color attachment should match the one of the swap chain -images and we're not doing anything with multisampling, so we stick to 1 sample. +The `format` of the color attachment should match the format of the swap chain +images, and we're not doing anything with multisampling yet, so we'll stick to 1 +sample. ```c++ colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; @@ -173,7 +174,8 @@ we can create the render pass itself. Create a new class member variable to hold the `VkRenderPass` object right above the `pipelineLayout` variable: ```c++ -VDeleter renderPass{device, vkDestroyRenderPass}; +VkRenderPass renderPass; +VkPipelineLayout pipelineLayout; ``` The render pass object can then be created by filling in the @@ -189,14 +191,25 @@ renderPassInfo.pAttachments = &colorAttachment; renderPassInfo.subpassCount = 1; renderPassInfo.pSubpasses = &subpass; -if (vkCreateRenderPass(device, &renderPassInfo, nullptr, renderPass.replace()) != VK_SUCCESS) { +if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { throw std::runtime_error("failed to create render pass!"); } ``` +Just like the pipeline layout, the render pass will be referenced throughout the +program, so it should only be cleaned up at the end: + +```c++ +void cleanup() { + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + ... +} +``` + That was a lot of work, but in the next chapter it all comes together to finally create the graphics pipeline object! -[C++ code](/code/render_passes.cpp) / -[Vertex shader](/code/shader_base.vert) / -[Fragment shader](/code/shader_base.frag) \ No newline at end of file +[C++ code](/code/11_render_passes.cpp) / +[Vertex shader](/code/09_shader_base.vert) / +[Fragment shader](/code/09_shader_base.frag) diff --git a/03_Drawing_a_triangle/02_Graphics_pipeline_basics/04_Conclusion.md b/03_Drawing_a_triangle/02_Graphics_pipeline_basics/04_Conclusion.md index 6ff4db9c..95bfb976 100644 --- a/03_Drawing_a_triangle/02_Graphics_pipeline_basics/04_Conclusion.md +++ b/03_Drawing_a_triangle/02_Graphics_pipeline_basics/04_Conclusion.md @@ -13,7 +13,8 @@ be updated at draw time All of these combined fully define the functionality of the graphics pipeline, so we can now begin filling in the `VkGraphicsPipelineCreateInfo` structure at -the end of the `createGraphicsPipeline` function. +the end of the `createGraphicsPipeline` function. But before the calls to +`vkDestroyShaderModule` because these are still to be used during the creation. ```c++ VkGraphicsPipelineCreateInfo pipelineInfo = {}; @@ -50,7 +51,11 @@ pipelineInfo.subpass = 0; ``` And finally we have the reference to the render pass and the index of the sub -pass where this graphics pipeline will be used. +pass where this graphics pipeline will be used. It is also possible to use other +render passes with this pipeline instead of this specific instance, but they +have to be *compatible* with `renderPass`. The requirements for compatibility +are described [here](https://www.khronos.org/registry/vulkan/specs/1.0/html/vkspec.html#renderpass-compatibility), +but we won't be using that feature in this tutorial. ```c++ pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; // Optional @@ -73,13 +78,13 @@ Now prepare for the final step by creating a class member to hold the `VkPipeline` object: ```c++ -VDeleter graphicsPipeline{device, vkDestroyPipeline}; +VkPipeline graphicsPipeline; ``` And finally create the graphics pipeline: ```c++ -if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, graphicsPipeline.replace()) != VK_SUCCESS) { +if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { throw std::runtime_error("failed to create graphics pipeline!"); } ``` @@ -96,11 +101,22 @@ store and reuse data relevant to pipeline creation across multiple calls to stored to a file. This makes it possible to significantly speed up pipeline creation at a later time. We'll get into this in the pipeline cache chapter. +The graphics pipeline is required for all common drawing operations, so it +should also only be destroyed at the end of the program: + +```c++ +void cleanup() { + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + ... +} +``` + Now run your program to confirm that all this hard work has resulted in a successful pipeline creation! We are already getting quite close to seeing something pop up on the screen. In the next couple of chapters we'll set up the actual framebuffers from the swap chain images and prepare the drawing commands. -[C++ code](/code/graphics_pipeline_complete.cpp) / -[Vertex shader](/code/shader_base.vert) / -[Fragment shader](/code/shader_base.frag) +[C++ code](/code/12_graphics_pipeline_complete.cpp) / +[Vertex shader](/code/09_shader_base.vert) / +[Fragment shader](/code/09_shader_base.frag) diff --git a/03_Drawing_a_triangle/03_Drawing/00_Framebuffers.md b/03_Drawing_a_triangle/03_Drawing/00_Framebuffers.md index cbf65363..b2c6bf6f 100644 --- a/03_Drawing_a_triangle/03_Drawing/00_Framebuffers.md +++ b/03_Drawing_a_triangle/03_Drawing/00_Framebuffers.md @@ -6,7 +6,7 @@ The attachments specified during render pass creation are bound by wrapping them into a `VkFramebuffer` object. A framebuffer object references all of the `VkImageView` objects that represent the attachments. In our case that will be only a single one: the color attachment. However, the image that we have to use -as attachment depends on which image the swap chain returns when we retrieve one +for the attachment depends on which image the swap chain returns when we retrieve one for presentation. That means that we have to create a framebuffer for all of the images in the swap chain and use the one that corresponds to the retrieved image at drawing time. @@ -14,7 +14,7 @@ at drawing time. To that end, create another `std::vector` class member to hold the framebuffers: ```c++ -std::vector> swapChainFramebuffers; +std::vector swapChainFramebuffers; ``` We'll create the objects for this array in a new function `createFramebuffers` @@ -45,7 +45,7 @@ Start by resizing the container to hold all of the framebuffers: ```c++ void createFramebuffers() { - swapChainFramebuffers.resize(swapChainImageViews.size(), VDeleter{device, vkDestroyFramebuffer}); + swapChainFramebuffers.resize(swapChainImageViews.size()); } ``` @@ -66,7 +66,7 @@ for (size_t i = 0; i < swapChainImageViews.size(); i++) { framebufferInfo.height = swapChainExtent.height; framebufferInfo.layers = 1; - if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, swapChainFramebuffers[i].replace()) != VK_SUCCESS) { + if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { throw std::runtime_error("failed to create framebuffer!"); } } @@ -85,10 +85,23 @@ The `width` and `height` parameters are self-explanatory and `layers` refers to the number of layers in image arrays. Our swap chain images are single images, so the number of layers is `1`. +We should delete the framebuffers before the image views and render pass that +they are based on, but only after we've finished rendering: + +```c++ +void cleanup() { + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } + + ... +} +``` + We've now reached the milestone where we have all of the objects that are required for rendering. In the next chapter we're going to write the first actual drawing commands. -[C++ code](/code/framebuffers.cpp) / -[Vertex shader](/code/shader_base.vert) / -[Fragment shader](/code/shader_base.frag) +[C++ code](/code/13_framebuffers.cpp) / +[Vertex shader](/code/09_shader_base.vert) / +[Fragment shader](/code/09_shader_base.frag) diff --git a/03_Drawing_a_triangle/03_Drawing/01_Command_buffers.md b/03_Drawing_a_triangle/03_Drawing/01_Command_buffers.md index 740495b2..036083d2 100644 --- a/03_Drawing_a_triangle/03_Drawing/01_Command_buffers.md +++ b/03_Drawing_a_triangle/03_Drawing/01_Command_buffers.md @@ -12,7 +12,7 @@ pools manage the memory that is used to store the buffers and command buffers are allocated from them. Add a new class member to store a `VkCommandPool`: ```c++ -VDeleter commandPool{device, vkDestroyCommandPool}; +VkCommandPool commandPool; ``` Then create a new function `createCommandPool` and call it from `initVulkan` @@ -69,13 +69,23 @@ execute them many times in the main loop, so we're not going to use either of these flags. ```c++ -if (vkCreateCommandPool(device, &poolInfo, nullptr, commandPool.replace()) != VK_SUCCESS) { +if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { throw std::runtime_error("failed to create command pool!"); } ``` Finish creating the command pool using the `vkCreateCommandPool` function. It -doesn't have any special parameters. +doesn't have any special parameters. Commands will be used throughout the +program to draw things on the screen, so the pool should only be destroyed at +the end: + +```c++ +void cleanup() { + vkDestroyCommandPool(device, commandPool, nullptr); + + ... +} +``` ## Command buffer allocation @@ -83,8 +93,8 @@ We can now start allocating command buffers and recording drawing commands in them. Because one of the drawing commands involves binding the right `VkFramebuffer`, we'll actually have to record a command buffer for every image in the swap chain once again. To that end, create a list of `VkCommandBuffer` -objects as class member. Command buffers will be automatically freed when their -command pool is destroyed, so we don't need a `VDeleter`. +objects as a class member. Command buffers will be automatically freed when their +command pool is destroyed, so we don't need an explicit cleanup. ```c++ std::vector commandBuffers; @@ -116,10 +126,6 @@ void createCommandBuffers() { } ``` -Cleaning up command buffers involves a slightly different function than other -objects. The `vkFreeCommandBuffers` function takes the command pool and an array -of command buffers as parameters. - Command buffers are allocated with the `vkAllocateCommandBuffers` function, which takes a `VkCommandBufferAllocateInfo` struct as parameter that specifies the command pool and number of buffers to allocate: @@ -161,7 +167,9 @@ for (size_t i = 0; i < commandBuffers.size(); i++) { beginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT; beginInfo.pInheritanceInfo = nullptr; // Optional - vkBeginCommandBuffer(commandBuffers[i], &beginInfo); + if (vkBeginCommandBuffer(commandBuffers[i], &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } } ``` @@ -293,6 +301,6 @@ In the next chapter we'll write the code for the main loop, which will acquire an image from the swap chain, execute the right command buffer and return the finished image to the swap chain. -[C++ code](/code/command_buffers.cpp) / -[Vertex shader](/code/shader_base.vert) / -[Fragment shader](/code/shader_base.frag) +[C++ code](/code/14_command_buffers.cpp) / +[Vertex shader](/code/09_shader_base.vert) / +[Fragment shader](/code/09_shader_base.frag) diff --git a/03_Drawing_a_triangle/03_Drawing/02_Rendering_and_presentation.md b/03_Drawing_a_triangle/03_Drawing/02_Rendering_and_presentation.md index 9252153e..2ded1e2d 100644 --- a/03_Drawing_a_triangle/03_Drawing/02_Rendering_and_presentation.md +++ b/03_Drawing_a_triangle/03_Drawing/02_Rendering_and_presentation.md @@ -10,8 +10,6 @@ void mainLoop() { glfwPollEvents(); drawFrame(); } - - glfwDestroyWindow(window); } ... @@ -55,8 +53,8 @@ presentation can happen. Create two class members to store these semaphore objects: ```c++ -VDeleter imageAvailableSemaphore{device, vkDestroySemaphore}; -VDeleter renderFinishedSemaphore{device, vkDestroySemaphore}; +VkSemaphore imageAvailableSemaphore; +VkSemaphore renderFinishedSemaphore; ``` To create the semaphores, we'll add the last `create` function for this part of @@ -102,17 +100,26 @@ Future versions of the Vulkan API or extensions may add functionality for the the semaphores follows the familiar pattern with `vkCreateSemaphore`: ```c++ -if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, imageAvailableSemaphore.replace()) != VK_SUCCESS || - vkCreateSemaphore(device, &semaphoreInfo, nullptr, renderFinishedSemaphore.replace()) != VK_SUCCESS) { +if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphore) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphore) != VK_SUCCESS) { throw std::runtime_error("failed to create semaphores!"); } ``` +The semaphores should be cleaned up at the end of the program, when all commands +have finished and no more synchronization is necessary: + +```c++ +void cleanup() { + vkDestroySemaphore(device, renderFinishedSemaphore, nullptr); + vkDestroySemaphore(device, imageAvailableSemaphore, nullptr); +``` + ## Acquiring an image from the swap chain As mentioned before, the first thing we need to do in the `drawFrame` function -is acquiring an image from the swap chain. Recall that the swap chain is an +is acquire an image from the swap chain. Recall that the swap chain is an extension feature, so we must use a function with the `vk*KHR` naming convention: @@ -160,7 +167,7 @@ begins and in which stage(s) of the pipeline to wait. We want to wait with writing colors to the image until it's available, so we're specifying the stage of the graphics pipeline that writes to the color attachment. That means that theoretically the implementation can already start executing our vertex shader -and such while the image is not available yet. Each entry in the `waitStages` +and such while the image is not yet available. Each entry in the `waitStages` array corresponds to the semaphore with the same index in `pWaitSemaphores`. ```c++ @@ -208,11 +215,11 @@ start of the render pass and at the end of the render pass, but the former does not occur at the right time. It assumes that the transition occurs at the start of the pipeline, but we haven't acquired the image yet at that point! There are two ways to deal with this problem. We could change the `waitStages` for the -`imageAvailableSemaphore` to `VK_PIPELINE_STAGE_TOP_OF_PIPELINE_BIT` to ensure -that the render passes don't begin until the image is available, or we can make -the render pass wait for the `VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT` -stage. I've decided to go with the second option here, because it's a good -excuse to have a look at subpass dependencies and how they work. +`imageAvailableSemaphore` to `VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT` to ensure that +the render passes don't begin until the image is available, or we can make the +render pass wait for the `VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT` stage. +I've decided to go with the second option here, because it's a good excuse to +have a look at subpass dependencies and how they work. Subpass dependencies are specified in `VkSubpassDependency` structs. Go to the `createRenderPass` function and add one: @@ -310,8 +317,8 @@ something resembling the following when you run your program: ![](/images/triangle.png) Yay! Unfortunately, you'll see that when validation layers are enabled, the -program crashes as soon as you close it. The message printed to the terminal -from `debugCallback` tells us why: +program crashes as soon as you close it. The messages printed to the terminal +from `debugCallback` tell us why: ![](/images/semaphore_in_use.png) @@ -331,8 +338,6 @@ void mainLoop() { } vkDeviceWaitIdle(device); - - glfwDestroyWindow(window); } ``` @@ -341,9 +346,207 @@ You can also wait for operations in a specific command queue to be finished with perform synchronization. You'll see that the program now exits without problems when closing the window. +## Frames in flight + +If you run your application with validation layers enabled and you monitor the +memory usage of your application, you may notice that it is slowly growing. The reason for this is that the application is rapidly submitting work in the `drawFrame` function, but doesn't actually check if any of it finishes. If the CPU is submitting work faster than the GPU can keep up with then the queue will slowly fill up with work. Worse, even, is that we are reusing the `imageAvailableSemaphore` and `renderFinishedSemaphore` for multiple frames at the same time. + +The easy way to solve this is to wait for work to finish right after submitting it, for example by using `vkQueueWaitIdle`: + +```c++ +void drawFrame() { + ... + + vkQueuePresentKHR(presentQueue, &presentInfo); + + vkQueueWaitIdle(presentQueue); +} +``` + +However, we are likely not optimally using the GPU in this way, because the whole graphics pipeline is only used for one frame at a time right now. The stages that the current frame has already progressed through are idle and could already be used for a next frame. We will now extend our application to allow for multiple frames to be *in-flight* while still bounding the amount of work that piles up. + +Start by adding a constant at the top of the program that defines how many frames should be processed concurrently: + +```c++ +const int MAX_FRAMES_IN_FLIGHT = 2; +``` + +Each frame should have its own set of semaphores: + +```c++ +std::vector imageAvailableSemaphores; +std::vector renderFinishedSemaphores; +``` + +The `createSemaphores` function should be changed to create all of these: + +```c++ +void createSemaphores() { + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + + VkSemaphoreCreateInfo semaphoreInfo = {}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS) { + + throw std::runtime_error("failed to create semaphores for a frame!"); + } +} +``` + +Similarly, they should also all be cleaned up: + +```c++ +void cleanup() { + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + } + + ... +} +``` + +To use the right pair of semaphores every time, we need to keep track of the current frame. We will use a frame index for that purpose: + +```c++ +size_t currentFrame = 0; +``` + +The `drawFrame` function can now be modified to use the right objects: + +```c++ +void drawFrame() { + vkAcquireNextImageKHR(device, swapChain, std::numeric_limits::max(), imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + + ... + + VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[currentFrame]}; + + ... + + VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[currentFrame]}; + + ... +} +``` + +Of course, we shouldn't forget to advance to the next frame every time: + +```c++ +void drawFrame() { + ... + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; +} +``` + +By using the modulo (%) operator, we ensure that the frame index loops around after every `MAX_FRAMES_IN_FLIGHT` enqueued frames. + +Although we've not set up the required objects to facilitate processing of multiple frames simultaneously, we still don't actually prevent more than `MAX_FRAMES_IN_FLIGHT` from being submitted. Right now there is only GPU-GPU synchronization and no CPU-GPU synchronization going on to keep track of how the work is going. We may be using the frame #0 objects while frame #0 is still in-flight! + +To perform CPU-GPU synchronization, Vulkan offers a second type of synchronization primitive called *fences*. Fences are similar to semaphores in the sense that they can be signaled and waited for, but this time we actually wait for them in our own code. We'll first create a fence for each frame: + +```c++ +std::vector imageAvailableSemaphores; +std::vector renderFinishedSemaphores; +std::vector inFlightFences; +size_t currentFrame = 0; +``` + +I've decided to create the fences together with the semaphores and renamed `createSemaphores` to `createSyncObjects`: + +```c++ +void createSyncObjects() { + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + inFlightFences.resize(MAX_FRAMES_IN_FLIGHT); + + VkSemaphoreCreateInfo semaphoreInfo = {}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + VkFenceCreateInfo fenceInfo = {}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS || + vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) { + + throw std::runtime_error("failed to create synchronization objects for a frame!"); + } + } +} +``` + +The creation of fences (`VkFence`) is very similar to the creation of semaphores. Also make sure to clean up the fences: + +```c++ +void cleanup() { + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + vkDestroyFence(device, inFlightFences[i], nullptr); + } + + ... +} +``` + +We will now change `drawFrame` to use the fences for synchronization. The `vkQueueSubmit` call includes an optional parameter to pass a fence that should be signaled when the command buffer finishes executing. We can use this to signal that a frame has finished. + +```c++ +void drawFrame() { + ... + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) { + throw std::runtime_error("failed to submit draw command buffer!"); + } + ... +} +``` + +Now the only thing remaining is to change the beginning of `drawFrame` to wait for the frame to be finished: + +```c++ +void drawFrame() { + vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, std::numeric_limits::max()); + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + ... +} +``` + +The `vkWaitForFences` function takes an array of fences and waits for either any or all of them to be signaled before returning. The `VK_TRUE` we pass here indicates that we want to wait for all fences, but in the case of a single one it obviously doesn't matter. Just like `vkAcquireNextImageKHR` this function also takes a timeout. Unlike the semaphores, we manually need to restore the fence to the unsignaled state by resetting it with the `vkResetFences` call. + +If you run the program now, you'll notice something something strange. The application no longer seems to be rendering anything. With validation layers enabled, you'll see the following message: + +![](/images/unsubmitted_fence.png) + +That means that we're waiting for a fence that has not been submitted. The problem here is that, by default, fences are created in the unsignaled state. That means that `vkWaitForFences` will wait forever if we haven't used the fence before. To solve that, we can change the fence creation to initialize it in the signaled state as if we had rendered an initial frame that finished: + +```c++ +void createSyncObjects() { + ... + + VkFenceCreateInfo fenceInfo = {}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; + + ... +} +``` + +The program should now work correctly and the memory leak should be gone! We've now implemented all the needed synchronization to ensure that there are no more than two frames of work enqueued. Note that it is fine for other parts of the code, like the final cleanup, to rely on more rough synchronization like `vkDeviceWaitIdle`. You should decide on which approach to use based on performance requirements. + +To learn more about synchronization through examples, have a look at [this extensive overview](https://github.com/KhronosGroup/Vulkan-Docs/wiki/Synchronization-Examples#swapchain-image-acquire-and-present) by Khronos. + ## Conclusion -About 800 lines of code later, we've finally gotten to the stage of seeing +A little over 900 lines of code later, we've finally gotten to the stage of seeing something pop up on the screen! Bootstrapping a Vulkan program is definitely a lot of work, but the take-away message is that Vulkan gives you an immense amount of control through its explicitness. I recommend you to take some time @@ -355,6 +558,6 @@ from this point on. In the next chapter we'll deal with one more small thing that is required for a well-behaved Vulkan program. -[C++ code](/code/hello_triangle.cpp) / -[Vertex shader](/code/shader_base.vert) / -[Fragment shader](/code/shader_base.frag) +[C++ code](/code/15_hello_triangle.cpp) / +[Vertex shader](/code/09_shader_base.vert) / +[Fragment shader](/code/09_shader_base.frag) diff --git a/03_Drawing_a_triangle/04_Swap_chain_recreation.md b/03_Drawing_a_triangle/04_Swap_chain_recreation.md index 322dc929..1602e2bd 100644 --- a/03_Drawing_a_triangle/04_Swap_chain_recreation.md +++ b/03_Drawing_a_triangle/04_Swap_chain_recreation.md @@ -30,131 +30,127 @@ shouldn't touch resources that may still be in use. Obviously, the first thing we'll have to do is recreate the swap chain itself. The image views need to be recreated because they are based directly on the swap chain images. The render pass needs to be recreated because it depends on the format of the swap chain -images. Viewport and scissor rectangle size is specified during graphics -pipeline creation, so the pipeline also needs to be rebuilt. It is possible to -avoid this by using dynamic state for the viewports and scissor rectangles. -Finally, the framebuffers and command buffers also directly depend on the swap -chain images. - -Because of our handy `VDeleter` construct, most of the functions will work fine -for recreation and will automatically clean up the old objects. However, the -`createSwapChain` and `createCommandBuffers` functions still need some -adjustments. +images. It is rare for the swap chain image format to change during an operation +like a window resize, but it should still be handled. Viewport and scissor +rectangle size is specified during graphics pipeline creation, so the pipeline +also needs to be rebuilt. It is possible to avoid this by using dynamic state +for the viewports and scissor rectangles. Finally, the framebuffers and command +buffers also directly depend on the swap chain images. + +To make sure that the old versions of these objects are cleaned up before +recreating them, we should move some of the cleanup code to a separate function +that we can call from the `recreateSwapChain` function. Let's call it +`cleanupSwapChain`: ```c++ -VkSwapchainKHR oldSwapChain = swapChain; -createInfo.oldSwapchain = oldSwapChain; +void cleanupSwapChain() { -VkSwapchainKHR newSwapChain; -if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &newSwapChain) != VK_SUCCESS) { - throw std::runtime_error("failed to create swap chain!"); } -swapChain = newSwapChain; -``` +void recreateSwapChain() { + vkDeviceWaitIdle(device); -We need to pass the previous swap chain object in the `oldSwapchain` parameter -of `VkSwapchainCreateInfoKHR` to indicate that we intend to replace it. The old -swap chain needs to stick around until after the new swap chain has been -created, which means that we can't directly write the new handle to `swapChain`. -The `VDeleter` would clear the old object before `vkCreateSwapchainKHR` has a -chance to execute. That's why we use the temporary `newSwapChain` variable. + cleanupSwapChain(); -```c++ -swapChain = newSwapChain; + createSwapChain(); + createImageViews(); + createRenderPass(); + createGraphicsPipeline(); + createFramebuffers(); + createCommandBuffers(); +} ``` -This line will actually destroy the old swap chain and replace the handle with -the handle of the new swap chain. - -The problem with `createCommandBuffers` is that it doesn't free the old command -buffers. There are two ways to solve this: - -* Call `createCommandPool` as well, which will automatically free the old -command buffers -* Extend `createCommandBuffers` to free any previous command buffers - -As there isn't really a need to recreate the command pool itself, I've chosen to -go for the second solution in this tutorial. +we'll move the cleanup code of all objects that are recreated as part of a swap +chain refresh from `cleanup` to `cleanupSwapChain`: ```c++ -if (commandBuffers.size() > 0) { - vkFreeCommandBuffers(device, commandPool, commandBuffers.size(), commandBuffers.data()); -} +void cleanupSwapChain() { + for (size_t i = 0; i < swapChainFramebuffers.size(); i++) { + vkDestroyFramebuffer(device, swapChainFramebuffers[i], nullptr); + } -commandBuffers.resize(swapChainFramebuffers.size()); -``` + vkFreeCommandBuffers(device, commandPool, static_cast(commandBuffers.size()), commandBuffers.data()); -The `createCommandBuffers` function now first checks if the `commandBuffers` -vector already contains previous command buffers, and if so, frees them. That's -all it takes to recreate the swap chain! + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); -## Window resizing + for (size_t i = 0; i < swapChainImageViews.size(); i++) { + vkDestroyImageView(device, swapChainImageViews[i], nullptr); + } -Now we just need to figure out when swap chain recreation is necessary and call -our new `recreateSwapChain` function. One of the most common conditions is -resizing of the window. Let's make the window resizable and catch that event. -Change the `initWindow` function to no longer include the `GLFW_RESIZABLE` line -or change its argument from `GLFW_FALSE` to `GLFW_TRUE`. - -```c++ -void initWindow() { - glfwInit(); + vkDestroySwapchainKHR(device, swapChain, nullptr); +} - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); +void cleanup() { + cleanupSwapChain(); - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + vkDestroyFence(device, inFlightFences[i], nullptr); + } - glfwSetWindowUserPointer(window, this); - glfwSetWindowSizeCallback(window, HelloTriangleApplication::onWindowResized); -} + vkDestroyCommandPool(device, commandPool, nullptr); -... + vkDestroyDevice(device, nullptr); + DestroyDebugReportCallbackEXT(instance, callback, nullptr); + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); -static void onWindowResized(GLFWwindow* window, int width, int height) { - if (width == 0 || height == 0) return; + glfwDestroyWindow(window); - HelloTriangleApplication* app = reinterpret_cast(glfwGetWindowUserPointer(window)); - app->recreateSwapChain(); + glfwTerminate(); } ``` -The `glfwSetWindowSizeCallback` function can be used to specify a callback for -the window resize event. Unfortunately it only accepts a function pointer as -argument, so we can't directly use a member function. Luckily GLFW allows us to -store an arbitrary pointer in the window object with `glfwSetWindowUserPointer`, -so we can specify a static class member and get the original class instance back -with `glfwGetWindowUserPointer`. We can then proceed to call -`recreateSwapChain`, but only if the size of the window is non-zero. This case -occurs when the window is minimized and it will cause swap chain creation to -fail. +We could recreate the command pool from scratch, but that is rather wasteful. +Instead I've opted to clean up the existing command buffers with the +`vkFreeCommandBuffers` function. This way we can reuse the existing pool to +allocate the new command buffers. -The `chooseSwapExtent` function should also be updated to take the current width -and height of the window into account instead of the initial `WIDTH` and -`HEIGHT`: +To handle window resizes properly, we also need to query the current size of the framebuffer to make sure that the swap chain images have the (new) right size. To do that change the `chooseSwapExtent` function to take the actual size into account: ```c++ -int width, height; -glfwGetWindowSize(window, &width, &height); - -VkExtent2D actualExtent = {width, height}; +VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + ... + } +} ``` +That's all it takes to recreate the swap chain! However, the disadvantage of +this approach is that we need to stop all rendering before creating the new swap +chain. It is possible to create a new swap chain while drawing commands on an +image from the old swap chain are still in-flight. You need to pass the previous +swap chain to the `oldSwapChain` field in the `VkSwapchainCreateInfoKHR` struct +and destroy the old swap chain as soon as you've finished using it. + ## Suboptimal or out-of-date swap chain -It is also possible for Vulkan to tell us that the swap chain is no longer -compatible during presentation. The `vkAcquireNextImageKHR` and +Now we just need to figure out when swap chain recreation is necessary and call +our new `recreateSwapChain` function. Luckily, Vulkan will usually just tell us that the swap chain is no longer adequate during presentation. The `vkAcquireNextImageKHR` and `vkQueuePresentKHR` functions can return the following special values to indicate this. * `VK_ERROR_OUT_OF_DATE_KHR`: The swap chain has become incompatible with the -surface and can no longer be used for rendering. +surface and can no longer be used for rendering. Usually happens after a window resize. * `VK_SUBOPTIMAL_KHR`: The swap chain can still be used to successfully present -to the surface, but the surface properties are no longer matched exactly. For -example, the platform may be simply resizing the image to fit the window now. +to the surface, but the surface properties are no longer matched exactly. ```c++ -VkResult result = vkAcquireNextImageKHR(device, swapChain, std::numeric_limits::max(), imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex); +VkResult result = vkAcquireNextImageKHR(device, swapChain, std::numeric_limits::max(), imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); if (result == VK_ERROR_OUT_OF_DATE_KHR) { recreateSwapChain(); @@ -168,6 +164,19 @@ If the swap chain turns out to be out of date when attempting to acquire an image, then it is no longer possible to present to it. Therefore we should immediately recreate the swap chain and try again in the next `drawFrame` call. +However, if we abort drawing at this point then the fence will be never have +been submitted with `vkQueueSubmit` and it'll be in an unexpected state when we +try to wait for it later on. We could recreate the fences as part of swap chain +recreation, but it's easier to move the `vkResetFences` call: + +```c++ +vkResetFences(device, 1, &inFlightFences[currentFrame]); + +if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) { + throw std::runtime_error("failed to submit draw command buffer!"); +} +``` + You could also decide to do that if the swap chain is suboptimal, but I've chosen to proceed anyway in that case because we've already acquired an image. Both `VK_SUCCESS` and `VK_SUBOPTIMAL_KHR` are considered "success" return codes. @@ -180,17 +189,96 @@ if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { } else if (result != VK_SUCCESS) { throw std::runtime_error("failed to present swap chain image!"); } + +currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; ``` The `vkQueuePresentKHR` function returns the same values with the same meaning. In this case we will also recreate the swap chain if it is suboptimal, because -we want the best possible result. Try to run it and resize the window to see if -the framebuffer is indeed resized properly with the window. +we want the best possible result. + +## Handling resizes explicitly + +Although many drivers and platforms trigger `VK_ERROR_OUT_OF_DATE_KHR` automatically after a window resize, it is not guaranteed to happen. That's why we'll add some extra code to also handle resizes explicitly. First add a new member variable that flags that a resize has happened: + +```c++ +std::vector inFlightFences; +size_t currentFrame = 0; + +bool framebufferResized = false; +``` + +The `drawFrame` function should then be modified to also check for this flag: + +```c++ +if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { + framebufferResized = false; + recreateSwapChain(); +} else if (result != VK_SUCCESS) { + ... +} +``` + +Now to actually detect resizes we can use the `glfwSetFramebufferSizeCallback` function in the GLFW framework to set up a callback: + +```c++ +void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); +} + +static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + +} +``` + +The reason that we're creating a `static` function as a callback is because GLFW does not know how to properly call a member function with the right `this` pointer to our `HelloTriangleApplication` instance. + +However, we do get a reference to the `GLFWwindow` in the callback and there is another GLFW function that allows you to store an arbitrary pointer inside of it: `glfwSetWindowUserPointer`: + +```c++ +window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); +glfwSetWindowUserPointer(window, this); +glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); +``` + +This value can now be retrieved from within the callback with `glfwGetWindowUserPointer` to properly set the flag: + +```c++ +static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; +} +``` + +Now try to run the program and resize the window to see if the framebuffer is indeed resized properly with the window. + +## Handling minimization + +There is another case where a swap chain may become out of data and that is a special kind of window resizing: window minimization. This case is special because it will result in a frame buffer size of `0`. In this tutorial we will handle that by pausing until the window is in the foreground again by extending the `recreateSwapChain` function: + +```c++ +void recreateSwapChain() { + int width = 0, height = 0; + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + vkDeviceWaitIdle(device); + + ... +} +``` Congratulations, you've now finished your very first well-behaved Vulkan program! In the next chapter we're going to get rid of the hardcoded vertices in the vertex shader and actually use a vertex buffer. -[C++ code](/code/swap_chain_recreation.cpp) / -[Vertex shader](/code/shader_base.vert) / -[Fragment shader](/code/shader_base.frag) \ No newline at end of file +[C++ code](/code/16_swap_chain_recreation.cpp) / +[Vertex shader](/code/09_shader_base.vert) / +[Fragment shader](/code/09_shader_base.frag) diff --git a/04_Vertex_buffers/00_Vertex_input_description.md b/04_Vertex_buffers/00_Vertex_input_description.md index a6c47c64..360fe189 100644 --- a/04_Vertex_buffers/00_Vertex_input_description.md +++ b/04_Vertex_buffers/00_Vertex_input_description.md @@ -36,6 +36,18 @@ properties that are specified per-vertex in the vertex buffer, just like we manually specified a position and color per vertex using the two arrays. Make sure to recompile the vertex shader! +Just like `fragColor`, the `layout(location = x)` annotations assign indices to +the inputs that we can later use to reference them. It is important to know that +some types, like `dvec3` 64 bit vectors, use multiple *slots*. That means that +the index after it must be at least 2 higher: + +```glsl +layout(location = 0) in dvec3 inPosition; +layout(location = 2) in vec3 inColor; +``` + +You can find more info about the layout qualifier in the [OpenGL wiki](https://www.khronos.org/opengl/wiki/Layout_Qualifier_(GLSL)). + ## Vertex data We're moving the vertex data from the shader code to an array in the code of our @@ -202,7 +214,7 @@ auto bindingDescription = Vertex::getBindingDescription(); auto attributeDescriptions = Vertex::getAttributeDescriptions(); vertexInputInfo.vertexBindingDescriptionCount = 1; -vertexInputInfo.vertexAttributeDescriptionCount = attributeDescriptions.size(); +vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()); vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); ``` @@ -213,6 +225,6 @@ validation layers enabled, you'll see that it complains that there is no vertex buffer bound to the binding. The next step is to create a vertex buffer and move the vertex data to it so the GPU is able to access it. -[C++ code](/code/vertex_input.cpp) / -[Vertex shader](/code/shader_vertexbuffer.vert) / -[Fragment shader](/code/shader_vertexbuffer.frag) \ No newline at end of file +[C++ code](/code/17_vertex_input.cpp) / +[Vertex shader](/code/17_shader_vertexbuffer.vert) / +[Fragment shader](/code/17_shader_vertexbuffer.frag) diff --git a/04_Vertex_buffers/01_Vertex_buffer_creation.md b/04_Vertex_buffers/01_Vertex_buffer_creation.md index 2fcd41f2..20c01678 100644 --- a/04_Vertex_buffers/01_Vertex_buffer_creation.md +++ b/04_Vertex_buffers/01_Vertex_buffer_creation.md @@ -28,7 +28,7 @@ void initVulkan() { createCommandPool(); createVertexBuffer(); createCommandBuffers(); - createSemaphores(); + createSyncObjects(); } ... @@ -74,7 +74,7 @@ We can now create the buffer with `vkCreateBuffer`. Define a class member to hold the buffer handle and call it `vertexBuffer`. ```c++ -VDeleter vertexBuffer{device, vkDestroyBuffer}; +VkBuffer vertexBuffer; ... @@ -85,12 +85,26 @@ void createVertexBuffer() { bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - if (vkCreateBuffer(device, &bufferInfo, nullptr, vertexBuffer.replace()) != VK_SUCCESS) { + if (vkCreateBuffer(device, &bufferInfo, nullptr, &vertexBuffer) != VK_SUCCESS) { throw std::runtime_error("failed to create vertex buffer!"); } } ``` +The buffer should be available for use in rendering commands until the end of +the program and it does not depend on the swap chain, so we'll clean it up in +the original `cleanup` function: + +```c++ +void cleanup() { + cleanupSwapChain(); + + vkDestroyBuffer(device, vertexBuffer, nullptr); + + ... +} +``` + ## Memory requirements The buffer has been created, but it doesn't actually have any memory assigned to @@ -175,11 +189,11 @@ for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { } ``` -In the future we may have more than one desirable property, so we should check -if the result of the bitwise AND is not just non-zero, but equal to the desired -properties bit field. If there is a memory type suitable for the buffer that -also has all of the properties we need, then we return its index, otherwise we -throw an exception. +We may have more than one desirable property, so we should check if the result +of the bitwise AND is not just non-zero, but equal to the desired properties bit +field. If there is a memory type suitable for the buffer that also has all of +the properties we need, then we return its index, otherwise we throw an +exception. ## Memory allocation @@ -199,20 +213,16 @@ desired property. Create a class member to store the handle to the memory and allocate it with `vkAllocateMemory`. ```c++ -VDeleter vertexBuffer{device, vkDestroyBuffer}; -VDeleter vertexBufferMemory{device, vkFreeMemory}; +VkBuffer vertexBuffer; +VkDeviceMemory vertexBufferMemory; ... -if (vkAllocateMemory(device, &allocInfo, nullptr, vertexBufferMemory.replace()) != VK_SUCCESS) { +if (vkAllocateMemory(device, &allocInfo, nullptr, &vertexBufferMemory) != VK_SUCCESS) { throw std::runtime_error("failed to allocate vertex buffer memory!"); } ``` -Note that specifying the `vertexBuffer` and `vertexBufferMemory` members in this -order will cause the memory to be freed before the buffer is destroyed, but -that's allowed as long as the buffer is no longer used. - If memory allocation was successful, then we can now associate this memory with the buffer using `vkBindBufferMemory`: @@ -220,11 +230,24 @@ the buffer using `vkBindBufferMemory`: vkBindBufferMemory(device, vertexBuffer, vertexBufferMemory, 0); ``` -The first two parameters are self-explanatory and the third parameter is the +The first three parameters are self-explanatory and the fourth parameter is the offset within the region of memory. Since this memory is allocated specifically for this the vertex buffer, the offset is simply `0`. If the offset is non-zero, then it is required to be divisible by `memRequirements.alignment`. +Of course, just like dynamic memory allocation in C++, the memory should be +freed at some point. Memory that is bound to a buffer object may be freed once +the buffer is no longer used, so let's free it after the buffer has been +destroyed: + +```c++ +void cleanup() { + cleanupSwapChain(); + + vkDestroyBuffer(device, vertexBuffer, nullptr); + vkFreeMemory(device, vertexBufferMemory, nullptr); +``` + ## Filling the vertex buffer It is now time to copy the vertex data to the buffer. This is done by [mapping @@ -279,7 +302,7 @@ VkBuffer vertexBuffers[] = {vertexBuffer}; VkDeviceSize offsets[] = {0}; vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets); -vkCmdDraw(commandBuffers[i], vertices.size(), 1, 0, 0); +vkCmdDraw(commandBuffers[i], static_cast(vertices.size()), 1, 0, 0); ``` The `vkCmdBindVertexBuffers` function is used to bind vertex buffers to @@ -312,6 +335,6 @@ Run the program again and you should see the following: In the next chapter we'll look at a different way to copy vertex data to a vertex buffer that results in better performance, but takes some more work. -[C++ code](/code/vertex_buffer.cpp) / -[Vertex shader](/code/shader_vertexbuffer.vert) / -[Fragment shader](/code/shader_vertexbuffer.frag) \ No newline at end of file +[C++ code](/code/18_vertex_buffer.cpp) / +[Vertex shader](/code/17_shader_vertexbuffer.vert) / +[Fragment shader](/code/17_shader_vertexbuffer.frag) diff --git a/04_Vertex_buffers/02_Staging_buffer.md b/04_Vertex_buffers/02_Staging_buffer.md index ea166480..2a2e752c 100644 --- a/04_Vertex_buffers/02_Staging_buffer.md +++ b/04_Vertex_buffers/02_Staging_buffer.md @@ -44,14 +44,14 @@ to move buffer creation to a helper function. Create a new function `createBuffer` and move the code in `createVertexBuffer` (except mapping) to it. ```c++ -void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VDeleter& buffer, VDeleter& bufferMemory) { +void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) { VkBufferCreateInfo bufferInfo = {}; bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; bufferInfo.size = size; bufferInfo.usage = usage; bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - if (vkCreateBuffer(device, &bufferInfo, nullptr, buffer.replace()) != VK_SUCCESS) { + if (vkCreateBuffer(device, &bufferInfo, nullptr, &buffer) != VK_SUCCESS) { throw std::runtime_error("failed to create buffer!"); } @@ -63,7 +63,7 @@ void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyF allocInfo.allocationSize = memRequirements.size; allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); - if (vkAllocateMemory(device, &allocInfo, nullptr, bufferMemory.replace()) != VK_SUCCESS) { + if (vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) { throw std::runtime_error("failed to allocate buffer memory!"); } @@ -101,8 +101,8 @@ as temporary buffer and use a device local one as actual vertex buffer. void createVertexBuffer() { VkDeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); - VDeleter stagingBuffer{device, vkDestroyBuffer}; - VDeleter stagingBufferMemory{device, vkFreeMemory}; + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); void* data; @@ -229,10 +229,23 @@ createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERT copyBuffer(stagingBuffer, vertexBuffer, bufferSize); ``` -Run your program to verify that you're seeing the familiar triangle again. It -may not be visible, but its vertex data is now being loaded from high -performance memory. This will matter when we're going to start rendering more -complex geometry. +After copying the data from the staging buffer to the device buffer, we should +clean it up: + +```c++ + ... + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); +} +``` + +Run your program to verify that you're seeing the familiar triangle again. The +improvement may not be visible right now, but its vertex data is now being +loaded from high performance memory. This will matter when we're going to start +rendering more complex geometry. ## Conclusion @@ -245,12 +258,12 @@ objects at the same time is to create a custom allocator that splits up a single allocation among many different objects by using the `offset` parameters that we've seen in many functions. -You will currently have to write such an allocator yourself, but the author -expects that there will be a library at some point that can be integrated into -any Vulkan program to properly handle allocations. It's okay to use a separate -allocation for every resource for this tutorial, because we won't come close to +You can either implement such an allocator yourself, or use the +[VulkanMemoryAllocator](https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator) +library provided by the GPUOpen initiative. However, for this tutorial it's okay +to use a separate allocation for every resource, because we won't come close to hitting any of these limits for now. -[C++ code](/code/staging_buffer.cpp) / -[Vertex shader](/code/shader_vertexbuffer.vert) / -[Fragment shader](/code/shader_vertexbuffer.frag) +[C++ code](/code/19_staging_buffer.cpp) / +[Vertex shader](/code/17_shader_vertexbuffer.vert) / +[Fragment shader](/code/17_shader_vertexbuffer.frag) diff --git a/04_Vertex_buffers/03_Index_buffer.md b/04_Vertex_buffers/03_Index_buffer.md index e0323de8..5e997e15 100644 --- a/04_Vertex_buffers/03_Index_buffer.md +++ b/04_Vertex_buffers/03_Index_buffer.md @@ -54,10 +54,10 @@ the GPU to be able to access them. Define two new class members to hold the resources for the index buffer: ```c++ -VDeleter vertexBuffer{device, vkDestroyBuffer}; -VDeleter vertexBufferMemory{device, vkFreeMemory}; -VDeleter indexBuffer{device, vkDestroyBuffer}; -VDeleter indexBufferMemory{device, vkFreeMemory}; +VkBuffer vertexBuffer; +VkDeviceMemory vertexBufferMemory; +VkBuffer indexBuffer; +VkDeviceMemory indexBufferMemory; ``` The `createIndexBuffer` function that we'll add now is almost identical to @@ -74,8 +74,8 @@ void initVulkan() { void createIndexBuffer() { VkDeviceSize bufferSize = sizeof(indices[0]) * indices.size(); - VDeleter stagingBuffer{device, vkDestroyBuffer}; - VDeleter stagingBufferMemory{device, vkFreeMemory}; + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); void* data; @@ -86,6 +86,9 @@ void createIndexBuffer() { createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, indexBuffer, indexBufferMemory); copyBuffer(stagingBuffer, indexBuffer, bufferSize); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); } ``` @@ -97,6 +100,23 @@ number of indices times the size of the index type, either `uint16_t` or process is exactly the same. We create a staging buffer to copy the contents of `indices` to and then copy it to the final device local index buffer. +The index buffer should be cleaned up at the end of the program, just like the +vertex buffer: + +```c++ +void cleanup() { + cleanupSwapChain(); + + vkDestroyBuffer(device, indexBuffer, nullptr); + vkFreeMemory(device, indexBufferMemory, nullptr); + + vkDestroyBuffer(device, vertexBuffer, nullptr); + vkFreeMemory(device, vertexBufferMemory, nullptr); + + ... +} +``` + ## Using an index buffer Using an index buffer for drawing involves two changes to @@ -122,7 +142,7 @@ the drawing command to tell Vulkan to use the index buffer. Remove the `vkCmdDraw` line and replace it with `vkCmdDrawIndexed`: ```c++ -vkCmdDrawIndexed(commandBuffers[i], indices.size(), 1, 0, 0, 0); +vkCmdDrawIndexed(commandBuffers[i], static_cast(indices.size()), 1, 0, 0, 0); ``` A call to this function is very similar to `vkCmdDraw`. The first two parameters @@ -154,6 +174,6 @@ provided that their data is refreshed, of course. This is known as *aliasing* and some Vulkan functions have explicit flags to specify that you want to do this. -[C++ code](/code/index_buffer.cpp) / -[Vertex shader](/code/shader_vertexbuffer.vert) / -[Fragment shader](/code/shader_vertexbuffer.frag) +[C++ code](/code/20_index_buffer.cpp) / +[Vertex shader](/code/17_shader_vertexbuffer.vert) / +[Fragment shader](/code/17_shader_vertexbuffer.frag) diff --git a/05_Uniform_buffers/00_Descriptor_layout_and_buffer.md b/05_Uniform_buffers/00_Descriptor_layout_and_buffer.md index eb275d5d..842b152f 100644 --- a/05_Uniform_buffers/00_Descriptor_layout_and_buffer.md +++ b/05_Uniform_buffers/00_Descriptor_layout_and_buffer.md @@ -93,7 +93,11 @@ Note that the order of the `uniform`, `in` and `out` declarations doesn't matter. The `binding` directive is similar to the `location` directive for attributes. We're going to reference this binding in the descriptor layout. The line with `gl_Position` is changed to use the transformations to compute the -final position in clip coordinates. +final position in clip coordinates. Unlike the 2D triangles, the last component +of the clip coordinates may not be `1`, which will result in a division when +converted to the final normalized device coordinates on the screen. This is used +in perspective projection as the *perspective division* and is essential for +making closer objects look larger than objects that are further away. ## Descriptor set layout @@ -158,7 +162,7 @@ uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; ``` We also need to specify in which shader stages the descriptor is going to be -referenced. The `stageFlags` field can be a combination of `VkShaderStage` flags +referenced. The `stageFlags` field can be a combination of `VkShaderStageFlagBits` values or the value `VK_SHADER_STAGE_ALL_GRAPHICS`. In our case, we're only referencing the descriptor from the vertex shader. @@ -174,8 +178,8 @@ All of the descriptor bindings are combined into a single `pipelineLayout`: ```c++ -VDeleter descriptorSetLayout{device, vkDestroyDescriptorSetLayout}; -VDeleter pipelineLayout{device, vkDestroyPipelineLayout}; +VkDescriptorSetLayout descriptorSetLayout; +VkPipelineLayout pipelineLayout; ``` We can then create it using `vkCreateDescriptorSetLayout`. This function accepts @@ -187,7 +191,7 @@ layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; layoutInfo.bindingCount = 1; layoutInfo.pBindings = &uboLayoutBinding; -if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, descriptorSetLayout.replace()) != VK_SUCCESS) { +if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &descriptorSetLayout) != VK_SUCCESS) { throw std::runtime_error("failed to create descriptor set layout!"); } ``` @@ -198,11 +202,10 @@ specified in the pipeline layout object. Modify the `VkPipelineLayoutCreateInfo` to reference the layout object: ```c++ -VkDescriptorSetLayout setLayouts[] = {descriptorSetLayout}; VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; pipelineLayoutInfo.setLayoutCount = 1; -pipelineLayoutInfo.pSetLayouts = setLayouts; +pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout; ``` You may be wondering why it's possible to specify multiple descriptor set @@ -210,27 +213,40 @@ layouts here, because a single one already includes all of the bindings. We'll get back to that in the next chapter, where we'll look into descriptor pools and descriptor sets. +The descriptor layout should stick around while we may create new graphics +pipelines i.e. until the program ends: + +```c++ +void cleanup() { + cleanupSwapChain(); + + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); + + ... +} +``` + ## Uniform buffer In the next chapter we'll specify the buffer that contains the UBO data for the shader, but we need to create this buffer first. We're going to copy new data to -the uniform buffer every frame, so this time the staging buffer actually needs -to stick around. +the uniform buffer every frame, so it doesn't really make any sense to have a +staging buffer. It would just add extra overhead in this case and likely degrade +performance instead of improving it. -Add new class members for `uniformStagingBuffer`, `uniformStagingBufferMemory`, -`uniformBuffer`, and `uniformBufferMemory`: +We should have multiple buffers, because multiple frames may be in flight at the same time and we don't want to update the buffer in preparation of the next frame while a previous one is still reading from it! We could either have a uniform buffer per frame or per swap chain image. However, since we need to refer to the uniform buffer from the command buffer that we have per swap chain image, it makes the most sense to also have a uniform buffer per swap chain image. + +To that end, add new class members for `uniformBuffers`, and `uniformBuffersMemory`: ```c++ -VDeleter indexBuffer{device, vkDestroyBuffer}; -VDeleter indexBufferMemory{device, vkFreeMemory}; +VkBuffer indexBuffer; +VkDeviceMemory indexBufferMemory; -VDeleter uniformStagingBuffer{device, vkDestroyBuffer}; -VDeleter uniformStagingBufferMemory{device, vkFreeMemory}; -VDeleter uniformBuffer{device, vkDestroyBuffer}; -VDeleter uniformBufferMemory{device, vkFreeMemory}; +std::vector uniformBuffers; +std::vector uniformBuffersMemory; ``` -Similarly, create a new function `createUniformBuffer` that is called after +Similarly, create a new function `createUniformBuffers` that is called after `createIndexBuffer` and allocates the buffers: ```c++ @@ -247,40 +263,64 @@ void initVulkan() { void createUniformBuffer() { VkDeviceSize bufferSize = sizeof(UniformBufferObject); - createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformStagingBuffer, uniformStagingBufferMemory); - createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, uniformBuffer, uniformBufferMemory); + uniformBuffers.resize(swapChainImages.size()); + uniformBuffersMemory.resize(swapChainImages.size()); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + createBuffer(bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformBuffers[i], uniformBuffersMemory[i]); + } } ``` We're going to write a separate function that updates the uniform buffer with a -new transformation every frame, so there will be no `vkMapMemory` and -`copyBuffer` operations here. +new transformation every frame, so there will be no `vkMapMemory` here. The +uniform data will be used for all draw calls, so the buffer containing it should +only be destroyed at the end: ```c++ -void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); +void cleanup() { + cleanupSwapChain(); + + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); - updateUniformBuffer(); - drawFrame(); + for (size_t i = 0; i < swapChainImages.size(); i++) { + vkDestroyBuffer(device, uniformBuffers[i], nullptr); + vkFreeMemory(device, uniformBuffersMemory[i], nullptr); } - vkDeviceWaitIdle(device); + ... +} +``` + +## Updating uniform data + +Create a new function `updateUniformBuffer` and add a call to it from the `drawFrame` function right after we know which swap chain image we're going to acquire: + +```c++ +void drawFrame() { + ... - glfwDestroyWindow(window); + uint32_t imageIndex; + VkResult result = vkAcquireNextImageKHR(device, swapChain, std::numeric_limits::max(), imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + + ... + + updateUniformBuffer(imageIndex); + + VkSubmitInfo submitInfo = {}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + + ... } ... -void updateUniformBuffer() { +void updateUniformBuffer(uint32_t currentImage) { } ``` -## Updating uniform data - -Create a new function `updateUniformBuffer` and add a call to it from the main -loop. This function will generate a new transformation every frame to make the +This function will generate a new transformation every frame to make the geometry spin around. We need to include two new headers to implement this functionality: @@ -303,18 +343,16 @@ timekeeping. We'll use this to make sure that the geometry rotates 90 degrees per second regardless of frame rate. ```c++ -void updateUniformBuffer() { +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_cast(currentTime - startTime).count() / 1000.0f; + float time = std::chrono::duration(currentTime - startTime).count(); } ``` The `updateUniformBuffer` function will start out with some logic to calculate -the time in seconds since rendering has started with millisecond accuracy. If -you need timing to be more precise, then you can use `std::chrono::microseconds` -and divide by `1e6f`, which is short for `1000000.0f`. +the time in seconds since rendering has started with floating point accuracy. We will now define the model, view and projection transformations in the uniform buffer object. The model rotation will be a simple rotation around the @@ -322,11 +360,11 @@ Z-axis using the `time` variable: ```c++ UniformBufferObject ubo = {}; -ubo.model = glm::rotate(glm::mat4(), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); +ubo.model = glm::rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); ``` The `glm::rotate` function takes an existing transformation, rotation angle and -rotation axis as parameters. The `glm::mat4()` default constructor returns an +rotation axis as parameters. The `glm::mat4(1.0f)` constructor returns an identity matrix. Using a rotation angle of `time * glm::radians(90.0f)` accomplishes the purpose of rotation 90 degrees per second. @@ -358,27 +396,24 @@ sign on the scaling factor of the Y axis in the projection matrix. If you don't do this, then the image will be rendered upside down. All of the transformations are defined now, so we can copy the data in the -uniform buffer object to the uniform buffer. This happens in exactly the same -way as we did for vertex buffers with a staging buffer: +uniform buffer object to the current uniform buffer. This happens in exactly the same +way as we did for vertex buffers, except without a staging buffer: ```c++ void* data; -vkMapMemory(device, uniformStagingBufferMemory, 0, sizeof(ubo), 0, &data); +vkMapMemory(device, uniformBuffersMemory[currentImage], 0, sizeof(ubo), 0, &data); memcpy(data, &ubo, sizeof(ubo)); -vkUnmapMemory(device, uniformStagingBufferMemory); - -copyBuffer(uniformStagingBuffer, uniformBuffer, sizeof(ubo)); +vkUnmapMemory(device, uniformBuffersMemory[currentImage]); ``` -Using a staging buffer and final buffer this way is not the most efficient way -to pass frequently changing values to the shader. A more efficient way to pass a -small buffer of data to shaders are *push constants*. We may look at these in a -future chapter. +Using a UBO this way is not the most efficient way to pass frequently changing +values to the shader. A more efficient way to pass a small buffer of data to +shaders are *push constants*. We may look at these in a future chapter. In the next chapter we'll look at descriptor sets, which will actually bind the -`VkBuffer` to the uniform buffer descriptor so that the shader can access this +`VkBuffer`s to the uniform buffer descriptors so that the shader can access this transformation data. -[C++ code](/code/descriptor_layout.cpp) / -[Vertex shader](/code/shader_ubo.vert) / -[Fragment shader](/code/shader_ubo.frag) +[C++ code](/code/21_descriptor_layout.cpp) / +[Vertex shader](/code/21_shader_ubo.vert) / +[Fragment shader](/code/21_shader_ubo.frag) diff --git a/05_Uniform_buffers/01_Descriptor_pool_and_sets.md b/05_Uniform_buffers/01_Descriptor_pool_and_sets.md index 119bd8f7..6d2df71f 100644 --- a/05_Uniform_buffers/01_Descriptor_pool_and_sets.md +++ b/05_Uniform_buffers/01_Descriptor_pool_and_sets.md @@ -1,8 +1,8 @@ ## Introduction The descriptor layout from the previous chapter describes the type of -descriptors that can be bound. In this chapter we're going to create a -descriptor set, which will actually specify a `VkBuffer` resource to bind to the +descriptors that can be bound. In this chapter we're going to create +a descriptor set for each `VkBuffer` resource to bind it to the uniform buffer descriptor. ## Descriptor pool @@ -33,10 +33,10 @@ to contain and how many of them, using `VkDescriptorPoolSize` structures. ```c++ VkDescriptorPoolSize poolSize = {}; poolSize.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; -poolSize.descriptorCount = 1; +poolSize.descriptorCount = static_cast(swapChainImages.size()); ``` -We only have a single descriptor right now with the uniform buffer type. This +We will allocate one of these descriptors for every frame. This pool size structure is referenced by the main `VkDescriptorPoolCreateInfo`: ```c++ @@ -46,11 +46,12 @@ poolInfo.poolSizeCount = 1; poolInfo.pPoolSizes = &poolSize; ``` -We also need to specify the maximum number of descriptor sets that will be +Aside from the maximum number of individual descriptors that are available, we +also need to specify the maximum number of descriptor sets that may be allocated: ```c++ -poolInfo.maxSets = 1; +poolInfo.maxSets = static_cast(swapChainImages.size());; ``` The structure has an optional flag similar to command pools that determines if @@ -60,34 +61,45 @@ the descriptor set after creating it, so we don't need this flag. You can leave `flags` to its default value of `0`. ```c++ -VDeleter descriptorPool{device, vkDestroyDescriptorPool}; +VkDescriptorPool descriptorPool; ... -if (vkCreateDescriptorPool(device, &poolInfo, nullptr, descriptorPool.replace()) != VK_SUCCESS) { +if (vkCreateDescriptorPool(device, &poolInfo, nullptr, &descriptorPool) != VK_SUCCESS) { throw std::runtime_error("failed to create descriptor pool!"); } ``` Add a new class member to store the handle of the descriptor pool and call -`vkCreateDescriptorPool` to create it. +`vkCreateDescriptorPool` to create it. The descriptor pool should be destroyed +only at the end of the program, much like the other drawing related resources: + +```c++ +void cleanup() { + cleanupSwapChain(); + + vkDestroyDescriptorPool(device, descriptorPool, nullptr); + + ... +} +``` ## Descriptor set -We can now allocate the descriptor set itself. Add a `createDescriptorSet` +We can now allocate the descriptor sets themselves. Add a `createDescriptorSets` function for that purpose: ```c++ void initVulkan() { ... createDescriptorPool(); - createDescriptorSet(); + createDescriptorSets(); ... } ... -void createDescriptorSet() { +void createDescriptorSets() { } ``` @@ -97,53 +109,66 @@ struct. You need to specify the descriptor pool to allocate from, the number of descriptor sets to allocate, and the descriptor layout to base them on: ```c++ -VkDescriptorSetLayout layouts[] = {descriptorSetLayout}; +std::vector layouts(swapChainImages.size(), descriptorSetLayout); VkDescriptorSetAllocateInfo allocInfo = {}; allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; allocInfo.descriptorPool = descriptorPool; -allocInfo.descriptorSetCount = 1; -allocInfo.pSetLayouts = layouts; +allocInfo.descriptorSetCount = static_cast(swapChainImages.size()); +allocInfo.pSetLayouts = layouts.data(); ``` -Add a class member to hold the descriptor set handle and allocate it with +In our case we will create one descriptor set for each swap chain image, all with the same layout. Unfortunately we do need all the copies of the layout because the next function expects an array matching the number of sets. + +Add a class member to hold the descriptor set handles and allocate them with `vkAllocateDescriptorSets`: ```c++ -VDeleter descriptorPool{device, vkDestroyDescriptorPool}; -VkDescriptorSet descriptorSet; +VkDescriptorPool descriptorPool; +std::vector descriptorSets; ... -if (vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet) != VK_SUCCESS) { - throw std::runtime_error("failed to allocate descriptor set!"); +descriptorSets.resize(swapChainImages.size()); +if (vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets[0]) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate descriptor sets!"); } ``` -You don't need to use a deleter for descriptor sets, because they will be +You don't need to explicitly clean up descriptor sets, because they will be automatically freed when the descriptor pool is destroyed. The call to -`vkAllocateDescriptorSets` will allocate one descriptor set with one uniform +`vkAllocateDescriptorSets` will allocate descriptor sets, each with one uniform buffer descriptor. -The descriptor set has been allocated now, but the descriptors within still need -to be configured. Descriptors that refer to buffers, like our uniform buffer +The descriptor sets have been allocated now, but the descriptors within still need +to be configured. We'll now add a loop to populate every descriptor: + +```c++ +for (size_t i = 0; i < swapChainImages.size(); i++) { + +} +``` + +Descriptors that refer to buffers, like our uniform buffer descriptor, are configured with a `VkDescriptorBufferInfo` struct. This structure specifies the buffer and the region within it that contains the data -for the descriptor: +for the descriptor. ```c++ -VkDescriptorBufferInfo bufferInfo = {}; -bufferInfo.buffer = uniformBuffer; -bufferInfo.offset = 0; -bufferInfo.range = sizeof(UniformBufferObject); +for (size_t i = 0; i < swapChainImages.size(); i++) { + VkDescriptorBufferInfo bufferInfo = {}; + bufferInfo.buffer = uniformBuffers[i]; + bufferInfo.offset = 0; + bufferInfo.range = sizeof(UniformBufferObject); +} ``` -The configuration of descriptors is updated using the `vkUpdateDescriptorSets` +If you're overwriting the whole buffer, like we are in this case, then it is is also possible to use the `VK_WHOLE_SIZE` value for the range. The configuration of descriptors is updated using the `vkUpdateDescriptorSets` function, which takes an array of `VkWriteDescriptorSet` structs as parameter. ```c++ VkWriteDescriptorSet descriptorWrite = {}; descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; -descriptorWrite.dstSet = descriptorSet; +descriptorWrite.dstSet = descriptorSets[i]; descriptorWrite.dstBinding = 0; descriptorWrite.dstArrayElement = 0; ``` @@ -182,16 +207,17 @@ vkUpdateDescriptorSets(device, 1, &descriptorWrite, 0, nullptr); The updates are applied using `vkUpdateDescriptorSets`. It accepts two kinds of arrays as parameters: an array of `VkWriteDescriptorSet` and an array of -`VkCopyDescriptorSet`. The latter can be used to copy the configuration of -descriptors, as its name implies. +`VkCopyDescriptorSet`. The latter can be used to copy descriptors to each other, +as its name implies. -## Using a descriptor set +## Using descriptor sets We now need to update the `createCommandBuffers` function to actually bind the -descriptor set to the descriptors in the shader with `cmdBindDescriptorSets`: +right descriptor set for each swap chain image to the descriptors in the shader with `cmdBindDescriptorSets`. This needs to be done before the `vkCmdDrawIndexed` call: ```c++ -vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr); +vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets[i], 0, nullptr); +vkCmdDrawIndexed(commandBuffers[i], static_cast(indices.size()), 1, 0, 0, 0); ``` Unlike vertex and index buffers, descriptor sets are not unique to graphics @@ -208,7 +234,7 @@ visible. The problem is that because of the Y-flip we did in the projection matrix, the vertices are now being drawn in clockwise order instead of counter-clockwise order. This causes backface culling to kick in and prevents any geometry from being drawn. Go to the `createGraphicsPipeline` function and -modify the `cullFace` in `VkPipelineRasterizationStateCreateInfo` to correct +modify the `frontFace` in `VkPipelineRasterizationStateCreateInfo` to correct this: ```c++ @@ -221,14 +247,14 @@ Run your program again and you should now see the following: ![](/images/spinning_quad.png) The rectangle has changed into a square because the projection matrix now -corrects for aspect ratio. The `updateUniformData` takes care of screen +corrects for aspect ratio. The `updateUniformBuffer` takes care of screen resizing, so we don't need to recreate the descriptor set in `recreateSwapChain`. ## Multiple descriptor sets As some of the structures and function calls hinted at, it is actually possible -to bind multiple descriptor sets. You need to specify a descriptor layout for +to bind multiple descriptor sets simultaneously. You need to specify a descriptor layout for each descriptor set when creating the pipeline layout. Shaders can then reference specific descriptor sets like this: @@ -240,6 +266,6 @@ You can use this feature to put descriptors that vary per-object and descriptors that are shared into separate descriptor sets. In that case you avoid rebinding most of the descriptors across draw calls which is potentially more efficient. -[C++ code](/code/descriptor_set.cpp) / -[Vertex shader](/code/shader_ubo.vert) / -[Fragment shader](/code/shader_ubo.frag) +[C++ code](/code/22_descriptor_sets.cpp) / +[Vertex shader](/code/21_shader_ubo.vert) / +[Fragment shader](/code/21_shader_ubo.frag) diff --git a/06_Texture_mapping/00_Images.md b/06_Texture_mapping/00_Images.md index 84ab051d..741f063c 100644 --- a/06_Texture_mapping/00_Images.md +++ b/06_Texture_mapping/00_Images.md @@ -14,12 +14,16 @@ Adding a texture to our application will involve the following steps: We've already worked with image objects before, but those were automatically created by the swap chain extension. This time we'll have to create one by -ourselves. Creating an image and filling it with data is very similar to vertex -buffer creation. You create a `VkImage`, query its memory requirements, allocate -device memory, bind the memory to the image, and finally map the memory to -upload the pixel data. We'll use a staging and final image again, to make sure -that the texture image itself ends up in fast device local memory. There is a -command to copy the contents of images similar to `vkCmdCopyBuffer`. +ourselves. Creating an image and filling it with data is similar to vertex +buffer creation. We'll start by creating a staging resource and filling it with +pixel data and then we copy this to the final image object that we'll use for +rendering. Although it is possible to create a staging image for this purpose, +Vulkan also allows you to copy pixels from a `VkBuffer` to an image and the API +for this is actually [faster on some hardware](https://developer.nvidia.com/vulkan-memory-management). +We'll first create this buffer and fill it with pixel values, and then we'll +create an image to copy the pixels to. Creating an image is not very different +from creating buffers. It involves querying the memory requirements, allocating +device memory and binding it, just like we've seen before. However, there is something extra that we'll have to take care of when working with images. Images can have different *layouts* that affect how the pixels are @@ -33,9 +37,9 @@ these layouts when we specified the render pass: * `VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL`: Optimal as attachment for writing colors from the fragment shader * `VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL`: Optimal as source in a transfer -operation, like `vkCmdCopyImage` +operation, like `vkCmdCopyImageToBuffer` * `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL`: Optimal as destination in a transfer -operation, like `vkCmdCopyImage` +operation, like `vkCmdCopyBufferToImage` * `VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL`: Optimal for sampling from a shader One of the most common ways to transition the layout of an image is a *pipeline @@ -140,16 +144,51 @@ returned is the first element in an array of pixel values. The pixels are laid out row by row with 4 bytes per pixel in the case of `STBI_rgba_alpha` for a total of `texWidth * texHeight * 4` values. -## Staging image +## Staging buffer -We're now going to create an image in host visible memory so that we can use -`vkMapMemory` and copy the pixels to it. Pixels within an image object are known -as texels and we'll use that name from this point on. Add the following two -variables in the `createTextureImage` function: +We're now going to create a buffer in host visible memory so that we can use +`vkMapMemory` and copy the pixels to it. Add variables for this temporary buffer +to the `createTextureImage` function: ```c++ -VDeleter stagingImage{device, vkDestroyImage}; -VDeleter stagingImageMemory{device, vkFreeMemory}; +VkBuffer stagingBuffer; +VkDeviceMemory stagingBufferMemory; +``` + +The buffer should be in host visible memory so that we can map it and it should +be usable as a transfer source so that we can copy it to an image later on: + +```c++ +createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); +``` + +We can then directly copy the pixel values that we got from the image loading +library to the buffer: + +```c++ +void* data; +vkMapMemory(device, stagingBufferMemory, 0, imageSize, 0, &data); + memcpy(data, pixels, static_cast(imageSize)); +vkUnmapMemory(device, stagingBufferMemory); +``` + +Don't forget to clean up the original pixel array now: + +```c++ +stbi_image_free(pixels); +``` + +## Texture Image + +Although we could set up the shader to access the pixel values in the buffer, +it's better to use image objects in Vulkan for this purpose. Image objects will +make it easier and faster to retrieve colors by allowing us to use 2D +coordinates, for one. Pixels within an image object are known as texels and +we'll use that name from this point on. Add the following new class members: + +```c++ +VkImage textureImage; +VkDeviceMemory textureImageMemory; ``` The parameters for an image are specified in a `VkImageCreateInfo` struct: @@ -158,14 +197,14 @@ The parameters for an image are specified in a `VkImageCreateInfo` struct: VkImageCreateInfo imageInfo = {}; imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; imageInfo.imageType = VK_IMAGE_TYPE_2D; -imageInfo.extent.width = texWidth; -imageInfo.extent.height = texHeight; +imageInfo.extent.width = static_cast(texWidth); +imageInfo.extent.height = static_cast(texHeight); imageInfo.extent.depth = 1; imageInfo.mipLevels = 1; imageInfo.arrayLayers = 1; ``` -The image type, specified in the `imageType` field, tells Vulkan with that kind +The image type, specified in the `imageType` field, tells Vulkan with what kind of coordinate system the texels in the image are going to be addressed. It is possible to create 1D, 2D and 3D images. One dimensional images can be used to store an array of data or gradient, two dimensional images are mainly used for @@ -178,11 +217,12 @@ many texels there are on each axis. That's why `depth` must be `1` instead of imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM; ``` -Vulkan supports many possible image formats, but it makes the most sense to use -exactly the same format for the texels as the pixels loaded with the library. +Vulkan supports many possible image formats, but we should use the same format +for the texels as the pixels in the buffer, otherwise the copy operation will +fail. ```c++ -imageInfo.tiling = VK_IMAGE_TILING_LINEAR; +imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; ``` The `tiling` field can have one of two values: @@ -192,14 +232,14 @@ The `tiling` field can have one of two values: * `VK_IMAGE_TILING_OPTIMAL`: Texels are laid out in an implementation defined order for optimal access -If you want to be able to directly access texels in the memory of the image, -then you must use `VK_IMAGE_TILING_LINEAR`. We want to be able to directly copy -the data in `pixels` to the staging image memory, so we should use it. Unlike -the layout of an image, the tiling mode cannot be changed at a later time. We're -going to use `VK_IMAGE_TILING_OPTIMAL` for the final image. +Unlike the layout of an image, the tiling mode cannot be changed at a later +time. If you want to be able to directly access texels in the memory of the +image, then you must use `VK_IMAGE_TILING_LINEAR`. We will be using a staging +buffer instead of a staging image, so this won't be necessary. We will be using +`VK_IMAGE_TILING_OPTIMAL` for efficient access from the shader. ```c++ -imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED; +imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; ``` There are only two possible values for the `initialLayout` of an image: @@ -209,26 +249,31 @@ transition will discard the texels. * `VK_IMAGE_LAYOUT_PREINITIALIZED`: Not usable by the GPU, but the first transition will preserve the texels. -An initially undefined layout is suitable for images that will be used as -attachments, like color and depth buffers. In that case we don't care about any -initial data, because it'll probably be cleared by a render pass before use. If -you want to fill it with data, like a texture, then you should use the -preinitialized layout. +There are few situations where it is necessary for the texels to be preserved +during the first transition. One example, however, would be if you wanted to use +an image as a staging image in combination with the `VK_IMAGE_TILING_LINEAR` +layout. In that case, you'd want to upload the texel data to it and then +transition the image to be a transfer source without losing the data. In our +case, however, we're first going to transition the image to be a transfer +destination and then copy texel data to it from a buffer object, so we don't +need this property and can safely use `VK_IMAGE_LAYOUT_UNDEFINED`. ```c++ -imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT; +imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; ``` The `usage` field has the same semantics as the one during buffer creation. The -staging image is going to be copied to the final texture image, so it should be -set up as a transfer source. +image is going to be used as destination for the buffer copy, so it should be +set up as a transfer destination. We also want to be able to access the image +from the shader to color our mesh, so the usage should include +`VK_IMAGE_USAGE_SAMPLED_BIT`. ```c++ imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; ``` -The staging image will only be used by one queue family: the one that supports -transfer operations. +The image will only be used by one queue family: the one that supports graphics +(and therefore also) transfer operations. ```c++ imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; @@ -244,7 +289,7 @@ avoid allocating memory to store large volumes of "air" values. We won't be using it in this tutorial, so leave it to its default value of `0`. ```c++ -if (vkCreateImage(device, &imageInfo, nullptr, stagingImage.replace()) != VK_SUCCESS) { +if (vkCreateImage(device, &imageInfo, nullptr, &textureImage) != VK_SUCCESS) { throw std::runtime_error("failed to create image!"); } ``` @@ -259,126 +304,32 @@ this in the depth buffer chapter, where we'll implement such a system. ```c++ VkMemoryRequirements memRequirements; -vkGetImageMemoryRequirements(device, stagingImage, &memRequirements); +vkGetImageMemoryRequirements(device, textureImage, &memRequirements); VkMemoryAllocateInfo allocInfo = {}; allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; allocInfo.allocationSize = memRequirements.size; -allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); +allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); -if (vkAllocateMemory(device, &allocInfo, nullptr, stagingImageMemory.replace()) != VK_SUCCESS) { +if (vkAllocateMemory(device, &allocInfo, nullptr, &textureImageMemory) != VK_SUCCESS) { throw std::runtime_error("failed to allocate image memory!"); } -vkBindImageMemory(device, stagingImage, stagingImageMemory, 0); +vkBindImageMemory(device, textureImage, textureImageMemory, 0); ``` Allocating memory for an image works in exactly the same way as allocating memory for a buffer. Use `vkGetImageMemoryRequirements` instead of `vkGetBufferMemoryRequirements`, and use `vkBindImageMemory` instead of -`vkBindBufferMemory`. Remember that we need the memory to be host visible to be -able to use `vkMapMemory`, so you should specify that property when looking for -the right memory type. - -We can now use the `vkMapMemory` function to (temporarily) access the memory of -the staging image directly from our application. It returns a pointer to the -first byte in the memory buffer: - -```c++ -void* data; -vkMapMemory(device, stagingImageMemory, 0, imageSize, 0, &data); -``` - -Unfortunately we can't just copy the pixel bytes directly into the image memory -with `memcpy` and assume that this works correctly. The problem is that there -may be padding bytes between rows of pixels. In other words, the graphics card -may assume that one row of pixels is not `texWidth * 4` bytes wide, but rather -`texWidth * 4 + paddingBytes`. To handle this correctly, we need to query how -bytes are arranged in our staging image using `vkGetImageSubresourceLayout`: - -```c++ -VkImageSubresource subresource = {}; -subresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; -subresource.mipLevel = 0; -subresource.arrayLayer = 0; - -VkSubresourceLayout stagingImageLayout; -vkGetImageSubresourceLayout(device, stagingImage, &subresource, &stagingImageLayout); -``` - -Images contain one or more *subresources*, which are specific images within an -image. For example, there is one subresource for every entry in an array image. -In this case we don't have an array image, so there is simply one subresource at -entry 0 and the base mipmapping level. - -The `rowPitch` member of the `VkSubresourceLayout` struct specifies the total -number of bytes of each row of pixels in the image. If this value is equal to -`texWidth * 4`, then we're lucky and we *can* use `memcpy`, because there are no -padding bytes in that case. - -```c++ -if (stagingImageLayout.rowPitch == texWidth * 4) { - memcpy(data, pixels, (size_t) imageSize); -} else { - -} -``` - -This is usually the case when your images have a power-of-2 size (e.g. 512 or -1024). Otherwise, we'll have to copy the pixels row-by-row using the right -offset: - -```c++ -uint8_t* dataBytes = reinterpret_cast(data); - -for (int y = 0; y < texHeight; y++) { - memcpy( - &dataBytes[y * stagingImageLayout.rowPitch], - &pixels[y * texWidth * 4], - texWidth * 4 - ); -} -``` - -Each subsequent row in the image memory is offset by `rowPitch` and the original -pixels are offset by `texWidth * 4` without padding bytes. - -If you're done accessing the memory buffer, then you should unmap it with -`vkUnmapMemory`. It is not necessary to call `vkUnmapMemory` now if you want to -access the staging image memory again later on. The writes to the buffer will -already be visible without calling this function. - -```c++ -void* data; -vkMapMemory(device, stagingImageMemory, 0, imageSize, 0, &data); - - if (stagingImageLayout.rowPitch == texWidth * 4) { - memcpy(data, pixels, (size_t) imageSize); - } else { - uint8_t* dataBytes = reinterpret_cast(data); - - for (int y = 0; y < texHeight; y++) { - memcpy(&dataBytes[y * stagingImageLayout.rowPitch], &pixels[y * texWidth * 4], texWidth * 4); - } - } - -vkUnmapMemory(device, stagingImageMemory); -``` +`vkBindBufferMemory`. -Don't forget to clean up the original pixel array now: - -```c++ -stbi_image_free(pixels); -``` - -## Texture image - -We will now abstract image creation into a `createImage` function, like we did -for buffers. Create the function and move the image object creation and memory -allocation to it: +This function is already getting quite large and there'll be a need to create +more images in later chapters, so we should abstract image creation into a +`createImage` function, like we did for buffers. Create the function and move +the image object creation and memory allocation to it: ```c++ -void createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VDeleter& image, VDeleter& imageMemory) { +void createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage& image, VkDeviceMemory& imageMemory) { VkImageCreateInfo imageInfo = {}; imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; imageInfo.imageType = VK_IMAGE_TYPE_2D; @@ -389,12 +340,12 @@ void createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling imageInfo.arrayLayers = 1; imageInfo.format = format; imageInfo.tiling = tiling; - imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED; + imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; imageInfo.usage = usage; imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - if (vkCreateImage(device, &imageInfo, nullptr, image.replace()) != VK_SUCCESS) { + if (vkCreateImage(device, &imageInfo, nullptr, &image) != VK_SUCCESS) { throw std::runtime_error("failed to create image!"); } @@ -406,7 +357,7 @@ void createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling allocInfo.allocationSize = memRequirements.size; allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); - if (vkAllocateMemory(device, &allocInfo, nullptr, imageMemory.replace()) != VK_SUCCESS) { + if (vkAllocateMemory(device, &allocInfo, nullptr, &imageMemory) != VK_SUCCESS) { throw std::runtime_error("failed to allocate image memory!"); } @@ -430,71 +381,21 @@ void createTextureImage() { throw std::runtime_error("failed to load texture image!"); } - VDeleter stagingImage{device, vkDestroyImage}; - VDeleter stagingImageMemory{device, vkFreeMemory}; - createImage(texWidth, texHeight, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_LINEAR, VK_IMAGE_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingImage, stagingImageMemory); - - VkImageSubresource subresource = {}; - subresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - subresource.mipLevel = 0; - subresource.arrayLayer = 0; - - VkSubresourceLayout stagingImageLayout; - vkGetImageSubresourceLayout(device, stagingImage, &subresource, &stagingImageLayout); + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); void* data; - vkMapMemory(device, stagingImageMemory, 0, imageSize, 0, &data); - - if (stagingImageLayout.rowPitch == texWidth * 4) { - memcpy(data, pixels, (size_t) imageSize); - } else { - uint8_t* dataBytes = reinterpret_cast(data); - - for (int y = 0; y < texHeight; y++) { - memcpy(&dataBytes[y * stagingImageLayout.rowPitch], &pixels[y * texWidth * 4], texWidth * 4); - } - } - - vkUnmapMemory(device, stagingImageMemory); + vkMapMemory(device, stagingBufferMemory, 0, imageSize, 0, &data); + memcpy(data, pixels, static_cast(imageSize)); + vkUnmapMemory(device, stagingBufferMemory); stbi_image_free(pixels); -} -``` - -The next step is to create the actual texture image. Define two new class -members to hold the handle to the image and its memory: -```c++ -VDeleter commandPool{device, vkDestroyCommandPool}; -VDeleter textureImage{device, vkDestroyImage}; -VDeleter textureImageMemory{device, vkFreeMemory}; -VDeleter vertexBuffer{device, vkDestroyBuffer}; -``` - -The final texture image can now be created using the same function: - -```c++ -createImage( - texWidth, texHeight, - VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_TILING_OPTIMAL, - VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, - textureImage, - textureImageMemory -); + createImage(texWidth, texHeight, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, textureImage, textureImageMemory); +} ``` -The dimensions of the image should be the same as the staging image. The formats -should also be *compatible*, because the command simply copies the raw image -data. Two color formats are compatible if they have the same number of bytes per -pixel. Depth/stencil formats, which we'll see in one of the next chapters, need -to be exactly equal. The tiling mode on the other hand does not need to be the -same. The texture image will be used as the destination in the transfer, and we -want to be able to sample texels from it in the shader. The -`VK_IMAGE_USAGE_SAMPLED_BIT` flag is necessary to allow that. The memory of the -image should be device local for best performance, just like the vertex buffer. - ## Layout transitions The function we're going to write now involves recording and executing a command @@ -552,9 +453,9 @@ void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { ``` If we were still using buffers, then we could now write a function to record and -execute `vkCmdCopyImage` to finish the job, but this command requires the images -to be in the right layout first. Create a new function to handle layout -transitions: +execute `vkCmdCopyBufferToImage` to finish the job, but this command requires +the image to be in the right layout first. Create a new function to handle +layout transitions: ```c++ void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout) { @@ -601,7 +502,7 @@ barrier.subresourceRange.layerCount = 1; ``` The `image` and `subresourceRange` specify the image that is affected and the -specific part of the image. Our image is not an array and does not mipmapping +specific part of the image. Our image is not an array and does not have mipmapping levels, so only one level and layer are specified. ```c++ @@ -619,7 +520,7 @@ back to this once we've figured out which transitions we're going to use. ```c++ vkCmdPipelineBarrier( commandBuffer, - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + 0 /* TODO */, 0 /* TODO */, 0, 0, nullptr, 0, nullptr, @@ -628,10 +529,19 @@ vkCmdPipelineBarrier( ``` All types of pipeline barriers are submitted using the same function. The first -parameter specifies in which pipeline stage the operations occur that should -happen before the barrier. The second parameter specifies the pipeline stage in -which operations will wait on the barrier. We want it to happen immediately, so -we're going with the top of the pipeline. +parameter after the command buffer specifies in which pipeline stage the +operations occur that should happen before the barrier. The second parameter +specifies the pipeline stage in which operations will wait on the barrier. The +pipeline stages that you are allowed to specify before and after the barrier +depend on how you use the resource before and after the barrier. The allowed +values are listed in [this table](https://www.khronos.org/registry/vulkan/specs/1.0/html/vkspec.html#synchronization-access-types-supported) +of the specification. For example, if you're going to read from a uniform after +the barrier, you would specify a usage of `VK_ACCESS_UNIFORM_READ_BIT` and the +earliest shader that will read from the uniform as pipeline stage, for example +`VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT`. It would not make sense to specify +a non-shader pipeline stage for this type of usage and the validation layers +will warn you when you specify a pipeline stage that does not match the type of +usage. The third parameter is either `0` or `VK_DEPENDENCY_BY_REGION_BIT`. The latter turns the barrier into a per-region condition. That means that the @@ -644,81 +554,94 @@ barriers like the one we're using here. Note that we're not using the `VkFormat` parameter yet, but we'll be using that one for special transitions in the depth buffer chapter. -## Copying images +## Copying buffer to image Before we get back to `createTextureImage`, we're going to write one more helper -function: `copyImage`: +function: `copyBufferToImage`: ```c++ -void copyImage(VkImage srcImage, VkImage dstImage, uint32_t width, uint32_t height) { +void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) { VkCommandBuffer commandBuffer = beginSingleTimeCommands(); endSingleTimeCommands(commandBuffer); } ``` -Just like with buffers, you need to specify which part of the image needs to be -copied to which part of the other image. This happens through `VkImageCopy` -structs: +Just like with buffer copies, you need to specify which part of the buffer is +going to be copied to which part of the image. This happens through +`VkBufferImageCopy` structs: ```c++ -VkImageSubresourceLayers subResource = {}; -subResource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; -subResource.baseArrayLayer = 0; -subResource.mipLevel = 0; -subResource.layerCount = 1; +VkBufferImageCopy region = {}; +region.bufferOffset = 0; +region.bufferRowLength = 0; +region.bufferImageHeight = 0; -VkImageCopy region = {}; -region.srcSubresource = subResource; -region.dstSubresource = subResource; -region.srcOffset = {0, 0, 0}; -region.dstOffset = {0, 0, 0}; -region.extent.width = width; -region.extent.height = height; -region.extent.depth = 1; +region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; +region.imageSubresource.mipLevel = 0; +region.imageSubresource.baseArrayLayer = 0; +region.imageSubresource.layerCount = 1; + +region.imageOffset = {0, 0, 0}; +region.imageExtent = { + width, + height, + 1 +}; ``` -All of these fields are fairly self-explanatory. Image copy operations are -enqueued using the `vkCmdCopyImage` function: +Most of these fields are self-explanatory. The `bufferOffset` specifies the byte +offset in the buffer at which the pixel values start. The `bufferRowLength` and +`bufferImageHeight` fields specify how the pixels are laid out in memory. For +example, you could have some padding bytes between rows of the image. Specifying +`0` for both indicates that the pixels are simply tightly packed like they are +in our case. The `imageSubresource`, `imageOffset` and `imageExtent` fields +indicate to which part of the image we want to copy the pixels. + +Buffer to image copy operations are enqueued using the `vkCmdCopyBufferToImage` +function: ```c++ -vkCmdCopyImage( +vkCmdCopyBufferToImage( commandBuffer, - srcImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - dstImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - 1, ®ion + buffer, + image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + ®ion ); ``` -The first two pairs of parameters specify the source image/layout and -destination image/layout. I'm assuming here that they've been previously -transitioned to the optimal transfer layouts. +The fourth parameter indicates which layout the image is currently using. I'm +assuming here that the image has already been transitioned to the layout that is +optimal for copying pixels to. Right now we're only copying one chunk of pixels +to the whole image, but it's possible to specify an array of `VkBufferImageCopy` +to perform many different copies from this buffer to the image in one operation. ## Preparing the texture image We now have all of the tools we need to finish setting up the texture image, so we're going back to the `createTextureImage` function. The last thing we did -there was creating the texture image. The next step is to copy the staging image -to the texture image. This involves three operations: +there was creating the texture image. The next step is to copy the staging +buffer to the texture image. This involves two steps: -* Transition the staging image to `VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL` * Transition the texture image to `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL` -* Execute the image copy operation +* Execute the buffer to image copy operation This is easy to do with the functions we just created: ```c++ -transitionImageLayout(stagingImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_PREINITIALIZED, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); -transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_PREINITIALIZED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); -copyImage(stagingImage, textureImage, texWidth, texHeight); +transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); +copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); ``` -Both `VK_IMAGE_LAYOUT_PREINITIALIZED` and `VK_IMAGE_LAYOUT_UNDEFINED` are valid -values for old layout when transitioning `textureImage`, because we don't care -about its contents before the copy operation. +The image was created with the `VK_IMAGE_LAYOUT_UNDEFINED` layout, so that one +should be specified as old layout when transitioning `textureImage`. Remember +that we can do this because we don't care about its contents before performing +the copy operation. To be able to start sampling from the texture image in the shader, we need one -last transition: +last transition to prepare it for shader access: ```c++ transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); @@ -726,44 +649,75 @@ transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_TR ## Transition barrier masks -If run your application with validation layers enabled now, then you'll see that -it complains about the access masks in `transitionImageLayout` being invalid. -We still need to set those based on the layouts in the transition. +If you run your application with validation layers enabled now, then you'll see that +it complains about the access masks and pipeline stages in +`transitionImageLayout` being invalid. We still need to set those based on the +layouts in the transition. -There are three transitions we need to handle: +There are two transitions we need to handle: -* Preinitialized → transfer source: transfer reads should wait on host writes -* Preinitialized → transfer destination: transfer writes should wait on host -writes +* Undefined → transfer destination: transfer writes that don't need to wait on +anything * Transfer destination → shader reading: shader reads should wait on transfer -writes +writes, specifically the shader reads in the fragment shader, because that's +where we're going to use the texture -These rules are specified using the following access masks: +These rules are specified using the following access masks and pipeline stages: ```c++ -if (oldLayout == VK_IMAGE_LAYOUT_PREINITIALIZED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL) { - barrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT; - barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; -} else if (oldLayout == VK_IMAGE_LAYOUT_PREINITIALIZED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { - barrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT; +VkPipelineStageFlags sourceStage; +VkPipelineStageFlags destinationStage; + +if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { + barrier.srcAccessMask = 0; barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + + sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT; } else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + + sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; } else { throw std::invalid_argument("unsupported layout transition!"); } + +vkCmdPipelineBarrier( + commandBuffer, + sourceStage, destinationStage, + 0, + 0, nullptr, + 0, nullptr, + 1, &barrier +); ``` +As you can see in the aforementioned table, transfer writes must occur in the +pipeline transfer stage. Since the writes don't have to wait on anything, you +may specify an empty access mask and the earliest possible pipeline stage +`VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT` for the pre-barrier operations. It should be +noted that `VK_PIPELINE_STAGE_TRANSFER_BIT` is not a *real* stage within the +graphics and compute pipelines. It is more of a pseudo-stage where transfers +happen. See [the documentation](https://www.khronos.org/registry/vulkan/specs/1.1-extensions/man/html/VkPipelineStageFlagBits.html) +for more information and other examples of pseudo-stages. + +The image will be written in the same pipeline stage and subsequently read by +the fragment shader, which is why we specify shader reading access in the +fragment shader pipeline stage. + If we need to do more transitions in the future, then we'll extend the function. The application should now run successfully, although there are of course no -visual changes yet. One thing to note is that command buffer submission results -in implicit `VK_ACCESS_HOST_WRITE_BIT` synchronization at the beginning. Since -the `transitionImageLayout` function executes a command buffer with only a -single command, we can use this implicit synchronization and set `srcAccessMask` -to `0` for the first two types of transitions. It's up to you if you want to be -explicit about it or not, but I'm personally not a fan of relying on these -OpenGL-like "hidden" operations. +visual changes yet. + +One thing to note is that command buffer submission results in implicit +`VK_ACCESS_HOST_WRITE_BIT` synchronization at the beginning. Since the +`transitionImageLayout` function executes a command buffer with only a single +command, you could use this implicit synchronization and set `srcAccessMask` to +`0` if you ever needed a `VK_ACCESS_HOST_WRITE_BIT` dependency in a layout +transition. It's up to you if you want to be explicit about it or not, but I'm +personally not a fan of relying on these OpenGL-like "hidden" operations. There is actually a special type of image layout that supports all operations, `VK_IMAGE_LAYOUT_GENERAL`. The problem with it, of course, is that it doesn't @@ -781,15 +735,35 @@ commands into, and add a `flushSetupCommands` to execute the commands that have been recorded so far. It's best to do this after the texture mapping works to check if the texture resources are still set up correctly. -In this tutorial we used another image as staging resource for the texture, but -it's also possible to use a buffer and copy pixels from it using -`vkCmdCopyBufferToImage`. It is recommended to use this approach for improved -performance on [some hardware](https://developer.nvidia.com/vulkan-memory-management) -if you need to update the data in an image often. +## Cleanup + +Finish the `createTextureImage` function by cleaning up the staging buffer and +its memory at the end: + +```c++ + transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); +} +``` + +The main texture image is used until the end of the program: + +```c++ +void cleanup() { + cleanupSwapChain(); + + vkDestroyImage(device, textureImage, nullptr); + vkFreeMemory(device, textureImageMemory, nullptr); + + ... +} +``` The image now contains the texture, but we still need a way to access it from the graphics pipeline. We'll work on that in the next chapter. -[C++ code](/code/texture_image.cpp) / -[Vertex shader](/code/shader_ubo.vert) / -[Fragment shader](/code/shader_ubo.frag) \ No newline at end of file +[C++ code](/code/23_texture_image.cpp) / +[Vertex shader](/code/21_shader_ubo.vert) / +[Fragment shader](/code/21_shader_ubo.frag) diff --git a/06_Texture_mapping/01_Image_view_and_sampler.md b/06_Texture_mapping/01_Image_view_and_sampler.md index cf658e46..8b8db25e 100644 --- a/06_Texture_mapping/01_Image_view_and_sampler.md +++ b/06_Texture_mapping/01_Image_view_and_sampler.md @@ -13,7 +13,7 @@ Add a class member to hold a `VkImageView` for the texture image and create a new function `createTextureImageView` where we'll create it: ```c++ -VDeleter textureImageView{device, vkDestroyImageView}; +VkImageView textureImageView; ... @@ -53,7 +53,7 @@ I've left out the explicit `viewInfo.components` initialization, because image view by calling `vkCreateImageView`: ```c++ -if (vkCreateImageView(device, &viewInfo, nullptr, textureImageView.replace()) != VK_SUCCESS) { +if (vkCreateImageView(device, &viewInfo, nullptr, &textureImageView) != VK_SUCCESS) { throw std::runtime_error("failed to create texture image view!"); } ``` @@ -62,7 +62,7 @@ Because so much of the logic is duplicated from `createImageViews`, you may wish to abstract it into a new `createImageView` function: ```c++ -void createImageView(VkImage image, VkFormat format, VDeleter& imageView) { +VkImageView createImageView(VkImage image, VkFormat format) { VkImageViewCreateInfo viewInfo = {}; viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; viewInfo.image = image; @@ -74,9 +74,12 @@ void createImageView(VkImage image, VkFormat format, VDeleter& imag viewInfo.subresourceRange.baseArrayLayer = 0; viewInfo.subresourceRange.layerCount = 1; - if (vkCreateImageView(device, &viewInfo, nullptr, imageView.replace()) != VK_SUCCESS) { + VkImageView imageView; + if (vkCreateImageView(device, &viewInfo, nullptr, &imageView) != VK_SUCCESS) { throw std::runtime_error("failed to create texture image view!"); } + + return imageView; } ``` @@ -84,7 +87,7 @@ The `createTextureImageView` function can now be simplified to: ```c++ void createTextureImageView() { - createImageView(textureImage, VK_FORMAT_R8G8B8A8_UNORM, textureImageView); + textureImageView = createImageView(textureImage, VK_FORMAT_R8G8B8A8_UNORM); } ``` @@ -92,14 +95,27 @@ And `createImageViews` can be simplified to: ```c++ void createImageViews() { - swapChainImageViews.resize(swapChainImages.size(), VDeleter{device, vkDestroyImageView}); + swapChainImageViews.resize(swapChainImages.size()); for (uint32_t i = 0; i < swapChainImages.size(); i++) { - createImageView(swapChainImages[i], swapChainImageFormat, swapChainImageViews[i]); + swapChainImageViews[i] = createImageView(swapChainImages[i], swapChainImageFormat); } } ``` +Make sure to destroy the image view at the end of the program, right before +destroying the image itself: + +```c++ +void cleanup() { + cleanupSwapChain(); + + vkDestroyImageView(device, textureImageView, nullptr); + + vkDestroyImage(device, textureImage, nullptr); + vkFreeMemory(device, textureImageMemory, nullptr); +``` + ## Samplers It is possible for shaders to read texels directly from images, but that is not @@ -213,7 +229,7 @@ There is no graphics hardware available today that will use more than 16 samples, because the difference is negligible beyond that point. ```c++ -samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK ; +samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; ``` The `borderColor` field specifies which color is returned when sampling beyond @@ -240,7 +256,7 @@ samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS; If a comparison function is enabled, then texels will first be compared to a value, and the result of that comparison is used in filtering operations. This -is mainly used for [percentage-closer filtering](http://http.developer.nvidia.com/GPUGems/gpugems_ch11.html) +is mainly used for [percentage-closer filtering](https://developer.nvidia.com/gpugems/GPUGems/gpugems_ch11.html) on shadow maps. We'll look at this in a future chapter. ```c++ @@ -250,23 +266,23 @@ samplerInfo.minLod = 0.0f; samplerInfo.maxLod = 0.0f; ``` -All of these fields apply to mipmapping. We will look at mipmapping in a future -chapter, but basically it's another type of filter that can be applied. +All of these fields apply to mipmapping. We will look at mipmapping in a [later +chapter](/Generating_Mipmaps), but basically it's another type of filter that can be applied. The functioning of the sampler is now fully defined. Add a class member to hold the handle of the sampler object and create the sampler with `vkCreateSampler`: ```c++ -VDeleter textureImageView{device, vkDestroyImageView}; -VDeleter textureSampler{device, vkDestroySampler}; +VkImageView textureImageView; +VkSampler textureSampler; ... void createTextureSampler() { ... - if (vkCreateSampler(device, &samplerInfo, nullptr, textureSampler.replace()) != VK_SUCCESS) { + if (vkCreateSampler(device, &samplerInfo, nullptr, &textureSampler) != VK_SUCCESS) { throw std::runtime_error("failed to create texture sampler!"); } } @@ -278,9 +294,64 @@ can be applied to any image you want, whether it is 1D, 2D or 3D. This is different from many older APIs, which combined texture images and filtering into a single state. +Destroy the sampler at the end of the program when we'll no longer be accessing +the image: + +```c++ +void cleanup() { + cleanupSwapChain(); + + vkDestroySampler(device, textureSampler, nullptr); + vkDestroyImageView(device, textureImageView, nullptr); + + ... +} +``` + +## Anisotropy device feature + +If you run your program right now, you'll see a validation layer message like +this: + +![](/images/validation_layer_anisotropy.png) + +That's because anisotropic filtering is actually an optional device feature. We +need to update the `createLogicalDevice` function to request it: + +```c++ +VkPhysicalDeviceFeatures deviceFeatures = {}; +deviceFeatures.samplerAnisotropy = VK_TRUE; +``` + +And even though it is very unlikely that a modern graphics card will not support +it, we should update `isDeviceSuitable` to check if it is available: + +```c++ +bool isDeviceSuitable(VkPhysicalDevice device) { + ... + + VkPhysicalDeviceFeatures supportedFeatures; + vkGetPhysicalDeviceFeatures(device, &supportedFeatures); + + return indices.isComplete() && extensionsSupported && swapChainAdequate && supportedFeatures.samplerAnisotropy; +} +``` + +The `vkGetPhysicalDeviceFeatures` repurposes the `VkPhysicalDeviceFeatures` +struct to indicate which features are supported rather than requested by setting +the boolean values. + +Instead of enforcing the availability of anisotropic filtering, it's also +possible to simply not use it by conditionally setting: + +```c++ +samplerInfo.anisotropyEnable = VK_FALSE; +samplerInfo.maxAnisotropy = 1; +``` + In the next chapter we will expose the image and sampler objects to the shaders to draw the texture onto the square. -[C++ code](/code/sampler.cpp) / -[Vertex shader](/code/shader_ubo.vert) / -[Fragment shader](/code/shader_ubo.frag) \ No newline at end of file +[C++ code](/code/24_sampler.cpp) / +[Vertex shader](/code/21_shader_ubo.vert) / +[Fragment shader](/code/21_shader_ubo.frag) diff --git a/06_Texture_mapping/02_Combined_image_sampler.md b/06_Texture_mapping/02_Combined_image_sampler.md index 10138400..3a86ad6b 100644 --- a/06_Texture_mapping/02_Combined_image_sampler.md +++ b/06_Texture_mapping/02_Combined_image_sampler.md @@ -28,7 +28,7 @@ samplerLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; std::array bindings = {uboLayoutBinding, samplerLayoutBinding}; VkDescriptorSetLayoutCreateInfo layoutInfo = {}; layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; -layoutInfo.bindingCount = bindings.size(); +layoutInfo.bindingCount = static_cast(bindings.size()); layoutInfo.pBindings = bindings.data(); ``` @@ -39,7 +39,7 @@ the vertex shader, for example to dynamically deform a grid of vertices by a [heightmap](https://en.wikipedia.org/wiki/Heightmap). If you would run the application with validation layers now, then you'll see -that it complains that the descriptor pool cannot allocate a descriptor set with +that it complains that the descriptor pool cannot allocate descriptor sets with this layout, because it doesn't have any combined image sampler descriptors. Go to the `createDescriptorPool` function and modify it to include a `VkDescriptorPoolSize` for this descriptor: @@ -47,25 +47,34 @@ to the `createDescriptorPool` function and modify it to include a ```c++ std::array poolSizes = {}; poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; -poolSizes[0].descriptorCount = 1; +poolSizes[0].descriptorCount = static_cast(swapChainImages.size()); poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; -poolSizes[1].descriptorCount = 1; +poolSizes[1].descriptorCount = static_cast(swapChainImages.size()); VkDescriptorPoolCreateInfo poolInfo = {}; poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; -poolInfo.poolSizeCount = poolSizes.size(); +poolInfo.poolSizeCount = static_cast(poolSizes.size()); poolInfo.pPoolSizes = poolSizes.data(); -poolInfo.maxSets = 1; +poolInfo.maxSets = static_cast(swapChainImages.size()); ``` The final step is to bind the actual image and sampler resources to the -descriptor in the descriptor set. Go to the `createDescriptorSet` function. +descriptors in the descriptor set. Go to the `createDescriptorSets` function. ```c++ -VkDescriptorImageInfo imageInfo = {}; -imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; -imageInfo.imageView = textureImageView; -imageInfo.sampler = textureSampler; +for (size_t i = 0; i < swapChainImages.size(); i++) { + VkDescriptorBufferInfo bufferInfo = {}; + bufferInfo.buffer = uniformBuffers[i]; + bufferInfo.offset = 0; + bufferInfo.range = sizeof(UniformBufferObject); + + VkDescriptorImageInfo imageInfo = {}; + imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + imageInfo.imageView = textureImageView; + imageInfo.sampler = textureSampler; + + ... +} ``` The resources for a combined image sampler structure must be specified in a @@ -77,7 +86,7 @@ where the objects from the previous chapter come together. std::array descriptorWrites = {}; descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; -descriptorWrites[0].dstSet = descriptorSet; +descriptorWrites[0].dstSet = descriptorSets[i]; descriptorWrites[0].dstBinding = 0; descriptorWrites[0].dstArrayElement = 0; descriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; @@ -85,19 +94,19 @@ descriptorWrites[0].descriptorCount = 1; descriptorWrites[0].pBufferInfo = &bufferInfo; descriptorWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; -descriptorWrites[1].dstSet = descriptorSet; +descriptorWrites[1].dstSet = descriptorSets[i]; descriptorWrites[1].dstBinding = 1; descriptorWrites[1].dstArrayElement = 0; descriptorWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; descriptorWrites[1].descriptorCount = 1; descriptorWrites[1].pImageInfo = &imageInfo; -vkUpdateDescriptorSets(device, descriptorWrites.size(), descriptorWrites.data(), 0, nullptr); +vkUpdateDescriptorSets(device, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); ``` -The descriptor must be updated with this image info, just like the buffer. This -time we're using the `pImageInfo` array instead of `pBufferInfo`. The descriptor -is now ready to be used by the shaders! +The descriptors must be updated with this image info, just like the buffer. This +time we're using the `pImageInfo` array instead of `pBufferInfo`. The descriptors +are now ready to be used by the shaders! ## Texture coordinates @@ -151,10 +160,10 @@ square. ```c++ const std::vector vertices = { - {{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}, - {{0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}}, - {{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, - {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}} + {{-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}} }; ``` @@ -265,6 +274,6 @@ when combined with images that are also written to in framebuffers. You can use these images as inputs to implement cool effects like post-processing and camera displays within the 3D world. -[C++ code](/code/texture_mapping.cpp) / -[Vertex shader](/code/shader_textures.vert) / -[Fragment shader](/code/shader_textures.frag) \ No newline at end of file +[C++ code](/code/25_texture_mapping.cpp) / +[Vertex shader](/code/25_shader_textures.vert) / +[Fragment shader](/code/25_shader_textures.frag) diff --git a/07_Depth_buffering.md b/07_Depth_buffering.md index a4051350..d6ef8da3 100644 --- a/07_Depth_buffering.md +++ b/07_Depth_buffering.md @@ -130,9 +130,9 @@ running at once. The depth image will again require the trifecta of resources: image, memory and image view. ```c++ -VDeleter depthImage{device, vkDestroyImage}; -VDeleter depthImageMemory{device, vkFreeMemory}; -VDeleter depthImageView{device, vkDestroyImageView}; +VkImage depthImage; +VkDeviceMemory depthImageMemory; +VkImageView depthImageView; ``` Create a new function `createDepthResources` to set up these resources: @@ -272,7 +272,7 @@ We now have all the required information to invoke our `createImage` and ```c++ createImage(swapChainExtent.width, swapChainExtent.height, depthFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, depthImage, depthImageMemory); -createImageView(depthImage, depthFormat, depthImageView); +depthImageView = createImageView(depthImage, depthFormat); ``` However, the `createImageView` function currently assumes that the subresource @@ -280,7 +280,7 @@ is always the `VK_IMAGE_ASPECT_COLOR_BIT`, so we will need to turn that field into a parameter: ```c++ -void createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags, VDeleter& imageView) { +VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags) { ... viewInfo.subresourceRange.aspectMask = aspectFlags; ... @@ -290,11 +290,11 @@ void createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFl Update all calls to this function to use the right aspect: ```c++ -createImageView(swapChainImages[i], swapChainImageFormat, VK_IMAGE_ASPECT_COLOR_BIT, swapChainImageViews[i]); +swapChainImageViews[i] = createImageView(swapChainImages[i], swapChainImageFormat, VK_IMAGE_ASPECT_COLOR_BIT); ... -createImageView(depthImage, depthFormat, VK_IMAGE_ASPECT_DEPTH_BIT, depthImageView); +depthImageView = createImageView(depthImage, depthFormat, VK_IMAGE_ASPECT_DEPTH_BIT); ... -createImageView(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT, textureImageView); +textureImageView = createImageView(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT); ``` That's it for creating the depth image. We don't need to map it or copy another @@ -327,27 +327,38 @@ if (newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) { Although we're not using the stencil component, we do need to include it in the layout transitions of the depth image. -Finally, add the correct access masks: +Finally, add the correct access masks and pipeline stages: ```c++ -if (oldLayout == VK_IMAGE_LAYOUT_PREINITIALIZED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL) { - barrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT; - barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; -} else if (oldLayout == VK_IMAGE_LAYOUT_PREINITIALIZED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { - barrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT; +if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { + barrier.srcAccessMask = 0; barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + + sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT; } else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + + sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; } else if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) { barrier.srcAccessMask = 0; barrier.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + + sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + destinationStage = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; } else { throw std::invalid_argument("unsupported layout transition!"); } ``` -The image is now completely ready for usage as depth attachment. +The depth buffer will be read from to perform depth tests to see if a fragment +is visible, and will be written to when a new fragment is drawn. The reading +happens in the `VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT` stage and the +writing in the `VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT`. You should pick the +earliest pipeline stage that matches the specified operations, so that it is +ready for usage as depth attachment when it needs to be. ## Render pass @@ -396,7 +407,7 @@ buffers. std::array attachments = {colorAttachment, depthAttachment}; VkRenderPassCreateInfo renderPassInfo = {}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; -renderPassInfo.attachmentCount = attachments.size(); +renderPassInfo.attachmentCount = static_cast(attachments.size()); renderPassInfo.pAttachments = attachments.data(); renderPassInfo.subpassCount = 1; renderPassInfo.pSubpasses = &subpass; @@ -422,7 +433,7 @@ std::array attachments = { VkFramebufferCreateInfo framebufferInfo = {}; framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; framebufferInfo.renderPass = renderPass; -framebufferInfo.attachmentCount = attachments.size(); +framebufferInfo.attachmentCount = static_cast(attachments.size()); framebufferInfo.pAttachments = attachments.data(); framebufferInfo.width = swapChainExtent.width; framebufferInfo.height = swapChainExtent.height; @@ -456,7 +467,7 @@ std::array clearValues = {}; clearValues[0].color = {0.0f, 0.0f, 0.0f, 1.0f}; clearValues[1].depthStencil = {1.0f, 0}; -renderPassInfo.clearValueCount = clearValues.size(); +renderPassInfo.clearValueCount = static_cast(clearValues.size()); renderPassInfo.pClearValues = clearValues.data(); ``` @@ -537,6 +548,8 @@ function to recreate the depth resources in that case: ```c++ void recreateSwapChain() { vkDeviceWaitIdle(device); + + cleanupSwapChain(); createSwapChain(); createImageViews(); @@ -548,10 +561,22 @@ void recreateSwapChain() { } ``` +The cleanup operations should happen in the swap chain cleanup function: + +```c++ +void cleanupSwapChain() { + vkDestroyImageView(device, depthImageView, nullptr); + vkDestroyImage(device, depthImage, nullptr); + vkFreeMemory(device, depthImageMemory, nullptr); + + ... +} +``` + Congratulations, your application is now finally ready to render arbitrary 3D geometry and have it look right. We're going to try this out in the next chapter by drawing a textured model! -[C++ code](/code/depth_buffering.cpp) / -[Vertex shader](/code/shader_depth.vert) / -[Fragment shader](/code/shader_depth.frag) +[C++ code](/code/26_depth_buffering.cpp) / +[Vertex shader](/code/26_shader_depth.vert) / +[Fragment shader](/code/26_shader_depth.frag) diff --git a/08_Loading_models.md b/08_Loading_models.md index 956bc068..282003d4 100644 --- a/08_Loading_models.md +++ b/08_Loading_models.md @@ -88,8 +88,8 @@ non-const containers as class members: ```c++ std::vector vertices; std::vector indices; -VDeleter vertexBuffer{device, vkDestroyBuffer}; -VDeleter vertexBufferMemory{device, vkFreeMemory}; +VkBuffer vertexBuffer; +VkDeviceMemory vertexBufferMemory; ``` You should change the type of the indices from `uint16_t` to `uint32_t`, because @@ -249,14 +249,14 @@ Unfortunately we're not really taking advantage of the index buffer yet. The vertices are included in multiple triangles. We should keep only the unique vertices and use the index buffer to reuse them whenever they come up. A straightforward way to implement this is to use a `map` or `unordered_map` to -keep track of the unique vertices and their index: +keep track of the unique vertices and respective indices: ```c++ #include ... -std::unordered_map uniqueVertices = {}; +std::unordered_map uniqueVertices = {}; for (const auto& shape : shapes) { for (const auto& index : shape.mesh.indices) { @@ -265,7 +265,7 @@ for (const auto& shape : shapes) { ... if (uniqueVertices.count(vertex) == 0) { - uniqueVertices[vertex] = vertices.size(); + uniqueVertices[vertex] = static_cast(vertices.size()); vertices.push_back(vertex); } @@ -303,7 +303,7 @@ namespace std { template<> struct hash { size_t operator()(Vertex const& vertex) const { return ((hash()(vertex.pos) ^ - (hash()(vertex.color) << 1)) >> 1) ^ + (hash()(vertex.color) << 1)) >> 1) ^ (hash()(vertex.texCoord) << 1); } }; @@ -314,34 +314,20 @@ This code should be placed outside the `Vertex` struct. The hash functions for the GLM types need to be included using the following header: ```c++ +#define GLM_ENABLE_EXPERIMENTAL #include ``` +The hash functions are defined in the `gtx` folder, which means that it is +technically still an experimental extension to GLM. Therefore you need to define +`GLM_ENABLE_EXPERIMENTAL` to use it. It means that the API could change with a +new version of GLM in the future, but in practice the API is very stable. + You should now be able to successfully compile and run your program. If you check the size of `vertices`, then you'll see that it has shrunk down from 1,500,000 to 265,645! That means that each vertex is reused in an average number of ~6 triangles. This definitely saves us a lot of GPU memory. -## Conclusion - -It has taken a lot of work to get to this point, but now you finally have a good -base for a Vulkan program. The knowledge of the basic principles of Vulkan that -you now possess should be sufficient to start exploring more of the features, -like: - -* Push constants -* Instanced rendering -* Dynamic uniforms -* Separate images and sampler descriptors -* Pipeline cache -* Multi-threaded command buffer generation -* Multiple subpasses - -The current program can be extended in many ways, like adding Blinn-Phong -lighting, post-processing effects and shadow mapping. You should be able to -learn how these effects work from tutorials for other APIs, because despite -Vulkan's explicitness, many concepts still work the same. - -[C++ code](/code/model_loading.cpp) / -[Vertex shader](/code/shader_depth.vert) / -[Fragment shader](/code/shader_depth.frag) \ No newline at end of file +[C++ code](/code/27_model_loading.cpp) / +[Vertex shader](/code/26_shader_depth.vert) / +[Fragment shader](/code/26_shader_depth.frag) diff --git a/09_Generating_Mipmaps.md b/09_Generating_Mipmaps.md new file mode 100644 index 00000000..3e13fa92 --- /dev/null +++ b/09_Generating_Mipmaps.md @@ -0,0 +1,351 @@ +## Introduction +Our program can now load and render 3D models. In this chapter, we will add one more feature, mipmap generation. Mipmaps are widely used in games and rendering software, and Vulkan gives us complete control over how they are created. + +Mipmaps are precalculated, downscaled versions of an image. Each new image is half the width and height of the previous one. Mipmaps are used as a form of *Level of Detail* or *LOD.* Objects that are far away from the camera will sample their textures from the smaller mip images. Using smaller images increases the rendering speed and avoids artifacts such as [Moiré patterns](https://en.wikipedia.org/wiki/Moir%C3%A9_pattern). An example of what mipmaps look like: + +![](/images/mipmaps_example.jpg) + +## Image creation + +In Vulkan, each of the mip images is stored in different *mip levels* of a `VkImage`. Mip level 0 is the original image, and the mip levels after level 0 are commonly referred to as the *mip chain.* + +The number of mip levels is specified when the `VkImage` is created. Up until now, we have always set this value to one. We need to calculate the number of mip levels from the dimensions of the image. First, add a class member to store this number: + +```c++ +... +uint32_t mipLevels; +VkImage textureImage; +... +``` + +The value for `mipLevels` can be found once we've loaded the texture in `createTextureImage`: + +```c++ +int texWidth, texHeight, texChannels; +stbi_uc* pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); +... +mipLevels = static_cast(std::floor(std::log2(std::max(texWidth, texHeight)))) + 1; + +``` + +This calculates the number of levels in the mip chain. The `max` function selects the largest dimension. The `log2` function calculates how many times that dimension can be divided by 2. The `floor` function handles cases where the largest dimension is not a power of 2. `1` is added so that the original image has a mip level. + +To use this value, we need to change the `createImage`, `createImageView`, and `transitionImageLayout` functions to allow us to specify the number of mip levels. Add a `mipLevels` parameter to the functions: + +```c++ +void createImage(uint32_t width, uint32_t height, uint32_t mipLevels, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage& image, VkDeviceMemory& imageMemory) { + ... + imageInfo.mipLevels = mipLevels; + ... +} +``` +```c++ +VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags, uint32_t mipLevels) { + ... + viewInfo.subresourceRange.levelCount = mipLevels; + ... +``` +```c++ +void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout, uint32_t mipLevels) { + ... + barrier.subresourceRange.levelCount = mipLevels; + ... +``` + +Update all calls to these functions to use the right values: + +```c++ +createImage(swapChainExtent.width, swapChainExtent.height, 1, depthFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, depthImage, depthImageMemory); +... +createImage(texWidth, texHeight, mipLevels, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, textureImage, textureImageMemory); +``` +```c++ +swapChainImageViews[i] = createImageView(swapChainImages[i], swapChainImageFormat, VK_IMAGE_ASPECT_COLOR_BIT, 1); +... +depthImageView = createImageView(depthImage, depthFormat, VK_IMAGE_ASPECT_DEPTH_BIT, 1); +... +textureImageView = createImageView(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT, mipLevels); +``` +```c++ +transitionImageLayout(depthImage, depthFormat, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, 1); +... +transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, mipLevels); +``` + + + +## Generating Mipmaps + +Our texture image now has multiple mip levels, but the staging buffer can only be used to fill mip level 0. The other levels are still undefined. To fill these levels we need to generate the data from the single level that we have. We will use the `vkCmdBlitImage` command. This command performs copying, scaling, and filtering operations. We will call this multiple times to *blit* data to each level of our texture image. + +`VkCmdBlit` is considered a transfer operation, so we must inform Vulkan that we intend to use the texture image as both the source and destination of a transfer. Add `VK_IMAGE_USAGE_TRANSFER_SRC_BIT` to the texture image's usage flags in `createTextureImage`: + +```c++ +... +createImage(texWidth, texHeight, mipLevels, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, textureImage, textureImageMemory); +... +``` + +Like other image operations, `vkCmdBlitImage` depends on the layout of the image it operates on. We could transition the entire image to `VK_IMAGE_LAYOUT_GENERAL`, but this will most likely be slow. For optimal performance, the source image should be in `VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL` and the destination image should be in `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL`. Vulkan allows us to transition each mip level of an image independently. Each blit will only deal with two mip levels at a time, so we can transition each level into the optimal layout between blits commands. + +`transitionImageLayout` only performs layout transitions on the entire image, so we'll need to write a few more pipeline barrier commands. Remove the existing transition to `VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL` in `createTextureImage`: + +```c++ +... +transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, mipLevels); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); +//transitioned to VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL while generating mipmaps +... +``` + +This will leave each level of the texture image in `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL`. Each level will be transitioned to `VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL` after the blit command reading from it is finished. + +We're now going to write the function that generates the mipmaps: + +```c++ +void generateMipmaps(VkImage image, int32_t texWidth, int32_t texHeight, uint32_t mipLevels) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkImageMemoryBarrier barrier = {}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.image = image; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + barrier.subresourceRange.levelCount = 1; + + endSingleTimeCommands(commandBuffer); +} +``` + +We're going to make several transitions, so we'll reuse this `VkImageMemoryBarrier`. The fields set above will remain the same for all barriers. `subresourceRange.miplevel`, `oldLayout`, `newLayout`, `srcAccessMask`, and `dstAccessMask` will be changed for each transition. + +```c++ +int32_t mipWidth = texWidth; +int32_t mipHeight = texHeight; + +for (uint32_t i = 1; i < mipLevels; i++) { + +} +``` + +This loop will record each of the `VkCmdBlitImage` commands. Note that the loop variable starts at 1, not 0. + +```c++ +barrier.subresourceRange.baseMipLevel = i - 1; +barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; +barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; +barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; +barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + +vkCmdPipelineBarrier(commandBuffer, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, + 0, nullptr, + 0, nullptr, + 1, &barrier); +``` + +First, we transition level `i - 1` to `VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL`. This transition will wait for level `i - 1` to be filled, either from the previous blit command, or from `vkCmdCopyBufferToImage`. The current blit command will wait on this transition. + +```c++ +VkImageBlit blit = {}; +blit.srcOffsets[0] = { 0, 0, 0 }; +blit.srcOffsets[1] = { mipWidth, mipHeight, 1 }; +blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; +blit.srcSubresource.mipLevel = i - 1; +blit.srcSubresource.baseArrayLayer = 0; +blit.srcSubresource.layerCount = 1; +blit.dstOffsets[0] = { 0, 0, 0 }; +blit.dstOffsets[1] = { mipWidth > 1 ? mipWidth / 2 : 1, mipHeight > 1 ? mipHeight / 2 : 1, 1 }; +blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; +blit.dstSubresource.mipLevel = i; +blit.dstSubresource.baseArrayLayer = 0; +blit.dstSubresource.layerCount = 1; +``` + +Next, we specify the regions that will be used in the blit operation. The source mip level is `i - 1` and the destination mip level is `i`. The two elements of the `srcOffsets` array determine the 3D region that data will be blitted from. `dstOffsets` determines the region that data will be blitted to. The X and Y dimensions of the `dstOffsets[1]` are divided by two since each mip level is half the size of the previous level. The Z dimension of `srcOffsets[1]` and `dstOffsets[1]` must be 1, since a 2D image has a depth of 1. + +```c++ +vkCmdBlitImage(commandBuffer, + image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, &blit, + VK_FILTER_LINEAR); +``` + +Now, we record the blit command. Note that `textureImage` is used for both the `srcImage` and `dstImage` parameter. This is because we're blitting between different levels of the same image. The source mip level was just transitioned to `VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL` and the destination level is still in `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL` from `createTextureImage`. + +The last parameter allows us to specify a `VkFilter` to use in the blit. We have the same filtering options here that we had when making the `VkSampler`. We use the `VK_FILTER_LINEAR` to enable interpolation. + +```c++ +barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; +barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; +barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; +barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + +vkCmdPipelineBarrier(commandBuffer, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, + 0, nullptr, + 0, nullptr, + 1, &barrier); +``` + +This barrier transitions mip level `i - 1` to `VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL`. This transition waits on the current blit command to finish. All sampling operations will wait on this transition to finish. + +```c++ + ... + if (mipWidth > 1) mipWidth /= 2; + if (mipHeight > 1) mipHeight /= 2; +} +``` + +At the end of the loop, we divide the current mip dimensions by two. We check each dimension before the division to ensure that dimension never becomes 0. This handles cases where the image is not square, since one of the mip dimensions would reach 1 before the other dimension. When this happens, that dimension should remain 1 for all remaining levels. + +```c++ + barrier.subresourceRange.baseMipLevel = mipLevels - 1; + barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + + vkCmdPipelineBarrier(commandBuffer, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, + 0, nullptr, + 0, nullptr, + 1, &barrier); + + endSingleTimeCommands(commandBuffer); +} +``` + +Before we end the command buffer, we insert one more pipeline barrier. This barrier transitions the last mip level from `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL` to `VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL`. This wasn't handled by the loop, since the last mip level is never blitted from. + +Finally, add the call to `generateMipmaps` in `createTextureImage`: + +```c++ +transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, mipLevels); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); +//transitioned to VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL while generating mipmaps +... +generateMipmaps(textureImage, texWidth, texHeight, mipLevels); +``` + +Our texture image's mipmaps are now completely filled. + +## Linear filtering support + +It is very convenient to use a built-in function like `vkCmdBlitImage` to generate all the mip levels, but unfortunately it is not guaranteed to be supported on all platforms. It requires the texture image format we use to support linear filtering, which can be checked with the `vkGetPhysicalDeviceFormatProperties` function. We will add a check to the `generateMipmaps` function for this. + +First add an additional parameter that specifies the image format: + +```c++ +void createTextureImage() { + ... + + generateMipmaps(textureImage, VK_FORMAT_R8G8B8A8_UNORM, texWidth, texHeight, mipLevels); +} + +void generateMipmaps(VkImage image, VkFormat imageFormat, int32_t texWidth, int32_t texHeight, uint32_t mipLevels) { + + ... +} +``` + +In the `generateMipmaps` function, use `vkGetPhysicalDeviceFormatProperties` to request the properties of the texture image format: + +```c++ +void generateMipmaps(VkImage image, VkFormat imageFormat, int32_t texWidth, int32_t texHeight, uint32_t mipLevels) { + + // Check if image format supports linear blitting + VkFormatProperties formatProperties; + vkGetPhysicalDeviceFormatProperties(physicalDevice, imageFormat, &formatProperties); + + ... +``` + +The `VkFormatProperties` struct has three fields named `linearTilingFeatures`, `optimalTilingFeatures` and `bufferFeatures` that each describe how the format can be used depending on the way it is used. We create a texture image with the optimal tiling format, so we need to check `optimalTilingFeatures`. Support for the linear filtering feature can be checked with the `VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT`: + +```c++ +if (!(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT)) { + throw std::runtime_error("texture image format does not support linear blitting!"); +} +``` + +There are two alternatives in this case. You could implement a function that searches common texture image formats for one that *does* support linear blitting, or you could implement the mipmap generation in software with a library like [stb_image_resize](https://github.com/nothings/stb/blob/master/stb_image_resize.h). Each mip level can then be loaded into the image in the same way that you loaded the original image. + +It should be noted that it is uncommon in practice to generate the mipmap levels at runtime anyway. Usually they are pregenerated and stored in the texture file alongside the base level to improve loading speed. Implementing resizing in software and loading multiple levels from a file is left as an exercise to the reader. + +## Sampler + +While the `VkImage` holds the mipmap data, `VkSampler` controls how that data is read while rendering. Vulkan allows us to specify `minLod`, `maxLod`, `mipLodBias`, and `mipmapMode` ("Lod" means "Level of Detail"). When a texture is sampled, the sampler selects a mip level according to the following pseudocode: + +```c++ +lod = getLodLevelFromScreenSize(); //smaller when the object is close, may be negative +lod = clamp(lod + mipLodBias, minLod, maxLod); + +level = clamp(floor(lod), 0, texture.mipLevels - 1); //clamped to the number of mip levels in the texture + +if (mipmapMode == VK_SAMPLER_MIPMAP_MODE_NEAREST) { + color = sample(level); +} else { + color = blend(sample(level), sample(level + 1)); +} +``` + +If `samplerInfo.mipmapMode` is `VK_SAMPLER_MIPMAP_MODE_NEAREST`, `lod` selects the mip level to sample from. If the mipmap mode is `VK_SAMPLER_MIPMAP_MODE_LINEAR`, `lod` is used to select two mip levels to be sampled. Those levels are sampled and the results are linearly blended. + +The sample operation is also affected by `lod`: + +```c++ +if (lod <= 0) { + color = readTexture(uv, magFilter); +} else { + color = readTexture(uv, minFilter); +} +``` + +If the object is close to the camera, `magFilter` is used as the filter. If the object is further from the camera, `minFilter` is used. Normally, `lod` is non-negative, and is only 0 when close the camera. `mipLodBias` lets us force Vulkan to use lower `lod` and `level` than it would normally use. + +To see the results of this chapter, we need to choose values for our `textureSampler`. We've already set the `minFilter` and `magFilter` to use `VK_FILTER_LINEAR`. We just need to choose values for `minLod`, `maxLod`, `mipLodBias`, and `mipmapMode`. + +```c++ +void createTextureSampler() { + ... + samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + samplerInfo.minLod = 0; // Optional + samplerInfo.maxLod = static_cast(mipLevels); + samplerInfo.mipLodBias = 0; // Optional + ... +} +``` + +To allow the full range of mip levels to be used, we set `minLod` to 0, and `maxLod` to the number of mip levels. We have no reason to change the `lod` value , so we set `mipLodBias` to 0. + +Now run your program and you should see the following: + +![](/images/mipmaps.png) + +It's not a dramatic difference, since our scene is so simple. There are subtle differences if you look closely. + +![](/images/mipmaps_comparison.png) + +The most noticeable difference is the writing on the signs. With mipmaps, the writing has been smoothed. Without mipmaps, the writing has harsh edges and gaps from Moiré artifacts. + +You can play around with the sampler settings to see how they affect mipmapping. For example, by changing `minLod`, you can force the sampler to not use the lowest mip levels: + +```c++ +samplerInfo.minLod = static_cast(mipLevels / 2); +``` + +These settings will produce this image: + + +![](/images/highmipmaps.png) + +This is how higher mip levels will be used when objects are further away from the camera. + +[C++ code](/code/28_mipmapping.cpp) / +[Vertex shader](/code/26_shader_depth.vert) / +[Fragment shader](/code/26_shader_depth.frag) diff --git a/10_Multisampling.md b/10_Multisampling.md new file mode 100644 index 00000000..2209341e --- /dev/null +++ b/10_Multisampling.md @@ -0,0 +1,305 @@ +## Introduction + +Our program can now load multiple levels of detail for textures which fixes artifacts when rendering objects far away from the viewer. The image is now a lot smoother, however on closer inspection you will notice jagged saw-like patterns along the edges of drawn geometric shapes. This is especially visible in one of our early programs when we rendered a quad: + +![](/images/texcoord_visualization.png) + +This undesired effect is called "aliasing" and it's a result of a limited numbers of pixels that are available for rendering. Since there are no displays out there with unlimited resolution, it will be always visible to some extent. There's a number of ways to fix this and in this chapter we'll focus on one of the more popular ones: [Multisample anti-aliasing](https://en.wikipedia.org/wiki/Multisample_anti-aliasing) (MSAA). + +In ordinary rendering, the pixel color is determined based on a single sample point which in most cases is the center of target pixel on screen. If part of the drawn line passes through a certain pixel but doesn't cover the sample point, that pixel will be left blank, leading to the jagged "staircase" effect. + +![](/images/aliasing.png) + +What MSAA does is it uses multiple sample points per pixel (hence the name) to determine its final color. As one might expect, more samples lead to better results, however it is also more computationally expensive. + +![](/images/antialiasing.png) + +In our implementation, we will focus on using the maximum available sample count. Depending on your application this may not always be the best approach and it might be better to use less samples for the sake of higher performance if the final result meets your quality demands. + + +## Getting available sample count + +Let's start off by determining how many samples our hardware can use. Most modern GPUs support at least 8 samples but this number is not guaranteed to be the same everywhere. We'll keep track of it by adding a new class member: + +```c++ +... +VkSampleCountFlagBits msaaSamples = VK_SAMPLE_COUNT_1_BIT; +... +``` + +By default we'll be using only one sample per pixel which is equivalent to no multisampling, in which case the final image will remain unchanged. The exact maximum number of samples can be extracted from `VkPhysicalDeviceProperties` associated with our selected physical device. We're using a depth buffer, so we have to take into account the sample count for both color and depth - the lower number will be the maximum we can support. Add a function that will fetch this information for us: + +```c++ +VkSampleCountFlagBits getMaxUsableSampleCount() { + VkPhysicalDeviceProperties physicalDeviceProperties; + vkGetPhysicalDeviceProperties(physicalDevice, &physicalDeviceProperties); + + VkSampleCountFlags counts = std::min(physicalDeviceProperties.limits.framebufferColorSampleCounts, physicalDeviceProperties.limits.framebufferDepthSampleCounts); + if (counts & VK_SAMPLE_COUNT_64_BIT) { return VK_SAMPLE_COUNT_64_BIT; } + if (counts & VK_SAMPLE_COUNT_32_BIT) { return VK_SAMPLE_COUNT_32_BIT; } + if (counts & VK_SAMPLE_COUNT_16_BIT) { return VK_SAMPLE_COUNT_16_BIT; } + if (counts & VK_SAMPLE_COUNT_8_BIT) { return VK_SAMPLE_COUNT_8_BIT; } + if (counts & VK_SAMPLE_COUNT_4_BIT) { return VK_SAMPLE_COUNT_4_BIT; } + if (counts & VK_SAMPLE_COUNT_2_BIT) { return VK_SAMPLE_COUNT_2_BIT; } + + return VK_SAMPLE_COUNT_1_BIT; +} +``` + +We will now use this function to set the `msaaSamples` variable during the physical device selection process. For this, we have to slightly modify the `pickPhysicalDevice` function: + +```c++ +void pickPhysicalDevice() { + ... + if (isDeviceSuitable(device)) { + physicalDevice = device; + msaaSamples = getMaxUsableSampleCount(); + break; + } + ... +} +``` + +## Setting up render targets + +In MSAA, each pixel is sampled in an offscreen buffer which is then rendered to the screen. These new buffers are slightly different from regular images we've been rendering to - they have to be able to store more than one sample per pixel. Once a multisampled buffer is created, it has to be attached to the default framebuffer (which stores only a single sample per pixel). This is why we have to create additional render targets and modify our current drawing process. Add the following class members: + +```c++ +... +std::vector colorImages; +std::vector colorImagesMemory; +std::vector colorImagesView; +... +``` + +These new images will have to store the desired number of samples per pixel, so we need to pass this number to `VkImageCreateInfo` during image creation process. Modify the `createImage` function by adding a `numSamples` parameter: + +```c++ +void createImage(uint32_t width, uint32_t height, uint32_t mipLevels, VkSampleCountFlagBits numSamples, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage& image, VkDeviceMemory& imageMemory) { + ... + imageInfo.samples = numSamples; + ... +``` + +For now, update all calls to this function using `VK_SAMPLE_COUNT_1_BIT` - we will be replacing this with proper values as we progress with implementation: + +```c++ +createImage(swapChainExtent.width, swapChainExtent.height, 1, VK_SAMPLE_COUNT_1_BIT, depthFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, depthImages[i], depthImagesMemory[i]); +... +createImage(texWidth, texHeight, mipLevels, VK_SAMPLE_COUNT_1_BIT, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, textureImage, textureImageMemory); +``` + +We will now create a multisampled color buffer. Same as in case of non-multisampled image, we'll be dealing with dedicated resources for each swapchain image. Add a `createColorResources` function and note that we're using `msaaSamples` here as a function parameter to `createImage`. We're also using only one mip level, since this is enforced by the Vulkan specification in case of images with more than one sample per pixel. Also, this color buffer doesn't need mipmaps since it's not going to be used as a texture: + +```c++ +void createColorResources() { + VkFormat colorFormat = swapChainImageFormat; + + colorImages.resize(swapChainImages.size()); + colorImagesMemory.resize(swapChainImages.size()); + colorImageViews.resize(swapChainImages.size()); + + for (size_t i = 0; i < swapChainImageViews.size(); i++) { + createImage(swapChainExtent.width, swapChainExtent.height, 1, msaaSamples, colorFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, colorImages[i], colorImagesMemory[i]); + colorImageViews[i] = createImageView(colorImages[i], colorFormat, VK_IMAGE_ASPECT_COLOR_BIT, 1); + + transitionImageLayout(colorImages[i], colorFormat, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, 1); + } +} +``` + +For consistency, call the function right before `createDepthResources`: + +```c++ +void initVulkan() { + ... + createColorResources(); + createDepthResources(); + ... +} +``` + +You may notice that the newly created color image uses a transition path from `VK_IMAGE_LAYOUT_UNDEFINED` to `VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL` which is a new case for us to handle. Let's update `transitionImageLayout` function to take this into account: + +```c++ +void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout, uint32_t mipLevels) { + ... + else if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) { + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + destinationStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + } + else { + throw std::invalid_argument("unsupported layout transition!"); + } + ... +} +``` + +Now that we have a multisampled color buffer in place it's time to take care of depth. Modify `createDepthResources` and update the number of samples used by the depth buffer: + +```c++ +void createDepthResources() { + ... + createImage(swapChainExtent.width, swapChainExtent.height, 1, msaaSamples, depthFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, depthImages[i], depthImagesMemory[i]); + ... +} +``` + +We have now created a couple of new Vulkan resources, so let's not forget to release them when necessary: + +```c++ +void cleanupSwapChain() { + for (size_t i = 0; i < swapChainImages.size(); i++) { + vkDestroyImageView(device, colorImageViews[i], nullptr); + vkDestroyImage(device, colorImages[i], nullptr); + vkFreeMemory(device, colorImagesMemory[i], nullptr); + ... + } + ... +} +``` + +We made it past the initial MSAA setup, now we need to start using these new resources in our graphics pipeline, framebuffer, render pass and see the results! + +## Adding new attachments + +Let's take care of the render pass first. Modify `createRenderPass` and update color and depth attachment creation info structs: + +```c++ +void createRenderPass() { + ... + colorAttachment.samples = msaaSamples; + colorAttachment.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + ... + depthAttachment.samples = msaaSamples; + ... +``` + +You'll notice that we have changed the finalLayout from `VK_IMAGE_LAYOUT_PRESENT_SRC_KHR` to `VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL`. That's because multisampled images cannot be presented directly. We first need to resolve them to a regular image. This requirement does not apply to the depth buffer, since it won't be presented at any point. Therefore we will have to add only one new attachment for color which is a so-called resolve attachment: + +```c++ + ... + VkAttachmentDescription colorAttachmentResolve = {}; + colorAttachmentResolve.format = swapChainImageFormat; + colorAttachmentResolve.samples = VK_SAMPLE_COUNT_1_BIT; + colorAttachmentResolve.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachmentResolve.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachmentResolve.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachmentResolve.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachmentResolve.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + colorAttachmentResolve.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + ... +``` + +The render pass now has to be instructed to resolve multisampled color image into regular attachment. Create a new attachment reference that will point to the color buffer which will serve as the resolve target: + +```c++ + ... + VkAttachmentReference colorAttachmentResolveRef = {}; + colorAttachmentResolveRef.attachment = 2; + colorAttachmentResolveRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + ... +``` + +Set the `pResolveAttachments` subpass struct member to point to the newly created attachment reference. This is enough to let the render pass define a multisample resolve operation which will let us render the image to screen: + +``` + ... + subpass.pResolveAttachments = &colorAttachmentResolveRef; + ... +``` + +Now update render pass info struct with new color attachment: + +```c++ + ... + std::array attachments = {colorAttachment, depthAttachment, colorAttachmentResolve }; + ... +``` + +With render pass in place, modify `createFrameBuffers` and add new image view to the list: + +```c++ +void createFrameBuffers() { + ... + std::array attachments = { + colorImageViews[i], + depthImagesView[i], + swapChainImageViews[i] + }; + ... +} +``` + +Finally, tell the newly created pipeline to use more than one sample by modifying `createGraphicsPipeline`: + +```c++ +void createGraphicsPipeline() { + ... + multisampling.rasterizationSamples = msaaSamples; + ... +} +``` + +Now run your program and you should see the following: + +![](/images/multisampling.png) + +Just like with mipmapping, the difference may not be apparent straight away. On a closer look you'll notice that the edges on the roof are not as jagged anymore and the whole image seems a bit smoother compared to the original. + +![](/images/multisampling_comparison.png) + +The difference is more noticable when looking up close at one of the edges: + +![](/images/multisampling_comparison2.png) + +## Quality improvements + +There are certain limitations of our current MSAA implementation which may impact the quality of the output image in more detailed scenes. For example, we're currently not solving potential problems caused by shader aliasing, i.e. MSAA only smoothens out the edges of geometry but not the interior filling. This may lead to a situation when you get a smooth polygon rendered on screen but the applied texture will still look aliased if it contains high contrasting colors. One way to approach this problem is to enable [Sample Shading](https://www.khronos.org/registry/vulkan/specs/1.0/html/vkspec.html#primsrast-sampleshading) which will improve the image quality even further, though at an additional performance cost: + +```c++ + +void createLogicalDevice() { + ... + deviceFeatures.sampleRateShading = VK_TRUE; // enable sample shading feature for the device + ... +} + +void createGraphicsPipeline() { + ... + multisampling.sampleShadingEnable = VK_TRUE; // enable sample shading in the pipeline + multisampling.minSampleShading = .2f; // min fraction for sample shading; closer to one is smoother + ... +} +``` + +In this example we'll leave sample shading disabled but in certain scenarios the quality improvement may be noticeable: + +![](/images/sample_shading.png) + +## Conclusion + +It has taken a lot of work to get to this point, but now you finally have a good +base for a Vulkan program. The knowledge of the basic principles of Vulkan that +you now possess should be sufficient to start exploring more of the features, +like: + +* Push constants +* Instanced rendering +* Dynamic uniforms +* Separate images and sampler descriptors +* Pipeline cache +* Multi-threaded command buffer generation +* Multiple subpasses +* Compute shaders + +The current program can be extended in many ways, like adding Blinn-Phong +lighting, post-processing effects and shadow mapping. You should be able to +learn how these effects work from tutorials for other APIs, because despite +Vulkan's explicitness, many concepts still work the same. + +[C++ code](/code/29_multisampling.cpp) / +[Vertex shader](/code/26_shader_depth.vert) / +[Fragment shader](/code/26_shader_depth.frag) diff --git a/99_FAQ.md b/99_FAQ.md new file mode 100644 index 00000000..397e8525 --- /dev/null +++ b/99_FAQ.md @@ -0,0 +1,18 @@ +This page lists solutions to common problems that you may encounter while +developing Vulkan applications. + +* **I get an access violation error in the core validation layer**: Make sure +that MSI Afterburner / RivaTuner Statistics Server is not running, because it +has some compatibility problems with Vulkan. + +* **I don't see any messages from the validation layers**: First make sure that +the validation layers get a chance to print errors by keeping the terminal open +after your program exits. You can do this from Visual Studio by running your +program with Ctrl-F5 instead of F5, and on Linux by executing your program from +a terminal window. If there are still no messages and you are sure that +validation layers are turned on, then you should ensure that your Vulkan SDK is +correctly installed by following [these instructions](https://vulkan.lunarg.com/doc/sdk/1.0.61.0/windows/getting_started.html). + +* **vkCreateSwapchainKHR triggers an error in SteamOverlayVulkanLayer64.dll**: +This appears to be a compatibility problem in the Steam client beta. Make sure +to opt out of the Steam beta program for now. diff --git a/README.md b/README.md index 7613a09f..5dc1da13 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Vulkan tutorial =============== This repository hosts the contents of [vulkan-tutorial.com](https://vulkan-tutorial.com). -The website itself is based on [daux.io](https://github.com/justinwalsh/daux.io), +The website itself is based on [daux.io](https://github.com/dauxio/daux.io), which supports [GitHub flavored Markdown](https://help.github.com/articles/basic-writing-and-formatting-syntax/). A few changes were made to daux.io and its themes, which are included in `daux.patch` and are licensed as [MIT](https://opensource.org/licenses/MIT). The @@ -13,12 +13,31 @@ have a problem with your code, then use the comments section in the related chapter to ask a question. Please provide your operating system, graphics card, driver version, source code, expected behaviour and actual behaviour. +E-book +------ + +This guide is now available in e-book formats as well: + +* [EPUB](https://raw.githubusercontent.com/Overv/VulkanTutorial/master/ebook/Vulkan%20Tutorial.epub) +* [PDF](https://raw.githubusercontent.com/Overv/VulkanTutorial/master/ebook/Vulkan%20Tutorial.pdf) + +The e-book can be built from the existing content by running: + + python3 build_ebook.py + +This script depends on the following utilities being available on the path: + +* `inkscape`: SVG to PNG conversion +* `pandoc`: Building a PDF and EPUB from the Markdown code + +You also need to install a LaTeX distribution for PDF generation. + Changing code across chapters ----------------------------- It is sometimes necessary to change code that is reused across many chapters, -for example the `VDeleter` class or a function like `createBuffer`. If you make -such a change, then you should update the code files using the following steps: +for example a function like `createBuffer`. If you make such a change, then you +should update the code files using the following steps: * Update any chapters that reference the modified code. * Make a copy of the first file that uses it and modify the code there, e.g. diff --git a/build_ebook.py b/build_ebook.py new file mode 100644 index 00000000..97b270de --- /dev/null +++ b/build_ebook.py @@ -0,0 +1,96 @@ +import subprocess +import datetime +import os +import re + +# Recursively gather all markdown files in the right order +markdownFiles = [] + +for root, subdirs, files in os.walk('.'): + for fn in files: + if 'md' in fn and 'ebook.md' not in fn: + path = os.path.join(root, fn) + + # "02_Development_environment.md" -> "Development environment" + title = fn.split('.')[0] # "02_Development_environment.md" -> "02_Development_environment" + title = title.replace('_', ' ') # "02_Development_environment" -> "02 Development environment" + title = ' '.join(title.split(' ')[1:]) # "02 Development environment" -> "Development environment" + + with open(path, 'r') as f: + markdownFiles.append({ + 'title': title, + 'filename': os.path.join(root, fn), + 'contents': f.read() + }) + +markdownFiles.sort(key=lambda entry: entry['filename']) + +# Create concatenated document +print('processing markdown...') + +allMarkdown = '' + +for entry in markdownFiles: + contents = entry['contents'] + + # Add title + contents = '# ' + entry['title'] + '\n\n' + contents + + # Fix image links + contents = re.sub(r'\/images\/', 'images/', contents) + contents = re.sub(r'\.svg', '.png', contents) + + # Fix remaining relative links (e.g. code files) + contents = re.sub(r'\]\(\/', '](https://vulkan-tutorial.com/', contents) + + # Fix chapter references + def repl(m): + target = m.group(1) + target = target.lower() + target = re.sub('_', '-', target) + target = target.split('/')[-1] + + return '](#' + target + ')' + + contents = re.sub(r'\]\(!([^)]+)\)', repl, contents) + + allMarkdown += contents + '\n\n' + +# Add title +dateNow = datetime.datetime.now() + +metadata = '% Vulkan Tutorial\n' +metadata += '% Alexander Overvoorde\n' +metadata += '% ' + dateNow.strftime('%B %Y') + '\n\n' + +allMarkdown = metadata + allMarkdown + +with open('ebook.md', 'w') as f: + f.write(allMarkdown) + +# Convert all SVG images to PNG for pandoc +print('converting svgs...') + +generatedPngs = [] + +for fn in os.listdir('images'): + parts = fn.split('.') + + if parts[1] == 'svg': + subprocess.check_output(['inkscape', '-z', '-e', 'images/' + parts[0] + '.png', 'images/' + fn], stderr=subprocess.STDOUT) + generatedPngs.append('images/' + parts[0] + '.png') + +# Building PDF +print('building pdf...') + +subprocess.check_output(['pandoc', 'ebook.md', '-V', 'documentclass=report', '-t', 'latex', '-s', '--toc', '--listings', '-H', 'ebook/listings-setup.tex', '-o', 'ebook/Vulkan Tutorial.pdf']) + +print('building epub...') + +subprocess.check_output(['pandoc', 'ebook.md', '--toc', '-o', 'ebook/Vulkan Tutorial.epub']) + +# Clean up +os.remove('ebook.md') + +for fn in generatedPngs: + os.remove(fn) diff --git a/code/00_base_code.cpp b/code/00_base_code.cpp new file mode 100644 index 00000000..279e4223 --- /dev/null +++ b/code/00_base_code.cpp @@ -0,0 +1,60 @@ +#define GLFW_INCLUDE_VULKAN +#include + +#include +#include +#include + +const int WIDTH = 800; +const int HEIGHT = 600; + +class HelloTriangleApplication { +public: + void run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + } + + void initVulkan() { + + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + } + } + + void cleanup() { + glfwDestroyWindow(window); + + glfwTerminate(); + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/code/instance_creation.cpp b/code/01_instance_creation.cpp similarity index 55% rename from code/instance_creation.cpp rename to code/01_instance_creation.cpp index 20c4a082..5583097f 100644 --- a/code/instance_creation.cpp +++ b/code/01_instance_creation.cpp @@ -3,81 +3,24 @@ #include #include -#include +#include const int WIDTH = 800; const int HEIGHT = 600; -template -class VDeleter { -public: - VDeleter() : VDeleter([](T, VkAllocationCallbacks*) {}) {} - - VDeleter(std::function deletef) { - this->deleter = [=](T obj) { deletef(obj, nullptr); }; - } - - VDeleter(const VDeleter& instance, std::function deletef) { - this->deleter = [&instance, deletef](T obj) { deletef(instance, obj, nullptr); }; - } - - VDeleter(const VDeleter& device, std::function deletef) { - this->deleter = [&device, deletef](T obj) { deletef(device, obj, nullptr); }; - } - - ~VDeleter() { - cleanup(); - } - - const T* operator &() const { - return &object; - } - - T* replace() { - cleanup(); - return &object; - } - - operator T() const { - return object; - } - - void operator=(T rhs) { - if (rhs != object) { - cleanup(); - object = rhs; - } - } - - template - bool operator==(V rhs) { - return object == T(rhs); - } - -private: - T object{VK_NULL_HANDLE}; - std::function deleter; - - void cleanup() { - if (object != VK_NULL_HANDLE) { - deleter(object); - } - object = VK_NULL_HANDLE; - } -}; - class HelloTriangleApplication { public: void run() { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; + VkInstance instance; void initWindow() { glfwInit(); @@ -96,6 +39,10 @@ class HelloTriangleApplication { while (!glfwWindowShouldClose(window)) { glfwPollEvents(); } + } + + void cleanup() { + vkDestroyInstance(instance, nullptr); glfwDestroyWindow(window); @@ -115,7 +62,7 @@ class HelloTriangleApplication { createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; createInfo.pApplicationInfo = &appInfo; - unsigned int glfwExtensionCount = 0; + uint32_t glfwExtensionCount = 0; const char** glfwExtensions; glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); @@ -124,7 +71,7 @@ class HelloTriangleApplication { createInfo.enabledLayerCount = 0; - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } @@ -135,10 +82,10 @@ int main() { try { app.run(); - } catch (const std::runtime_error& e) { + } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; -} \ No newline at end of file +} diff --git a/code/validation_layers.cpp b/code/02_validation_layers.cpp similarity index 72% rename from code/validation_layers.cpp rename to code/02_validation_layers.cpp index 0dd637ec..e70569bc 100644 --- a/code/validation_layers.cpp +++ b/code/02_validation_layers.cpp @@ -3,9 +3,9 @@ #include #include -#include #include #include +#include const int WIDTH = 800; const int HEIGHT = 600; @@ -36,77 +36,20 @@ void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT } } -template -class VDeleter { -public: - VDeleter() : VDeleter([](T, VkAllocationCallbacks*) {}) {} - - VDeleter(std::function deletef) { - this->deleter = [=](T obj) { deletef(obj, nullptr); }; - } - - VDeleter(const VDeleter& instance, std::function deletef) { - this->deleter = [&instance, deletef](T obj) { deletef(instance, obj, nullptr); }; - } - - VDeleter(const VDeleter& device, std::function deletef) { - this->deleter = [&device, deletef](T obj) { deletef(device, obj, nullptr); }; - } - - ~VDeleter() { - cleanup(); - } - - const T* operator &() const { - return &object; - } - - T* replace() { - cleanup(); - return &object; - } - - operator T() const { - return object; - } - - void operator=(T rhs) { - if (rhs != object) { - cleanup(); - object = rhs; - } - } - - template - bool operator==(V rhs) { - return object == T(rhs); - } - -private: - T object{VK_NULL_HANDLE}; - std::function deleter; - - void cleanup() { - if (object != VK_NULL_HANDLE) { - deleter(object); - } - object = VK_NULL_HANDLE; - } -}; - class HelloTriangleApplication { public: void run() { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; + VkInstance instance; + VkDebugReportCallbackEXT callback; void initWindow() { glfwInit(); @@ -126,6 +69,14 @@ class HelloTriangleApplication { while (!glfwWindowShouldClose(window)) { glfwPollEvents(); } + } + + void cleanup() { + if (enableValidationLayers) { + DestroyDebugReportCallbackEXT(instance, callback, nullptr); + } + + vkDestroyInstance(instance, nullptr); glfwDestroyWindow(window); @@ -150,17 +101,17 @@ class HelloTriangleApplication { createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } @@ -173,21 +124,17 @@ class HelloTriangleApplication { createInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT; createInfo.pfnCallback = debugCallback; - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { + if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, &callback) != VK_SUCCESS) { throw std::runtime_error("failed to set up debug callback!"); } } std::vector getRequiredExtensions() { - std::vector extensions; - - unsigned int glfwExtensionCount = 0; + uint32_t glfwExtensionCount = 0; const char** glfwExtensions; glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - for (unsigned int i = 0; i < glfwExtensionCount; i++) { - extensions.push_back(glfwExtensions[i]); - } + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); if (enableValidationLayers) { extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); @@ -233,10 +180,10 @@ int main() { try { app.run(); - } catch (const std::runtime_error& e) { + } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; -} \ No newline at end of file +} diff --git a/code/physical_device_selection.cpp b/code/03_physical_device_selection.cpp similarity index 77% rename from code/physical_device_selection.cpp rename to code/03_physical_device_selection.cpp index 5c1d657b..d29c5693 100644 --- a/code/physical_device_selection.cpp +++ b/code/03_physical_device_selection.cpp @@ -3,9 +3,9 @@ #include #include -#include #include #include +#include const int WIDTH = 800; const int HEIGHT = 600; @@ -36,64 +36,6 @@ void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT } } -template -class VDeleter { -public: - VDeleter() : VDeleter([](T, VkAllocationCallbacks*) {}) {} - - VDeleter(std::function deletef) { - this->deleter = [=](T obj) { deletef(obj, nullptr); }; - } - - VDeleter(const VDeleter& instance, std::function deletef) { - this->deleter = [&instance, deletef](T obj) { deletef(instance, obj, nullptr); }; - } - - VDeleter(const VDeleter& device, std::function deletef) { - this->deleter = [&device, deletef](T obj) { deletef(device, obj, nullptr); }; - } - - ~VDeleter() { - cleanup(); - } - - const T* operator &() const { - return &object; - } - - T* replace() { - cleanup(); - return &object; - } - - operator T() const { - return object; - } - - void operator=(T rhs) { - if (rhs != object) { - cleanup(); - object = rhs; - } - } - - template - bool operator==(V rhs) { - return object == T(rhs); - } - -private: - T object{VK_NULL_HANDLE}; - std::function deleter; - - void cleanup() { - if (object != VK_NULL_HANDLE) { - deleter(object); - } - object = VK_NULL_HANDLE; - } -}; - struct QueueFamilyIndices { int graphicsFamily = -1; @@ -108,14 +50,14 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugReportCallbackEXT callback; VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; @@ -138,6 +80,14 @@ class HelloTriangleApplication { while (!glfwWindowShouldClose(window)) { glfwPollEvents(); } + } + + void cleanup() { + if (enableValidationLayers) { + DestroyDebugReportCallbackEXT(instance, callback, nullptr); + } + + vkDestroyInstance(instance, nullptr); glfwDestroyWindow(window); @@ -162,17 +112,17 @@ class HelloTriangleApplication { createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } @@ -185,7 +135,7 @@ class HelloTriangleApplication { createInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT; createInfo.pfnCallback = debugCallback; - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { + if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, &callback) != VK_SUCCESS) { throw std::runtime_error("failed to set up debug callback!"); } } @@ -245,15 +195,11 @@ class HelloTriangleApplication { } std::vector getRequiredExtensions() { - std::vector extensions; - - unsigned int glfwExtensionCount = 0; + uint32_t glfwExtensionCount = 0; const char** glfwExtensions; glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - for (unsigned int i = 0; i < glfwExtensionCount; i++) { - extensions.push_back(glfwExtensions[i]); - } + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); if (enableValidationLayers) { extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); @@ -299,10 +245,10 @@ int main() { try { app.run(); - } catch (const std::runtime_error& e) { + } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; -} \ No newline at end of file +} diff --git a/code/logical_device.cpp b/code/04_logical_device.cpp similarity index 78% rename from code/logical_device.cpp rename to code/04_logical_device.cpp index 47f5e896..c0065c3a 100644 --- a/code/logical_device.cpp +++ b/code/04_logical_device.cpp @@ -3,9 +3,9 @@ #include #include -#include #include #include +#include const int WIDTH = 800; const int HEIGHT = 600; @@ -36,64 +36,6 @@ void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT } } -template -class VDeleter { -public: - VDeleter() : VDeleter([](T, VkAllocationCallbacks*) {}) {} - - VDeleter(std::function deletef) { - this->deleter = [=](T obj) { deletef(obj, nullptr); }; - } - - VDeleter(const VDeleter& instance, std::function deletef) { - this->deleter = [&instance, deletef](T obj) { deletef(instance, obj, nullptr); }; - } - - VDeleter(const VDeleter& device, std::function deletef) { - this->deleter = [&device, deletef](T obj) { deletef(device, obj, nullptr); }; - } - - ~VDeleter() { - cleanup(); - } - - const T* operator &() const { - return &object; - } - - T* replace() { - cleanup(); - return &object; - } - - operator T() const { - return object; - } - - void operator=(T rhs) { - if (rhs != object) { - cleanup(); - object = rhs; - } - } - - template - bool operator==(V rhs) { - return object == T(rhs); - } - -private: - T object{VK_NULL_HANDLE}; - std::function deleter; - - void cleanup() { - if (object != VK_NULL_HANDLE) { - deleter(object); - } - object = VK_NULL_HANDLE; - } -}; - struct QueueFamilyIndices { int graphicsFamily = -1; @@ -108,17 +50,17 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugReportCallbackEXT callback; VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; - VDeleter device{vkDestroyDevice}; + VkDevice device; VkQueue graphicsQueue; @@ -142,6 +84,16 @@ class HelloTriangleApplication { while (!glfwWindowShouldClose(window)) { glfwPollEvents(); } + } + + void cleanup() { + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugReportCallbackEXT(instance, callback, nullptr); + } + + vkDestroyInstance(instance, nullptr); glfwDestroyWindow(window); @@ -166,17 +118,17 @@ class HelloTriangleApplication { createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } @@ -189,7 +141,7 @@ class HelloTriangleApplication { createInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT; createInfo.pfnCallback = debugCallback; - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { + if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, &callback) != VK_SUCCESS) { throw std::runtime_error("failed to set up debug callback!"); } } @@ -239,15 +191,15 @@ class HelloTriangleApplication { createInfo.pEnabledFeatures = &deviceFeatures; createInfo.enabledExtensionCount = 0; - + if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateDevice(physicalDevice, &createInfo, nullptr, device.replace()) != VK_SUCCESS) { + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { throw std::runtime_error("failed to create logical device!"); } @@ -286,15 +238,11 @@ class HelloTriangleApplication { } std::vector getRequiredExtensions() { - std::vector extensions; - - unsigned int glfwExtensionCount = 0; + uint32_t glfwExtensionCount = 0; const char** glfwExtensions; glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - for (unsigned int i = 0; i < glfwExtensionCount; i++) { - extensions.push_back(glfwExtensions[i]); - } + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); if (enableValidationLayers) { extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); @@ -340,10 +288,10 @@ int main() { try { app.run(); - } catch (const std::runtime_error& e) { + } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; -} \ No newline at end of file +} diff --git a/code/window_surface.cpp b/code/05_window_surface.cpp similarity index 79% rename from code/window_surface.cpp rename to code/05_window_surface.cpp index de4a36d4..dd72100a 100644 --- a/code/window_surface.cpp +++ b/code/05_window_surface.cpp @@ -3,9 +3,9 @@ #include #include -#include #include #include +#include #include const int WIDTH = 800; @@ -37,64 +37,6 @@ void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT } } -template -class VDeleter { -public: - VDeleter() : VDeleter([](T, VkAllocationCallbacks*) {}) {} - - VDeleter(std::function deletef) { - this->deleter = [=](T obj) { deletef(obj, nullptr); }; - } - - VDeleter(const VDeleter& instance, std::function deletef) { - this->deleter = [&instance, deletef](T obj) { deletef(instance, obj, nullptr); }; - } - - VDeleter(const VDeleter& device, std::function deletef) { - this->deleter = [&device, deletef](T obj) { deletef(device, obj, nullptr); }; - } - - ~VDeleter() { - cleanup(); - } - - const T* operator &() const { - return &object; - } - - T* replace() { - cleanup(); - return &object; - } - - operator T() const { - return object; - } - - void operator=(T rhs) { - if (rhs != object) { - cleanup(); - object = rhs; - } - } - - template - bool operator==(V rhs) { - return object == T(rhs); - } - -private: - T object{VK_NULL_HANDLE}; - std::function deleter; - - void cleanup() { - if (object != VK_NULL_HANDLE) { - deleter(object); - } - object = VK_NULL_HANDLE; - } -}; - struct QueueFamilyIndices { int graphicsFamily = -1; int presentFamily = -1; @@ -110,17 +52,18 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugReportCallbackEXT callback; + VkSurfaceKHR surface; VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; - VDeleter device{vkDestroyDevice}; + VkDevice device; VkQueue graphicsQueue; VkQueue presentQueue; @@ -146,6 +89,17 @@ class HelloTriangleApplication { while (!glfwWindowShouldClose(window)) { glfwPollEvents(); } + } + + void cleanup() { + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugReportCallbackEXT(instance, callback, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); glfwDestroyWindow(window); @@ -170,17 +124,17 @@ class HelloTriangleApplication { createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } @@ -193,13 +147,13 @@ class HelloTriangleApplication { createInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT; createInfo.pfnCallback = debugCallback; - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { + if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, &callback) != VK_SUCCESS) { throw std::runtime_error("failed to set up debug callback!"); } } void createSurface() { - if (glfwCreateWindowSurface(instance, window, nullptr, surface.replace()) != VK_SUCCESS) { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { throw std::runtime_error("failed to create window surface!"); } } @@ -248,21 +202,21 @@ class HelloTriangleApplication { VkDeviceCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); createInfo.pQueueCreateInfos = queueCreateInfos.data(); - createInfo.queueCreateInfoCount = (uint32_t) queueCreateInfos.size(); createInfo.pEnabledFeatures = &deviceFeatures; createInfo.enabledExtensionCount = 0; - + if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateDevice(physicalDevice, &createInfo, nullptr, device.replace()) != VK_SUCCESS) { + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { throw std::runtime_error("failed to create logical device!"); } @@ -309,15 +263,11 @@ class HelloTriangleApplication { } std::vector getRequiredExtensions() { - std::vector extensions; - - unsigned int glfwExtensionCount = 0; + uint32_t glfwExtensionCount = 0; const char** glfwExtensions; glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - for (unsigned int i = 0; i < glfwExtensionCount; i++) { - extensions.push_back(glfwExtensions[i]); - } + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); if (enableValidationLayers) { extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); @@ -363,7 +313,7 @@ int main() { try { app.run(); - } catch (const std::runtime_error& e) { + } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; } diff --git a/code/swap_chain_creation.cpp b/code/06_swap_chain_creation.cpp similarity index 85% rename from code/swap_chain_creation.cpp rename to code/06_swap_chain_creation.cpp index 954e6584..1248e265 100644 --- a/code/swap_chain_creation.cpp +++ b/code/06_swap_chain_creation.cpp @@ -3,10 +3,10 @@ #include #include -#include #include #include #include +#include #include const int WIDTH = 800; @@ -42,64 +42,6 @@ void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT } } -template -class VDeleter { -public: - VDeleter() : VDeleter([](T, VkAllocationCallbacks*) {}) {} - - VDeleter(std::function deletef) { - this->deleter = [=](T obj) { deletef(obj, nullptr); }; - } - - VDeleter(const VDeleter& instance, std::function deletef) { - this->deleter = [&instance, deletef](T obj) { deletef(instance, obj, nullptr); }; - } - - VDeleter(const VDeleter& device, std::function deletef) { - this->deleter = [&device, deletef](T obj) { deletef(device, obj, nullptr); }; - } - - ~VDeleter() { - cleanup(); - } - - const T* operator &() const { - return &object; - } - - T* replace() { - cleanup(); - return &object; - } - - operator T() const { - return object; - } - - void operator=(T rhs) { - if (rhs != object) { - cleanup(); - object = rhs; - } - } - - template - bool operator==(V rhs) { - return object == T(rhs); - } - -private: - T object{VK_NULL_HANDLE}; - std::function deleter; - - void cleanup() { - if (object != VK_NULL_HANDLE) { - deleter(object); - } - object = VK_NULL_HANDLE; - } -}; - struct QueueFamilyIndices { int graphicsFamily = -1; int presentFamily = -1; @@ -121,22 +63,23 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugReportCallbackEXT callback; + VkSurfaceKHR surface; VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; - VDeleter device{vkDestroyDevice}; + VkDevice device; VkQueue graphicsQueue; VkQueue presentQueue; - - VDeleter swapChain{device, vkDestroySwapchainKHR}; + + VkSwapchainKHR swapChain; std::vector swapChainImages; VkFormat swapChainImageFormat; VkExtent2D swapChainExtent; @@ -163,6 +106,18 @@ class HelloTriangleApplication { while (!glfwWindowShouldClose(window)) { glfwPollEvents(); } + } + + void cleanup() { + vkDestroySwapchainKHR(device, swapChain, nullptr); + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugReportCallbackEXT(instance, callback, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); glfwDestroyWindow(window); @@ -187,17 +142,17 @@ class HelloTriangleApplication { createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } @@ -210,13 +165,13 @@ class HelloTriangleApplication { createInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT; createInfo.pfnCallback = debugCallback; - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { + if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, &callback) != VK_SUCCESS) { throw std::runtime_error("failed to set up debug callback!"); } } void createSurface() { - if (glfwCreateWindowSurface(instance, window, nullptr, surface.replace()) != VK_SUCCESS) { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { throw std::runtime_error("failed to create window surface!"); } } @@ -265,22 +220,22 @@ class HelloTriangleApplication { VkDeviceCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); createInfo.pQueueCreateInfos = queueCreateInfos.data(); - createInfo.queueCreateInfoCount = (uint32_t) queueCreateInfos.size(); createInfo.pEnabledFeatures = &deviceFeatures; - createInfo.enabledExtensionCount = deviceExtensions.size(); + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); createInfo.ppEnabledExtensionNames = deviceExtensions.data(); if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateDevice(physicalDevice, &createInfo, nullptr, device.replace()) != VK_SUCCESS) { + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { throw std::runtime_error("failed to create logical device!"); } @@ -329,10 +284,10 @@ class HelloTriangleApplication { createInfo.oldSwapchain = VK_NULL_HANDLE; - if (vkCreateSwapchainKHR(device, &createInfo, nullptr, swapChain.replace()) != VK_SUCCESS) { + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { throw std::runtime_error("failed to create swap chain!"); } - + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); swapChainImages.resize(imageCount); vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); @@ -377,7 +332,7 @@ class HelloTriangleApplication { actualExtent.width = std::max(capabilities.minImageExtent.width, std::min(capabilities.maxImageExtent.width, actualExtent.width)); actualExtent.height = std::max(capabilities.minImageExtent.height, std::min(capabilities.maxImageExtent.height, actualExtent.height)); - + return actualExtent; } } @@ -469,15 +424,11 @@ class HelloTriangleApplication { } std::vector getRequiredExtensions() { - std::vector extensions; - - unsigned int glfwExtensionCount = 0; + uint32_t glfwExtensionCount = 0; const char** glfwExtensions; glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - for (unsigned int i = 0; i < glfwExtensionCount; i++) { - extensions.push_back(glfwExtensions[i]); - } + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); if (enableValidationLayers) { extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); @@ -523,10 +474,10 @@ int main() { try { app.run(); - } catch (const std::runtime_error& e) { + } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; -} \ No newline at end of file +} diff --git a/code/graphics_pipeline.cpp b/code/07_image_views.cpp similarity index 84% rename from code/graphics_pipeline.cpp rename to code/07_image_views.cpp index 34b1d80c..9f4cca20 100644 --- a/code/graphics_pipeline.cpp +++ b/code/07_image_views.cpp @@ -3,11 +3,10 @@ #include #include -#include -#include #include #include #include +#include #include const int WIDTH = 800; @@ -43,64 +42,6 @@ void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT } } -template -class VDeleter { -public: - VDeleter() : VDeleter([](T, VkAllocationCallbacks*) {}) {} - - VDeleter(std::function deletef) { - this->deleter = [=](T obj) { deletef(obj, nullptr); }; - } - - VDeleter(const VDeleter& instance, std::function deletef) { - this->deleter = [&instance, deletef](T obj) { deletef(instance, obj, nullptr); }; - } - - VDeleter(const VDeleter& device, std::function deletef) { - this->deleter = [&device, deletef](T obj) { deletef(device, obj, nullptr); }; - } - - ~VDeleter() { - cleanup(); - } - - const T* operator &() const { - return &object; - } - - T* replace() { - cleanup(); - return &object; - } - - operator T() const { - return object; - } - - void operator=(T rhs) { - if (rhs != object) { - cleanup(); - object = rhs; - } - } - - template - bool operator==(V rhs) { - return object == T(rhs); - } - -private: - T object{VK_NULL_HANDLE}; - std::function deleter; - - void cleanup() { - if (object != VK_NULL_HANDLE) { - deleter(object); - } - object = VK_NULL_HANDLE; - } -}; - struct QueueFamilyIndices { int graphicsFamily = -1; int presentFamily = -1; @@ -122,26 +63,27 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugReportCallbackEXT callback; + VkSurfaceKHR surface; VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; - VDeleter device{vkDestroyDevice}; + VkDevice device; VkQueue graphicsQueue; VkQueue presentQueue; - - VDeleter swapChain{device, vkDestroySwapchainKHR}; + + VkSwapchainKHR swapChain; std::vector swapChainImages; VkFormat swapChainImageFormat; VkExtent2D swapChainExtent; - std::vector> swapChainImageViews; + std::vector swapChainImageViews; void initWindow() { glfwInit(); @@ -160,13 +102,28 @@ class HelloTriangleApplication { createLogicalDevice(); createSwapChain(); createImageViews(); - createGraphicsPipeline(); } void mainLoop() { while (!glfwWindowShouldClose(window)) { glfwPollEvents(); } + } + + void cleanup() { + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugReportCallbackEXT(instance, callback, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); glfwDestroyWindow(window); @@ -191,17 +148,17 @@ class HelloTriangleApplication { createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } @@ -214,13 +171,13 @@ class HelloTriangleApplication { createInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT; createInfo.pfnCallback = debugCallback; - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { + if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, &callback) != VK_SUCCESS) { throw std::runtime_error("failed to set up debug callback!"); } } void createSurface() { - if (glfwCreateWindowSurface(instance, window, nullptr, surface.replace()) != VK_SUCCESS) { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { throw std::runtime_error("failed to create window surface!"); } } @@ -269,22 +226,22 @@ class HelloTriangleApplication { VkDeviceCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); createInfo.pQueueCreateInfos = queueCreateInfos.data(); - createInfo.queueCreateInfoCount = (uint32_t) queueCreateInfos.size(); createInfo.pEnabledFeatures = &deviceFeatures; - createInfo.enabledExtensionCount = deviceExtensions.size(); + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); createInfo.ppEnabledExtensionNames = deviceExtensions.data(); if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateDevice(physicalDevice, &createInfo, nullptr, device.replace()) != VK_SUCCESS) { + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { throw std::runtime_error("failed to create logical device!"); } @@ -333,10 +290,10 @@ class HelloTriangleApplication { createInfo.oldSwapchain = VK_NULL_HANDLE; - if (vkCreateSwapchainKHR(device, &createInfo, nullptr, swapChain.replace()) != VK_SUCCESS) { + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { throw std::runtime_error("failed to create swap chain!"); } - + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); swapChainImages.resize(imageCount); vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); @@ -346,9 +303,9 @@ class HelloTriangleApplication { } void createImageViews() { - swapChainImageViews.resize(swapChainImages.size(), VDeleter{device, vkDestroyImageView}); + swapChainImageViews.resize(swapChainImages.size()); - for (uint32_t i = 0; i < swapChainImages.size(); i++) { + for (size_t i = 0; i < swapChainImages.size(); i++) { VkImageViewCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; createInfo.image = swapChainImages[i]; @@ -364,16 +321,12 @@ class HelloTriangleApplication { createInfo.subresourceRange.baseArrayLayer = 0; createInfo.subresourceRange.layerCount = 1; - if (vkCreateImageView(device, &createInfo, nullptr, swapChainImageViews[i].replace()) != VK_SUCCESS) { - throw std::runtime_error("failed to create image views"); + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create image views!"); } } } - void createGraphicsPipeline() { - - } - VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { if (availableFormats.size() == 1 && availableFormats[0].format == VK_FORMAT_UNDEFINED) { return {VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}; @@ -410,7 +363,7 @@ class HelloTriangleApplication { actualExtent.width = std::max(capabilities.minImageExtent.width, std::min(capabilities.maxImageExtent.width, actualExtent.width)); actualExtent.height = std::max(capabilities.minImageExtent.height, std::min(capabilities.maxImageExtent.height, actualExtent.height)); - + return actualExtent; } } @@ -502,15 +455,11 @@ class HelloTriangleApplication { } std::vector getRequiredExtensions() { - std::vector extensions; - - unsigned int glfwExtensionCount = 0; + uint32_t glfwExtensionCount = 0; const char** glfwExtensions; glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - for (unsigned int i = 0; i < glfwExtensionCount; i++) { - extensions.push_back(glfwExtensions[i]); - } + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); if (enableValidationLayers) { extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); @@ -556,10 +505,10 @@ int main() { try { app.run(); - } catch (const std::runtime_error& e) { + } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; -} \ No newline at end of file +} diff --git a/code/image_views.cpp b/code/08_graphics_pipeline.cpp similarity index 85% rename from code/image_views.cpp rename to code/08_graphics_pipeline.cpp index 7bd4a6ef..f9bd6898 100644 --- a/code/image_views.cpp +++ b/code/08_graphics_pipeline.cpp @@ -3,10 +3,10 @@ #include #include -#include #include #include #include +#include #include const int WIDTH = 800; @@ -42,64 +42,6 @@ void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT } } -template -class VDeleter { -public: - VDeleter() : VDeleter([](T, VkAllocationCallbacks*) {}) {} - - VDeleter(std::function deletef) { - this->deleter = [=](T obj) { deletef(obj, nullptr); }; - } - - VDeleter(const VDeleter& instance, std::function deletef) { - this->deleter = [&instance, deletef](T obj) { deletef(instance, obj, nullptr); }; - } - - VDeleter(const VDeleter& device, std::function deletef) { - this->deleter = [&device, deletef](T obj) { deletef(device, obj, nullptr); }; - } - - ~VDeleter() { - cleanup(); - } - - const T* operator &() const { - return &object; - } - - T* replace() { - cleanup(); - return &object; - } - - operator T() const { - return object; - } - - void operator=(T rhs) { - if (rhs != object) { - cleanup(); - object = rhs; - } - } - - template - bool operator==(V rhs) { - return object == T(rhs); - } - -private: - T object{VK_NULL_HANDLE}; - std::function deleter; - - void cleanup() { - if (object != VK_NULL_HANDLE) { - deleter(object); - } - object = VK_NULL_HANDLE; - } -}; - struct QueueFamilyIndices { int graphicsFamily = -1; int presentFamily = -1; @@ -121,26 +63,27 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugReportCallbackEXT callback; + VkSurfaceKHR surface; VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; - VDeleter device{vkDestroyDevice}; + VkDevice device; VkQueue graphicsQueue; VkQueue presentQueue; - - VDeleter swapChain{device, vkDestroySwapchainKHR}; + + VkSwapchainKHR swapChain; std::vector swapChainImages; VkFormat swapChainImageFormat; VkExtent2D swapChainExtent; - std::vector> swapChainImageViews; + std::vector swapChainImageViews; void initWindow() { glfwInit(); @@ -159,12 +102,29 @@ class HelloTriangleApplication { createLogicalDevice(); createSwapChain(); createImageViews(); + createGraphicsPipeline(); } void mainLoop() { while (!glfwWindowShouldClose(window)) { glfwPollEvents(); } + } + + void cleanup() { + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugReportCallbackEXT(instance, callback, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); glfwDestroyWindow(window); @@ -189,17 +149,17 @@ class HelloTriangleApplication { createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } @@ -212,13 +172,13 @@ class HelloTriangleApplication { createInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT; createInfo.pfnCallback = debugCallback; - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { + if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, &callback) != VK_SUCCESS) { throw std::runtime_error("failed to set up debug callback!"); } } void createSurface() { - if (glfwCreateWindowSurface(instance, window, nullptr, surface.replace()) != VK_SUCCESS) { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { throw std::runtime_error("failed to create window surface!"); } } @@ -267,22 +227,22 @@ class HelloTriangleApplication { VkDeviceCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); createInfo.pQueueCreateInfos = queueCreateInfos.data(); - createInfo.queueCreateInfoCount = (uint32_t) queueCreateInfos.size(); createInfo.pEnabledFeatures = &deviceFeatures; - createInfo.enabledExtensionCount = deviceExtensions.size(); + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); createInfo.ppEnabledExtensionNames = deviceExtensions.data(); if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateDevice(physicalDevice, &createInfo, nullptr, device.replace()) != VK_SUCCESS) { + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { throw std::runtime_error("failed to create logical device!"); } @@ -331,10 +291,10 @@ class HelloTriangleApplication { createInfo.oldSwapchain = VK_NULL_HANDLE; - if (vkCreateSwapchainKHR(device, &createInfo, nullptr, swapChain.replace()) != VK_SUCCESS) { + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { throw std::runtime_error("failed to create swap chain!"); } - + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); swapChainImages.resize(imageCount); vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); @@ -344,9 +304,9 @@ class HelloTriangleApplication { } void createImageViews() { - swapChainImageViews.resize(swapChainImages.size(), VDeleter{device, vkDestroyImageView}); + swapChainImageViews.resize(swapChainImages.size()); - for (uint32_t i = 0; i < swapChainImages.size(); i++) { + for (size_t i = 0; i < swapChainImages.size(); i++) { VkImageViewCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; createInfo.image = swapChainImages[i]; @@ -362,12 +322,16 @@ class HelloTriangleApplication { createInfo.subresourceRange.baseArrayLayer = 0; createInfo.subresourceRange.layerCount = 1; - if (vkCreateImageView(device, &createInfo, nullptr, swapChainImageViews[i].replace()) != VK_SUCCESS) { + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { throw std::runtime_error("failed to create image views!"); } } } + void createGraphicsPipeline() { + + } + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { if (availableFormats.size() == 1 && availableFormats[0].format == VK_FORMAT_UNDEFINED) { return {VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}; @@ -404,7 +368,7 @@ class HelloTriangleApplication { actualExtent.width = std::max(capabilities.minImageExtent.width, std::min(capabilities.maxImageExtent.width, actualExtent.width)); actualExtent.height = std::max(capabilities.minImageExtent.height, std::min(capabilities.maxImageExtent.height, actualExtent.height)); - + return actualExtent; } } @@ -496,15 +460,11 @@ class HelloTriangleApplication { } std::vector getRequiredExtensions() { - std::vector extensions; - - unsigned int glfwExtensionCount = 0; + uint32_t glfwExtensionCount = 0; const char** glfwExtensions; glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - for (unsigned int i = 0; i < glfwExtensionCount; i++) { - extensions.push_back(glfwExtensions[i]); - } + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); if (enableValidationLayers) { extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); @@ -550,10 +510,10 @@ int main() { try { app.run(); - } catch (const std::runtime_error& e) { + } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; -} \ No newline at end of file +} diff --git a/code/shader_base.frag b/code/09_shader_base.frag similarity index 100% rename from code/shader_base.frag rename to code/09_shader_base.frag diff --git a/code/shader_base.vert b/code/09_shader_base.vert similarity index 100% rename from code/shader_base.vert rename to code/09_shader_base.vert diff --git a/code/shader_modules.cpp b/code/09_shader_modules.cpp similarity index 83% rename from code/shader_modules.cpp rename to code/09_shader_modules.cpp index 982f1905..a396279e 100644 --- a/code/shader_modules.cpp +++ b/code/09_shader_modules.cpp @@ -2,12 +2,12 @@ #include #include -#include -#include #include +#include #include #include #include +#include #include const int WIDTH = 800; @@ -43,64 +43,6 @@ void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT } } -template -class VDeleter { -public: - VDeleter() : VDeleter([](T, VkAllocationCallbacks*) {}) {} - - VDeleter(std::function deletef) { - this->deleter = [=](T obj) { deletef(obj, nullptr); }; - } - - VDeleter(const VDeleter& instance, std::function deletef) { - this->deleter = [&instance, deletef](T obj) { deletef(instance, obj, nullptr); }; - } - - VDeleter(const VDeleter& device, std::function deletef) { - this->deleter = [&device, deletef](T obj) { deletef(device, obj, nullptr); }; - } - - ~VDeleter() { - cleanup(); - } - - const T* operator &() const { - return &object; - } - - T* replace() { - cleanup(); - return &object; - } - - operator T() const { - return object; - } - - void operator=(T rhs) { - if (rhs != object) { - cleanup(); - object = rhs; - } - } - - template - bool operator==(V rhs) { - return object == T(rhs); - } - -private: - T object{VK_NULL_HANDLE}; - std::function deleter; - - void cleanup() { - if (object != VK_NULL_HANDLE) { - deleter(object); - } - object = VK_NULL_HANDLE; - } -}; - struct QueueFamilyIndices { int graphicsFamily = -1; int presentFamily = -1; @@ -122,26 +64,27 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugReportCallbackEXT callback; + VkSurfaceKHR surface; VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; - VDeleter device{vkDestroyDevice}; + VkDevice device; VkQueue graphicsQueue; VkQueue presentQueue; - - VDeleter swapChain{device, vkDestroySwapchainKHR}; + + VkSwapchainKHR swapChain; std::vector swapChainImages; VkFormat swapChainImageFormat; VkExtent2D swapChainExtent; - std::vector> swapChainImageViews; + std::vector swapChainImageViews; void initWindow() { glfwInit(); @@ -167,6 +110,22 @@ class HelloTriangleApplication { while (!glfwWindowShouldClose(window)) { glfwPollEvents(); } + } + + void cleanup() { + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugReportCallbackEXT(instance, callback, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); glfwDestroyWindow(window); @@ -191,17 +150,17 @@ class HelloTriangleApplication { createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } @@ -214,13 +173,13 @@ class HelloTriangleApplication { createInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT; createInfo.pfnCallback = debugCallback; - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { + if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, &callback) != VK_SUCCESS) { throw std::runtime_error("failed to set up debug callback!"); } } void createSurface() { - if (glfwCreateWindowSurface(instance, window, nullptr, surface.replace()) != VK_SUCCESS) { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { throw std::runtime_error("failed to create window surface!"); } } @@ -269,22 +228,22 @@ class HelloTriangleApplication { VkDeviceCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); createInfo.pQueueCreateInfos = queueCreateInfos.data(); - createInfo.queueCreateInfoCount = (uint32_t) queueCreateInfos.size(); createInfo.pEnabledFeatures = &deviceFeatures; - createInfo.enabledExtensionCount = deviceExtensions.size(); + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); createInfo.ppEnabledExtensionNames = deviceExtensions.data(); if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateDevice(physicalDevice, &createInfo, nullptr, device.replace()) != VK_SUCCESS) { + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { throw std::runtime_error("failed to create logical device!"); } @@ -333,10 +292,10 @@ class HelloTriangleApplication { createInfo.oldSwapchain = VK_NULL_HANDLE; - if (vkCreateSwapchainKHR(device, &createInfo, nullptr, swapChain.replace()) != VK_SUCCESS) { + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { throw std::runtime_error("failed to create swap chain!"); } - + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); swapChainImages.resize(imageCount); vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); @@ -346,9 +305,9 @@ class HelloTriangleApplication { } void createImageViews() { - swapChainImageViews.resize(swapChainImages.size(), VDeleter{device, vkDestroyImageView}); + swapChainImageViews.resize(swapChainImages.size()); - for (uint32_t i = 0; i < swapChainImages.size(); i++) { + for (size_t i = 0; i < swapChainImages.size(); i++) { VkImageViewCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; createInfo.image = swapChainImages[i]; @@ -364,7 +323,7 @@ class HelloTriangleApplication { createInfo.subresourceRange.baseArrayLayer = 0; createInfo.subresourceRange.layerCount = 1; - if (vkCreateImageView(device, &createInfo, nullptr, swapChainImageViews[i].replace()) != VK_SUCCESS) { + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { throw std::runtime_error("failed to create image views!"); } } @@ -374,10 +333,8 @@ class HelloTriangleApplication { auto vertShaderCode = readFile("shaders/vert.spv"); auto fragShaderCode = readFile("shaders/frag.spv"); - VDeleter vertShaderModule{device, vkDestroyShaderModule}; - VDeleter fragShaderModule{device, vkDestroyShaderModule}; - createShaderModule(vertShaderCode, vertShaderModule); - createShaderModule(fragShaderCode, fragShaderModule); + VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); VkPipelineShaderStageCreateInfo vertShaderStageInfo = {}; vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; @@ -392,21 +349,23 @@ class HelloTriangleApplication { fragShaderStageInfo.pName = "main"; VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; + + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); } - void createShaderModule(const std::vector& code, VDeleter& shaderModule) { + VkShaderModule createShaderModule(const std::vector& code) { VkShaderModuleCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); - std::vector codeAligned(code.size() / 4 + 1); - memcpy(codeAligned.data(), code.data(), code.size()); - - createInfo.pCode = codeAligned.data(); - - if (vkCreateShaderModule(device, &createInfo, nullptr, shaderModule.replace()) != VK_SUCCESS) { + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { throw std::runtime_error("failed to create shader module!"); } + + return shaderModule; } VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { @@ -445,7 +404,7 @@ class HelloTriangleApplication { actualExtent.width = std::max(capabilities.minImageExtent.width, std::min(capabilities.maxImageExtent.width, actualExtent.width)); actualExtent.height = std::max(capabilities.minImageExtent.height, std::min(capabilities.maxImageExtent.height, actualExtent.height)); - + return actualExtent; } } @@ -537,15 +496,11 @@ class HelloTriangleApplication { } std::vector getRequiredExtensions() { - std::vector extensions; - - unsigned int glfwExtensionCount = 0; + uint32_t glfwExtensionCount = 0; const char** glfwExtensions; glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - for (unsigned int i = 0; i < glfwExtensionCount; i++) { - extensions.push_back(glfwExtensions[i]); - } + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); if (enableValidationLayers) { extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); @@ -609,10 +564,10 @@ int main() { try { app.run(); - } catch (const std::runtime_error& e) { + } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; -} \ No newline at end of file +} diff --git a/code/fixed_functions.cpp b/code/10_fixed_functions.cpp similarity index 84% rename from code/fixed_functions.cpp rename to code/10_fixed_functions.cpp index 364c39b5..f14a4b4b 100644 --- a/code/fixed_functions.cpp +++ b/code/10_fixed_functions.cpp @@ -2,12 +2,12 @@ #include #include -#include -#include #include +#include #include #include #include +#include #include const int WIDTH = 800; @@ -43,64 +43,6 @@ void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT } } -template -class VDeleter { -public: - VDeleter() : VDeleter([](T, VkAllocationCallbacks*) {}) {} - - VDeleter(std::function deletef) { - this->deleter = [=](T obj) { deletef(obj, nullptr); }; - } - - VDeleter(const VDeleter& instance, std::function deletef) { - this->deleter = [&instance, deletef](T obj) { deletef(instance, obj, nullptr); }; - } - - VDeleter(const VDeleter& device, std::function deletef) { - this->deleter = [&device, deletef](T obj) { deletef(device, obj, nullptr); }; - } - - ~VDeleter() { - cleanup(); - } - - const T* operator &() const { - return &object; - } - - T* replace() { - cleanup(); - return &object; - } - - operator T() const { - return object; - } - - void operator=(T rhs) { - if (rhs != object) { - cleanup(); - object = rhs; - } - } - - template - bool operator==(V rhs) { - return object == T(rhs); - } - -private: - T object{VK_NULL_HANDLE}; - std::function deleter; - - void cleanup() { - if (object != VK_NULL_HANDLE) { - deleter(object); - } - object = VK_NULL_HANDLE; - } -}; - struct QueueFamilyIndices { int graphicsFamily = -1; int presentFamily = -1; @@ -122,30 +64,29 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugReportCallbackEXT callback; + VkSurfaceKHR surface; VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; - VDeleter device{vkDestroyDevice}; + VkDevice device; VkQueue graphicsQueue; VkQueue presentQueue; - - VDeleter swapChain{device, vkDestroySwapchainKHR}; + + VkSwapchainKHR swapChain; std::vector swapChainImages; VkFormat swapChainImageFormat; VkExtent2D swapChainExtent; - std::vector> swapChainImageViews; - std::vector> swapChainFramebuffers; - - VDeleter renderPass{device, vkDestroyRenderPass}; - VDeleter pipelineLayout{device, vkDestroyPipelineLayout}; + std::vector swapChainImageViews; + + VkPipelineLayout pipelineLayout; void initWindow() { glfwInit(); @@ -171,6 +112,24 @@ class HelloTriangleApplication { while (!glfwWindowShouldClose(window)) { glfwPollEvents(); } + } + + void cleanup() { + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugReportCallbackEXT(instance, callback, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); glfwDestroyWindow(window); @@ -195,17 +154,17 @@ class HelloTriangleApplication { createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } @@ -218,13 +177,13 @@ class HelloTriangleApplication { createInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT; createInfo.pfnCallback = debugCallback; - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { + if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, &callback) != VK_SUCCESS) { throw std::runtime_error("failed to set up debug callback!"); } } void createSurface() { - if (glfwCreateWindowSurface(instance, window, nullptr, surface.replace()) != VK_SUCCESS) { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { throw std::runtime_error("failed to create window surface!"); } } @@ -273,22 +232,22 @@ class HelloTriangleApplication { VkDeviceCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); createInfo.pQueueCreateInfos = queueCreateInfos.data(); - createInfo.queueCreateInfoCount = (uint32_t) queueCreateInfos.size(); createInfo.pEnabledFeatures = &deviceFeatures; - createInfo.enabledExtensionCount = deviceExtensions.size(); + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); createInfo.ppEnabledExtensionNames = deviceExtensions.data(); if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateDevice(physicalDevice, &createInfo, nullptr, device.replace()) != VK_SUCCESS) { + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { throw std::runtime_error("failed to create logical device!"); } @@ -337,10 +296,10 @@ class HelloTriangleApplication { createInfo.oldSwapchain = VK_NULL_HANDLE; - if (vkCreateSwapchainKHR(device, &createInfo, nullptr, swapChain.replace()) != VK_SUCCESS) { + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { throw std::runtime_error("failed to create swap chain!"); } - + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); swapChainImages.resize(imageCount); vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); @@ -350,9 +309,9 @@ class HelloTriangleApplication { } void createImageViews() { - swapChainImageViews.resize(swapChainImages.size(), VDeleter{device, vkDestroyImageView}); + swapChainImageViews.resize(swapChainImages.size()); - for (uint32_t i = 0; i < swapChainImages.size(); i++) { + for (size_t i = 0; i < swapChainImages.size(); i++) { VkImageViewCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; createInfo.image = swapChainImages[i]; @@ -368,7 +327,7 @@ class HelloTriangleApplication { createInfo.subresourceRange.baseArrayLayer = 0; createInfo.subresourceRange.layerCount = 1; - if (vkCreateImageView(device, &createInfo, nullptr, swapChainImageViews[i].replace()) != VK_SUCCESS) { + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { throw std::runtime_error("failed to create image views!"); } } @@ -378,10 +337,8 @@ class HelloTriangleApplication { auto vertShaderCode = readFile("shaders/vert.spv"); auto fragShaderCode = readFile("shaders/frag.spv"); - VDeleter vertShaderModule{device, vkDestroyShaderModule}; - VDeleter fragShaderModule{device, vkDestroyShaderModule}; - createShaderModule(vertShaderCode, vertShaderModule); - createShaderModule(fragShaderCode, fragShaderModule); + VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); VkPipelineShaderStageCreateInfo vertShaderStageInfo = {}; vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; @@ -460,25 +417,27 @@ class HelloTriangleApplication { pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; pipelineLayoutInfo.setLayoutCount = 0; pipelineLayoutInfo.pushConstantRangeCount = 0; - - if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, pipelineLayout.replace()) != VK_SUCCESS) { + + if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { throw std::runtime_error("failed to create pipeline layout!"); } + + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); } - void createShaderModule(const std::vector& code, VDeleter& shaderModule) { + VkShaderModule createShaderModule(const std::vector& code) { VkShaderModuleCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); - std::vector codeAligned(code.size() / 4 + 1); - memcpy(codeAligned.data(), code.data(), code.size()); - - createInfo.pCode = codeAligned.data(); - - if (vkCreateShaderModule(device, &createInfo, nullptr, shaderModule.replace()) != VK_SUCCESS) { + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { throw std::runtime_error("failed to create shader module!"); } + + return shaderModule; } VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { @@ -517,7 +476,7 @@ class HelloTriangleApplication { actualExtent.width = std::max(capabilities.minImageExtent.width, std::min(capabilities.maxImageExtent.width, actualExtent.width)); actualExtent.height = std::max(capabilities.minImageExtent.height, std::min(capabilities.maxImageExtent.height, actualExtent.height)); - + return actualExtent; } } @@ -609,15 +568,11 @@ class HelloTriangleApplication { } std::vector getRequiredExtensions() { - std::vector extensions; - - unsigned int glfwExtensionCount = 0; + uint32_t glfwExtensionCount = 0; const char** glfwExtensions; glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - for (unsigned int i = 0; i < glfwExtensionCount; i++) { - extensions.push_back(glfwExtensions[i]); - } + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); if (enableValidationLayers) { extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); @@ -681,10 +636,10 @@ int main() { try { app.run(); - } catch (const std::runtime_error& e) { + } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; -} \ No newline at end of file +} diff --git a/code/framebuffers.cpp b/code/11_render_passes.cpp similarity index 79% rename from code/framebuffers.cpp rename to code/11_render_passes.cpp index 598ccc92..684c19a4 100644 --- a/code/framebuffers.cpp +++ b/code/11_render_passes.cpp @@ -2,12 +2,12 @@ #include #include -#include -#include #include +#include #include #include #include +#include #include const int WIDTH = 800; @@ -43,64 +43,6 @@ void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT } } -template -class VDeleter { -public: - VDeleter() : VDeleter([](T, VkAllocationCallbacks*) {}) {} - - VDeleter(std::function deletef) { - this->deleter = [=](T obj) { deletef(obj, nullptr); }; - } - - VDeleter(const VDeleter& instance, std::function deletef) { - this->deleter = [&instance, deletef](T obj) { deletef(instance, obj, nullptr); }; - } - - VDeleter(const VDeleter& device, std::function deletef) { - this->deleter = [&device, deletef](T obj) { deletef(device, obj, nullptr); }; - } - - ~VDeleter() { - cleanup(); - } - - const T* operator &() const { - return &object; - } - - T* replace() { - cleanup(); - return &object; - } - - operator T() const { - return object; - } - - void operator=(T rhs) { - if (rhs != object) { - cleanup(); - object = rhs; - } - } - - template - bool operator==(V rhs) { - return object == T(rhs); - } - -private: - T object{VK_NULL_HANDLE}; - std::function deleter; - - void cleanup() { - if (object != VK_NULL_HANDLE) { - deleter(object); - } - object = VK_NULL_HANDLE; - } -}; - struct QueueFamilyIndices { int graphicsFamily = -1; int presentFamily = -1; @@ -122,31 +64,30 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugReportCallbackEXT callback; + VkSurfaceKHR surface; VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; - VDeleter device{vkDestroyDevice}; + VkDevice device; VkQueue graphicsQueue; VkQueue presentQueue; - - VDeleter swapChain{device, vkDestroySwapchainKHR}; + + VkSwapchainKHR swapChain; std::vector swapChainImages; VkFormat swapChainImageFormat; VkExtent2D swapChainExtent; - std::vector> swapChainImageViews; - std::vector> swapChainFramebuffers; - - VDeleter renderPass{device, vkDestroyRenderPass}; - VDeleter pipelineLayout{device, vkDestroyPipelineLayout}; - VDeleter graphicsPipeline{device, vkDestroyPipeline}; + std::vector swapChainImageViews; + + VkRenderPass renderPass; + VkPipelineLayout pipelineLayout; void initWindow() { glfwInit(); @@ -167,13 +108,31 @@ class HelloTriangleApplication { createImageViews(); createRenderPass(); createGraphicsPipeline(); - createFramebuffers(); } void mainLoop() { while (!glfwWindowShouldClose(window)) { glfwPollEvents(); } + } + + void cleanup() { + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugReportCallbackEXT(instance, callback, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); glfwDestroyWindow(window); @@ -198,17 +157,17 @@ class HelloTriangleApplication { createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } @@ -221,13 +180,13 @@ class HelloTriangleApplication { createInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT; createInfo.pfnCallback = debugCallback; - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { + if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, &callback) != VK_SUCCESS) { throw std::runtime_error("failed to set up debug callback!"); } } void createSurface() { - if (glfwCreateWindowSurface(instance, window, nullptr, surface.replace()) != VK_SUCCESS) { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { throw std::runtime_error("failed to create window surface!"); } } @@ -276,22 +235,22 @@ class HelloTriangleApplication { VkDeviceCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); createInfo.pQueueCreateInfos = queueCreateInfos.data(); - createInfo.queueCreateInfoCount = (uint32_t) queueCreateInfos.size(); createInfo.pEnabledFeatures = &deviceFeatures; - createInfo.enabledExtensionCount = deviceExtensions.size(); + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); createInfo.ppEnabledExtensionNames = deviceExtensions.data(); if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateDevice(physicalDevice, &createInfo, nullptr, device.replace()) != VK_SUCCESS) { + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { throw std::runtime_error("failed to create logical device!"); } @@ -340,10 +299,10 @@ class HelloTriangleApplication { createInfo.oldSwapchain = VK_NULL_HANDLE; - if (vkCreateSwapchainKHR(device, &createInfo, nullptr, swapChain.replace()) != VK_SUCCESS) { + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { throw std::runtime_error("failed to create swap chain!"); } - + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); swapChainImages.resize(imageCount); vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); @@ -353,9 +312,9 @@ class HelloTriangleApplication { } void createImageViews() { - swapChainImageViews.resize(swapChainImages.size(), VDeleter{device, vkDestroyImageView}); + swapChainImageViews.resize(swapChainImages.size()); - for (uint32_t i = 0; i < swapChainImages.size(); i++) { + for (size_t i = 0; i < swapChainImages.size(); i++) { VkImageViewCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; createInfo.image = swapChainImages[i]; @@ -371,7 +330,7 @@ class HelloTriangleApplication { createInfo.subresourceRange.baseArrayLayer = 0; createInfo.subresourceRange.layerCount = 1; - if (vkCreateImageView(device, &createInfo, nullptr, swapChainImageViews[i].replace()) != VK_SUCCESS) { + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { throw std::runtime_error("failed to create image views!"); } } @@ -404,7 +363,7 @@ class HelloTriangleApplication { renderPassInfo.subpassCount = 1; renderPassInfo.pSubpasses = &subpass; - if (vkCreateRenderPass(device, &renderPassInfo, nullptr, renderPass.replace()) != VK_SUCCESS) { + if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { throw std::runtime_error("failed to create render pass!"); } } @@ -413,10 +372,8 @@ class HelloTriangleApplication { auto vertShaderCode = readFile("shaders/vert.spv"); auto fragShaderCode = readFile("shaders/frag.spv"); - VDeleter vertShaderModule{device, vkDestroyShaderModule}; - VDeleter fragShaderModule{device, vkDestroyShaderModule}; - createShaderModule(vertShaderCode, vertShaderModule); - createShaderModule(fragShaderCode, fragShaderModule); + VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); VkPipelineShaderStageCreateInfo vertShaderStageInfo = {}; vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; @@ -495,67 +452,27 @@ class HelloTriangleApplication { pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; pipelineLayoutInfo.setLayoutCount = 0; pipelineLayoutInfo.pushConstantRangeCount = 0; - - if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, pipelineLayout.replace()) != VK_SUCCESS) { + + if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { throw std::runtime_error("failed to create pipeline layout!"); } - VkGraphicsPipelineCreateInfo pipelineInfo = {}; - pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; - pipelineInfo.stageCount = 2; - pipelineInfo.pStages = shaderStages; - pipelineInfo.pVertexInputState = &vertexInputInfo; - pipelineInfo.pInputAssemblyState = &inputAssembly; - pipelineInfo.pViewportState = &viewportState; - pipelineInfo.pRasterizationState = &rasterizer; - pipelineInfo.pMultisampleState = &multisampling; - pipelineInfo.pColorBlendState = &colorBlending; - pipelineInfo.layout = pipelineLayout; - pipelineInfo.renderPass = renderPass; - pipelineInfo.subpass = 0; - pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; - - if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, graphicsPipeline.replace()) != VK_SUCCESS) { - throw std::runtime_error("failed to create graphics pipeline!"); - } - } - - void createFramebuffers() { - swapChainFramebuffers.resize(swapChainImageViews.size(), VDeleter{device, vkDestroyFramebuffer}); - - for (size_t i = 0; i < swapChainImageViews.size(); i++) { - VkImageView attachments[] = { - swapChainImageViews[i] - }; - - VkFramebufferCreateInfo framebufferInfo = {}; - framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; - framebufferInfo.renderPass = renderPass; - framebufferInfo.attachmentCount = 1; - framebufferInfo.pAttachments = attachments; - framebufferInfo.width = swapChainExtent.width; - framebufferInfo.height = swapChainExtent.height; - framebufferInfo.layers = 1; - - if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, swapChainFramebuffers[i].replace()) != VK_SUCCESS) { - throw std::runtime_error("failed to create framebuffer!"); - } - } + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); } - void createShaderModule(const std::vector& code, VDeleter& shaderModule) { + VkShaderModule createShaderModule(const std::vector& code) { VkShaderModuleCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); - std::vector codeAligned(code.size() / 4 + 1); - memcpy(codeAligned.data(), code.data(), code.size()); - - createInfo.pCode = codeAligned.data(); - - if (vkCreateShaderModule(device, &createInfo, nullptr, shaderModule.replace()) != VK_SUCCESS) { + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { throw std::runtime_error("failed to create shader module!"); } + + return shaderModule; } VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { @@ -594,7 +511,7 @@ class HelloTriangleApplication { actualExtent.width = std::max(capabilities.minImageExtent.width, std::min(capabilities.maxImageExtent.width, actualExtent.width)); actualExtent.height = std::max(capabilities.minImageExtent.height, std::min(capabilities.maxImageExtent.height, actualExtent.height)); - + return actualExtent; } } @@ -686,15 +603,11 @@ class HelloTriangleApplication { } std::vector getRequiredExtensions() { - std::vector extensions; - - unsigned int glfwExtensionCount = 0; + uint32_t glfwExtensionCount = 0; const char** glfwExtensions; glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - for (unsigned int i = 0; i < glfwExtensionCount; i++) { - extensions.push_back(glfwExtensions[i]); - } + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); if (enableValidationLayers) { extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); @@ -758,10 +671,10 @@ int main() { try { app.run(); - } catch (const std::runtime_error& e) { + } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; -} \ No newline at end of file +} diff --git a/code/render_passes.cpp b/code/12_graphics_pipeline_complete.cpp similarity index 85% rename from code/render_passes.cpp rename to code/12_graphics_pipeline_complete.cpp index d239acdf..fa1328b5 100644 --- a/code/render_passes.cpp +++ b/code/12_graphics_pipeline_complete.cpp @@ -2,12 +2,12 @@ #include #include -#include -#include #include +#include #include #include #include +#include #include const int WIDTH = 800; @@ -43,64 +43,6 @@ void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT } } -template -class VDeleter { -public: - VDeleter() : VDeleter([](T, VkAllocationCallbacks*) {}) {} - - VDeleter(std::function deletef) { - this->deleter = [=](T obj) { deletef(obj, nullptr); }; - } - - VDeleter(const VDeleter& instance, std::function deletef) { - this->deleter = [&instance, deletef](T obj) { deletef(instance, obj, nullptr); }; - } - - VDeleter(const VDeleter& device, std::function deletef) { - this->deleter = [&device, deletef](T obj) { deletef(device, obj, nullptr); }; - } - - ~VDeleter() { - cleanup(); - } - - const T* operator &() const { - return &object; - } - - T* replace() { - cleanup(); - return &object; - } - - operator T() const { - return object; - } - - void operator=(T rhs) { - if (rhs != object) { - cleanup(); - object = rhs; - } - } - - template - bool operator==(V rhs) { - return object == T(rhs); - } - -private: - T object{VK_NULL_HANDLE}; - std::function deleter; - - void cleanup() { - if (object != VK_NULL_HANDLE) { - deleter(object); - } - object = VK_NULL_HANDLE; - } -}; - struct QueueFamilyIndices { int graphicsFamily = -1; int presentFamily = -1; @@ -122,30 +64,31 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugReportCallbackEXT callback; + VkSurfaceKHR surface; VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; - VDeleter device{vkDestroyDevice}; + VkDevice device; VkQueue graphicsQueue; VkQueue presentQueue; - - VDeleter swapChain{device, vkDestroySwapchainKHR}; + + VkSwapchainKHR swapChain; std::vector swapChainImages; VkFormat swapChainImageFormat; VkExtent2D swapChainExtent; - std::vector> swapChainImageViews; - std::vector> swapChainFramebuffers; - - VDeleter renderPass{device, vkDestroyRenderPass}; - VDeleter pipelineLayout{device, vkDestroyPipelineLayout}; + std::vector swapChainImageViews; + + VkRenderPass renderPass; + VkPipelineLayout pipelineLayout; + VkPipeline graphicsPipeline; void initWindow() { glfwInit(); @@ -172,6 +115,26 @@ class HelloTriangleApplication { while (!glfwWindowShouldClose(window)) { glfwPollEvents(); } + } + + void cleanup() { + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugReportCallbackEXT(instance, callback, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); glfwDestroyWindow(window); @@ -196,17 +159,17 @@ class HelloTriangleApplication { createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } @@ -219,13 +182,13 @@ class HelloTriangleApplication { createInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT; createInfo.pfnCallback = debugCallback; - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { + if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, &callback) != VK_SUCCESS) { throw std::runtime_error("failed to set up debug callback!"); } } void createSurface() { - if (glfwCreateWindowSurface(instance, window, nullptr, surface.replace()) != VK_SUCCESS) { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { throw std::runtime_error("failed to create window surface!"); } } @@ -274,22 +237,22 @@ class HelloTriangleApplication { VkDeviceCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); createInfo.pQueueCreateInfos = queueCreateInfos.data(); - createInfo.queueCreateInfoCount = (uint32_t) queueCreateInfos.size(); createInfo.pEnabledFeatures = &deviceFeatures; - createInfo.enabledExtensionCount = deviceExtensions.size(); + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); createInfo.ppEnabledExtensionNames = deviceExtensions.data(); if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateDevice(physicalDevice, &createInfo, nullptr, device.replace()) != VK_SUCCESS) { + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { throw std::runtime_error("failed to create logical device!"); } @@ -338,10 +301,10 @@ class HelloTriangleApplication { createInfo.oldSwapchain = VK_NULL_HANDLE; - if (vkCreateSwapchainKHR(device, &createInfo, nullptr, swapChain.replace()) != VK_SUCCESS) { + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { throw std::runtime_error("failed to create swap chain!"); } - + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); swapChainImages.resize(imageCount); vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); @@ -351,9 +314,9 @@ class HelloTriangleApplication { } void createImageViews() { - swapChainImageViews.resize(swapChainImages.size(), VDeleter{device, vkDestroyImageView}); + swapChainImageViews.resize(swapChainImages.size()); - for (uint32_t i = 0; i < swapChainImages.size(); i++) { + for (size_t i = 0; i < swapChainImages.size(); i++) { VkImageViewCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; createInfo.image = swapChainImages[i]; @@ -369,7 +332,7 @@ class HelloTriangleApplication { createInfo.subresourceRange.baseArrayLayer = 0; createInfo.subresourceRange.layerCount = 1; - if (vkCreateImageView(device, &createInfo, nullptr, swapChainImageViews[i].replace()) != VK_SUCCESS) { + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { throw std::runtime_error("failed to create image views!"); } } @@ -402,7 +365,7 @@ class HelloTriangleApplication { renderPassInfo.subpassCount = 1; renderPassInfo.pSubpasses = &subpass; - if (vkCreateRenderPass(device, &renderPassInfo, nullptr, renderPass.replace()) != VK_SUCCESS) { + if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { throw std::runtime_error("failed to create render pass!"); } } @@ -411,10 +374,8 @@ class HelloTriangleApplication { auto vertShaderCode = readFile("shaders/vert.spv"); auto fragShaderCode = readFile("shaders/frag.spv"); - VDeleter vertShaderModule{device, vkDestroyShaderModule}; - VDeleter fragShaderModule{device, vkDestroyShaderModule}; - createShaderModule(vertShaderCode, vertShaderModule); - createShaderModule(fragShaderCode, fragShaderModule); + VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); VkPipelineShaderStageCreateInfo vertShaderStageInfo = {}; vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; @@ -493,25 +454,46 @@ class HelloTriangleApplication { pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; pipelineLayoutInfo.setLayoutCount = 0; pipelineLayoutInfo.pushConstantRangeCount = 0; - - if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, pipelineLayout.replace()) != VK_SUCCESS) { + + if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { throw std::runtime_error("failed to create pipeline layout!"); } + + VkGraphicsPipelineCreateInfo pipelineInfo = {}; + pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipelineInfo.stageCount = 2; + pipelineInfo.pStages = shaderStages; + pipelineInfo.pVertexInputState = &vertexInputInfo; + pipelineInfo.pInputAssemblyState = &inputAssembly; + pipelineInfo.pViewportState = &viewportState; + pipelineInfo.pRasterizationState = &rasterizer; + pipelineInfo.pMultisampleState = &multisampling; + pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.layout = pipelineLayout; + pipelineInfo.renderPass = renderPass; + pipelineInfo.subpass = 0; + pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; + + if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { + throw std::runtime_error("failed to create graphics pipeline!"); + } + + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); } - void createShaderModule(const std::vector& code, VDeleter& shaderModule) { + VkShaderModule createShaderModule(const std::vector& code) { VkShaderModuleCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); - std::vector codeAligned(code.size() / 4 + 1); - memcpy(codeAligned.data(), code.data(), code.size()); - - createInfo.pCode = codeAligned.data(); - - if (vkCreateShaderModule(device, &createInfo, nullptr, shaderModule.replace()) != VK_SUCCESS) { + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { throw std::runtime_error("failed to create shader module!"); } + + return shaderModule; } VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { @@ -550,7 +532,7 @@ class HelloTriangleApplication { actualExtent.width = std::max(capabilities.minImageExtent.width, std::min(capabilities.maxImageExtent.width, actualExtent.width)); actualExtent.height = std::max(capabilities.minImageExtent.height, std::min(capabilities.maxImageExtent.height, actualExtent.height)); - + return actualExtent; } } @@ -642,15 +624,11 @@ class HelloTriangleApplication { } std::vector getRequiredExtensions() { - std::vector extensions; - - unsigned int glfwExtensionCount = 0; + uint32_t glfwExtensionCount = 0; const char** glfwExtensions; glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - for (unsigned int i = 0; i < glfwExtensionCount; i++) { - extensions.push_back(glfwExtensions[i]); - } + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); if (enableValidationLayers) { extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); @@ -714,10 +692,10 @@ int main() { try { app.run(); - } catch (const std::runtime_error& e) { + } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; -} \ No newline at end of file +} diff --git a/code/graphics_pipeline_complete.cpp b/code/13_framebuffers.cpp similarity index 85% rename from code/graphics_pipeline_complete.cpp rename to code/13_framebuffers.cpp index c22f9892..b7e04df9 100644 --- a/code/graphics_pipeline_complete.cpp +++ b/code/13_framebuffers.cpp @@ -2,12 +2,12 @@ #include #include -#include -#include #include +#include #include #include #include +#include #include const int WIDTH = 800; @@ -43,64 +43,6 @@ void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT } } -template -class VDeleter { -public: - VDeleter() : VDeleter([](T, VkAllocationCallbacks*) {}) {} - - VDeleter(std::function deletef) { - this->deleter = [=](T obj) { deletef(obj, nullptr); }; - } - - VDeleter(const VDeleter& instance, std::function deletef) { - this->deleter = [&instance, deletef](T obj) { deletef(instance, obj, nullptr); }; - } - - VDeleter(const VDeleter& device, std::function deletef) { - this->deleter = [&device, deletef](T obj) { deletef(device, obj, nullptr); }; - } - - ~VDeleter() { - cleanup(); - } - - const T* operator &() const { - return &object; - } - - T* replace() { - cleanup(); - return &object; - } - - operator T() const { - return object; - } - - void operator=(T rhs) { - if (rhs != object) { - cleanup(); - object = rhs; - } - } - - template - bool operator==(V rhs) { - return object == T(rhs); - } - -private: - T object{VK_NULL_HANDLE}; - std::function deleter; - - void cleanup() { - if (object != VK_NULL_HANDLE) { - deleter(object); - } - object = VK_NULL_HANDLE; - } -}; - struct QueueFamilyIndices { int graphicsFamily = -1; int presentFamily = -1; @@ -122,31 +64,32 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugReportCallbackEXT callback; + VkSurfaceKHR surface; VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; - VDeleter device{vkDestroyDevice}; + VkDevice device; VkQueue graphicsQueue; VkQueue presentQueue; - - VDeleter swapChain{device, vkDestroySwapchainKHR}; + + VkSwapchainKHR swapChain; std::vector swapChainImages; VkFormat swapChainImageFormat; VkExtent2D swapChainExtent; - std::vector> swapChainImageViews; - std::vector> swapChainFramebuffers; - - VDeleter renderPass{device, vkDestroyRenderPass}; - VDeleter pipelineLayout{device, vkDestroyPipelineLayout}; - VDeleter graphicsPipeline{device, vkDestroyPipeline}; + std::vector swapChainImageViews; + std::vector swapChainFramebuffers; + + VkRenderPass renderPass; + VkPipelineLayout pipelineLayout; + VkPipeline graphicsPipeline; void initWindow() { glfwInit(); @@ -167,12 +110,37 @@ class HelloTriangleApplication { createImageViews(); createRenderPass(); createGraphicsPipeline(); + createFramebuffers(); } void mainLoop() { while (!glfwWindowShouldClose(window)) { glfwPollEvents(); } + } + + void cleanup() { + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugReportCallbackEXT(instance, callback, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); glfwDestroyWindow(window); @@ -197,17 +165,17 @@ class HelloTriangleApplication { createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } @@ -220,13 +188,13 @@ class HelloTriangleApplication { createInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT; createInfo.pfnCallback = debugCallback; - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { + if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, &callback) != VK_SUCCESS) { throw std::runtime_error("failed to set up debug callback!"); } } void createSurface() { - if (glfwCreateWindowSurface(instance, window, nullptr, surface.replace()) != VK_SUCCESS) { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { throw std::runtime_error("failed to create window surface!"); } } @@ -275,22 +243,22 @@ class HelloTriangleApplication { VkDeviceCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); createInfo.pQueueCreateInfos = queueCreateInfos.data(); - createInfo.queueCreateInfoCount = (uint32_t) queueCreateInfos.size(); createInfo.pEnabledFeatures = &deviceFeatures; - createInfo.enabledExtensionCount = deviceExtensions.size(); + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); createInfo.ppEnabledExtensionNames = deviceExtensions.data(); if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateDevice(physicalDevice, &createInfo, nullptr, device.replace()) != VK_SUCCESS) { + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { throw std::runtime_error("failed to create logical device!"); } @@ -339,10 +307,10 @@ class HelloTriangleApplication { createInfo.oldSwapchain = VK_NULL_HANDLE; - if (vkCreateSwapchainKHR(device, &createInfo, nullptr, swapChain.replace()) != VK_SUCCESS) { + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { throw std::runtime_error("failed to create swap chain!"); } - + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); swapChainImages.resize(imageCount); vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); @@ -352,9 +320,9 @@ class HelloTriangleApplication { } void createImageViews() { - swapChainImageViews.resize(swapChainImages.size(), VDeleter{device, vkDestroyImageView}); + swapChainImageViews.resize(swapChainImages.size()); - for (uint32_t i = 0; i < swapChainImages.size(); i++) { + for (size_t i = 0; i < swapChainImages.size(); i++) { VkImageViewCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; createInfo.image = swapChainImages[i]; @@ -370,7 +338,7 @@ class HelloTriangleApplication { createInfo.subresourceRange.baseArrayLayer = 0; createInfo.subresourceRange.layerCount = 1; - if (vkCreateImageView(device, &createInfo, nullptr, swapChainImageViews[i].replace()) != VK_SUCCESS) { + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { throw std::runtime_error("failed to create image views!"); } } @@ -403,7 +371,7 @@ class HelloTriangleApplication { renderPassInfo.subpassCount = 1; renderPassInfo.pSubpasses = &subpass; - if (vkCreateRenderPass(device, &renderPassInfo, nullptr, renderPass.replace()) != VK_SUCCESS) { + if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { throw std::runtime_error("failed to create render pass!"); } } @@ -412,10 +380,8 @@ class HelloTriangleApplication { auto vertShaderCode = readFile("shaders/vert.spv"); auto fragShaderCode = readFile("shaders/frag.spv"); - VDeleter vertShaderModule{device, vkDestroyShaderModule}; - VDeleter fragShaderModule{device, vkDestroyShaderModule}; - createShaderModule(vertShaderCode, vertShaderModule); - createShaderModule(fragShaderCode, fragShaderModule); + VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); VkPipelineShaderStageCreateInfo vertShaderStageInfo = {}; vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; @@ -494,8 +460,8 @@ class HelloTriangleApplication { pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; pipelineLayoutInfo.setLayoutCount = 0; pipelineLayoutInfo.pushConstantRangeCount = 0; - - if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, pipelineLayout.replace()) != VK_SUCCESS) { + + if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { throw std::runtime_error("failed to create pipeline layout!"); } @@ -514,24 +480,49 @@ class HelloTriangleApplication { pipelineInfo.subpass = 0; pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; - if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, graphicsPipeline.replace()) != VK_SUCCESS) { + if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { throw std::runtime_error("failed to create graphics pipeline!"); } + + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); + } + + void createFramebuffers() { + swapChainFramebuffers.resize(swapChainImageViews.size()); + + for (size_t i = 0; i < swapChainImageViews.size(); i++) { + VkImageView attachments[] = { + swapChainImageViews[i] + }; + + VkFramebufferCreateInfo framebufferInfo = {}; + framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebufferInfo.renderPass = renderPass; + framebufferInfo.attachmentCount = 1; + framebufferInfo.pAttachments = attachments; + framebufferInfo.width = swapChainExtent.width; + framebufferInfo.height = swapChainExtent.height; + framebufferInfo.layers = 1; + + if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create framebuffer!"); + } + } } - void createShaderModule(const std::vector& code, VDeleter& shaderModule) { + VkShaderModule createShaderModule(const std::vector& code) { VkShaderModuleCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); - std::vector codeAligned(code.size() / 4 + 1); - memcpy(codeAligned.data(), code.data(), code.size()); - - createInfo.pCode = codeAligned.data(); - - if (vkCreateShaderModule(device, &createInfo, nullptr, shaderModule.replace()) != VK_SUCCESS) { + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { throw std::runtime_error("failed to create shader module!"); } + + return shaderModule; } VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { @@ -570,7 +561,7 @@ class HelloTriangleApplication { actualExtent.width = std::max(capabilities.minImageExtent.width, std::min(capabilities.maxImageExtent.width, actualExtent.width)); actualExtent.height = std::max(capabilities.minImageExtent.height, std::min(capabilities.maxImageExtent.height, actualExtent.height)); - + return actualExtent; } } @@ -662,15 +653,11 @@ class HelloTriangleApplication { } std::vector getRequiredExtensions() { - std::vector extensions; - - unsigned int glfwExtensionCount = 0; + uint32_t glfwExtensionCount = 0; const char** glfwExtensions; glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - for (unsigned int i = 0; i < glfwExtensionCount; i++) { - extensions.push_back(glfwExtensions[i]); - } + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); if (enableValidationLayers) { extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); @@ -734,10 +721,10 @@ int main() { try { app.run(); - } catch (const std::runtime_error& e) { + } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; -} \ No newline at end of file +} diff --git a/code/command_buffers.cpp b/code/14_command_buffers.cpp similarity index 85% rename from code/command_buffers.cpp rename to code/14_command_buffers.cpp index f1da66b0..13efe632 100644 --- a/code/command_buffers.cpp +++ b/code/14_command_buffers.cpp @@ -2,12 +2,12 @@ #include #include -#include -#include #include +#include #include #include #include +#include #include const int WIDTH = 800; @@ -43,64 +43,6 @@ void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT } } -template -class VDeleter { -public: - VDeleter() : VDeleter([](T, VkAllocationCallbacks*) {}) {} - - VDeleter(std::function deletef) { - this->deleter = [=](T obj) { deletef(obj, nullptr); }; - } - - VDeleter(const VDeleter& instance, std::function deletef) { - this->deleter = [&instance, deletef](T obj) { deletef(instance, obj, nullptr); }; - } - - VDeleter(const VDeleter& device, std::function deletef) { - this->deleter = [&device, deletef](T obj) { deletef(device, obj, nullptr); }; - } - - ~VDeleter() { - cleanup(); - } - - const T* operator &() const { - return &object; - } - - T* replace() { - cleanup(); - return &object; - } - - operator T() const { - return object; - } - - void operator=(T rhs) { - if (rhs != object) { - cleanup(); - object = rhs; - } - } - - template - bool operator==(V rhs) { - return object == T(rhs); - } - -private: - T object{VK_NULL_HANDLE}; - std::function deleter; - - void cleanup() { - if (object != VK_NULL_HANDLE) { - deleter(object); - } - object = VK_NULL_HANDLE; - } -}; - struct QueueFamilyIndices { int graphicsFamily = -1; int presentFamily = -1; @@ -122,33 +64,34 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugReportCallbackEXT callback; + VkSurfaceKHR surface; VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; - VDeleter device{vkDestroyDevice}; + VkDevice device; VkQueue graphicsQueue; VkQueue presentQueue; - - VDeleter swapChain{device, vkDestroySwapchainKHR}; + + VkSwapchainKHR swapChain; std::vector swapChainImages; VkFormat swapChainImageFormat; VkExtent2D swapChainExtent; - std::vector> swapChainImageViews; - std::vector> swapChainFramebuffers; - - VDeleter renderPass{device, vkDestroyRenderPass}; - VDeleter pipelineLayout{device, vkDestroyPipelineLayout}; - VDeleter graphicsPipeline{device, vkDestroyPipeline}; - - VDeleter commandPool{device, vkDestroyCommandPool}; + std::vector swapChainImageViews; + std::vector swapChainFramebuffers; + + VkRenderPass renderPass; + VkPipelineLayout pipelineLayout; + VkPipeline graphicsPipeline; + + VkCommandPool commandPool; std::vector commandBuffers; void initWindow() { @@ -179,6 +122,32 @@ class HelloTriangleApplication { while (!glfwWindowShouldClose(window)) { glfwPollEvents(); } + } + + void cleanup() { + vkDestroyCommandPool(device, commandPool, nullptr); + + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugReportCallbackEXT(instance, callback, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); glfwDestroyWindow(window); @@ -203,17 +172,17 @@ class HelloTriangleApplication { createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } @@ -226,13 +195,13 @@ class HelloTriangleApplication { createInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT; createInfo.pfnCallback = debugCallback; - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { + if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, &callback) != VK_SUCCESS) { throw std::runtime_error("failed to set up debug callback!"); } } void createSurface() { - if (glfwCreateWindowSurface(instance, window, nullptr, surface.replace()) != VK_SUCCESS) { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { throw std::runtime_error("failed to create window surface!"); } } @@ -281,22 +250,22 @@ class HelloTriangleApplication { VkDeviceCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); createInfo.pQueueCreateInfos = queueCreateInfos.data(); - createInfo.queueCreateInfoCount = (uint32_t) queueCreateInfos.size(); createInfo.pEnabledFeatures = &deviceFeatures; - createInfo.enabledExtensionCount = deviceExtensions.size(); + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); createInfo.ppEnabledExtensionNames = deviceExtensions.data(); if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateDevice(physicalDevice, &createInfo, nullptr, device.replace()) != VK_SUCCESS) { + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { throw std::runtime_error("failed to create logical device!"); } @@ -345,10 +314,10 @@ class HelloTriangleApplication { createInfo.oldSwapchain = VK_NULL_HANDLE; - if (vkCreateSwapchainKHR(device, &createInfo, nullptr, swapChain.replace()) != VK_SUCCESS) { + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { throw std::runtime_error("failed to create swap chain!"); } - + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); swapChainImages.resize(imageCount); vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); @@ -358,9 +327,9 @@ class HelloTriangleApplication { } void createImageViews() { - swapChainImageViews.resize(swapChainImages.size(), VDeleter{device, vkDestroyImageView}); + swapChainImageViews.resize(swapChainImages.size()); - for (uint32_t i = 0; i < swapChainImages.size(); i++) { + for (size_t i = 0; i < swapChainImages.size(); i++) { VkImageViewCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; createInfo.image = swapChainImages[i]; @@ -376,7 +345,7 @@ class HelloTriangleApplication { createInfo.subresourceRange.baseArrayLayer = 0; createInfo.subresourceRange.layerCount = 1; - if (vkCreateImageView(device, &createInfo, nullptr, swapChainImageViews[i].replace()) != VK_SUCCESS) { + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { throw std::runtime_error("failed to create image views!"); } } @@ -409,7 +378,7 @@ class HelloTriangleApplication { renderPassInfo.subpassCount = 1; renderPassInfo.pSubpasses = &subpass; - if (vkCreateRenderPass(device, &renderPassInfo, nullptr, renderPass.replace()) != VK_SUCCESS) { + if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { throw std::runtime_error("failed to create render pass!"); } } @@ -418,10 +387,8 @@ class HelloTriangleApplication { auto vertShaderCode = readFile("shaders/vert.spv"); auto fragShaderCode = readFile("shaders/frag.spv"); - VDeleter vertShaderModule{device, vkDestroyShaderModule}; - VDeleter fragShaderModule{device, vkDestroyShaderModule}; - createShaderModule(vertShaderCode, vertShaderModule); - createShaderModule(fragShaderCode, fragShaderModule); + VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); VkPipelineShaderStageCreateInfo vertShaderStageInfo = {}; vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; @@ -500,8 +467,8 @@ class HelloTriangleApplication { pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; pipelineLayoutInfo.setLayoutCount = 0; pipelineLayoutInfo.pushConstantRangeCount = 0; - - if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, pipelineLayout.replace()) != VK_SUCCESS) { + + if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { throw std::runtime_error("failed to create pipeline layout!"); } @@ -520,13 +487,16 @@ class HelloTriangleApplication { pipelineInfo.subpass = 0; pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; - if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, graphicsPipeline.replace()) != VK_SUCCESS) { + if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { throw std::runtime_error("failed to create graphics pipeline!"); } + + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); } void createFramebuffers() { - swapChainFramebuffers.resize(swapChainImageViews.size(), VDeleter{device, vkDestroyFramebuffer}); + swapChainFramebuffers.resize(swapChainImageViews.size()); for (size_t i = 0; i < swapChainImageViews.size(); i++) { VkImageView attachments[] = { @@ -542,7 +512,7 @@ class HelloTriangleApplication { framebufferInfo.height = swapChainExtent.height; framebufferInfo.layers = 1; - if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, swapChainFramebuffers[i].replace()) != VK_SUCCESS) { + if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { throw std::runtime_error("failed to create framebuffer!"); } } @@ -554,8 +524,8 @@ class HelloTriangleApplication { VkCommandPoolCreateInfo poolInfo = {}; poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily; - - if (vkCreateCommandPool(device, &poolInfo, nullptr, commandPool.replace()) != VK_SUCCESS) { + + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { throw std::runtime_error("failed to create command pool!"); } } @@ -578,7 +548,9 @@ class HelloTriangleApplication { beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; beginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT; - vkBeginCommandBuffer(commandBuffers[i], &beginInfo); + if (vkBeginCommandBuffer(commandBuffers[i], &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } VkRenderPassBeginInfo renderPassInfo = {}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; @@ -605,19 +577,18 @@ class HelloTriangleApplication { } } - void createShaderModule(const std::vector& code, VDeleter& shaderModule) { + VkShaderModule createShaderModule(const std::vector& code) { VkShaderModuleCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); - std::vector codeAligned(code.size() / 4 + 1); - memcpy(codeAligned.data(), code.data(), code.size()); - - createInfo.pCode = codeAligned.data(); - - if (vkCreateShaderModule(device, &createInfo, nullptr, shaderModule.replace()) != VK_SUCCESS) { + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { throw std::runtime_error("failed to create shader module!"); } + + return shaderModule; } VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { @@ -656,7 +627,7 @@ class HelloTriangleApplication { actualExtent.width = std::max(capabilities.minImageExtent.width, std::min(capabilities.maxImageExtent.width, actualExtent.width)); actualExtent.height = std::max(capabilities.minImageExtent.height, std::min(capabilities.maxImageExtent.height, actualExtent.height)); - + return actualExtent; } } @@ -748,15 +719,11 @@ class HelloTriangleApplication { } std::vector getRequiredExtensions() { - std::vector extensions; - - unsigned int glfwExtensionCount = 0; + uint32_t glfwExtensionCount = 0; const char** glfwExtensions; glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - for (unsigned int i = 0; i < glfwExtensionCount; i++) { - extensions.push_back(glfwExtensions[i]); - } + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); if (enableValidationLayers) { extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); @@ -820,10 +787,10 @@ int main() { try { app.run(); - } catch (const std::runtime_error& e) { + } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; -} \ No newline at end of file +} diff --git a/code/hello_triangle.cpp b/code/15_hello_triangle.cpp similarity index 84% rename from code/hello_triangle.cpp rename to code/15_hello_triangle.cpp index d09876d5..03b7c2eb 100644 --- a/code/hello_triangle.cpp +++ b/code/15_hello_triangle.cpp @@ -2,17 +2,19 @@ #include #include -#include -#include #include +#include #include #include #include +#include #include const int WIDTH = 800; const int HEIGHT = 600; +const int MAX_FRAMES_IN_FLIGHT = 2; + const std::vector validationLayers = { "VK_LAYER_LUNARG_standard_validation" }; @@ -43,64 +45,6 @@ void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT } } -template -class VDeleter { -public: - VDeleter() : VDeleter([](T, VkAllocationCallbacks*) {}) {} - - VDeleter(std::function deletef) { - this->deleter = [=](T obj) { deletef(obj, nullptr); }; - } - - VDeleter(const VDeleter& instance, std::function deletef) { - this->deleter = [&instance, deletef](T obj) { deletef(instance, obj, nullptr); }; - } - - VDeleter(const VDeleter& device, std::function deletef) { - this->deleter = [&device, deletef](T obj) { deletef(device, obj, nullptr); }; - } - - ~VDeleter() { - cleanup(); - } - - const T* operator &() const { - return &object; - } - - T* replace() { - cleanup(); - return &object; - } - - operator T() const { - return object; - } - - void operator=(T rhs) { - if (rhs != object) { - cleanup(); - object = rhs; - } - } - - template - bool operator==(V rhs) { - return object == T(rhs); - } - -private: - T object{VK_NULL_HANDLE}; - std::function deleter; - - void cleanup() { - if (object != VK_NULL_HANDLE) { - deleter(object); - } - object = VK_NULL_HANDLE; - } -}; - struct QueueFamilyIndices { int graphicsFamily = -1; int presentFamily = -1; @@ -122,37 +66,40 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugReportCallbackEXT callback; + VkSurfaceKHR surface; VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; - VDeleter device{vkDestroyDevice}; + VkDevice device; VkQueue graphicsQueue; VkQueue presentQueue; - - VDeleter swapChain{device, vkDestroySwapchainKHR}; + + VkSwapchainKHR swapChain; std::vector swapChainImages; VkFormat swapChainImageFormat; VkExtent2D swapChainExtent; - std::vector> swapChainImageViews; - std::vector> swapChainFramebuffers; - - VDeleter renderPass{device, vkDestroyRenderPass}; - VDeleter pipelineLayout{device, vkDestroyPipelineLayout}; - VDeleter graphicsPipeline{device, vkDestroyPipeline}; - - VDeleter commandPool{device, vkDestroyCommandPool}; + std::vector swapChainImageViews; + std::vector swapChainFramebuffers; + + VkRenderPass renderPass; + VkPipelineLayout pipelineLayout; + VkPipeline graphicsPipeline; + + VkCommandPool commandPool; std::vector commandBuffers; - VDeleter imageAvailableSemaphore{device, vkDestroySemaphore}; - VDeleter renderFinishedSemaphore{device, vkDestroySemaphore}; + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + std::vector inFlightFences; + size_t currentFrame = 0; void initWindow() { glfwInit(); @@ -176,7 +123,7 @@ class HelloTriangleApplication { createFramebuffers(); createCommandPool(); createCommandBuffers(); - createSemaphores(); + createSyncObjects(); } void mainLoop() { @@ -186,6 +133,38 @@ class HelloTriangleApplication { } vkDeviceWaitIdle(device); + } + + void cleanup() { + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + vkDestroyFence(device, inFlightFences[i], nullptr); + } + + vkDestroyCommandPool(device, commandPool, nullptr); + + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugReportCallbackEXT(instance, callback, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); glfwDestroyWindow(window); @@ -210,17 +189,17 @@ class HelloTriangleApplication { createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } @@ -233,13 +212,13 @@ class HelloTriangleApplication { createInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT; createInfo.pfnCallback = debugCallback; - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { + if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, &callback) != VK_SUCCESS) { throw std::runtime_error("failed to set up debug callback!"); } } void createSurface() { - if (glfwCreateWindowSurface(instance, window, nullptr, surface.replace()) != VK_SUCCESS) { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { throw std::runtime_error("failed to create window surface!"); } } @@ -288,22 +267,22 @@ class HelloTriangleApplication { VkDeviceCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); createInfo.pQueueCreateInfos = queueCreateInfos.data(); - createInfo.queueCreateInfoCount = (uint32_t) queueCreateInfos.size(); createInfo.pEnabledFeatures = &deviceFeatures; - createInfo.enabledExtensionCount = deviceExtensions.size(); + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); createInfo.ppEnabledExtensionNames = deviceExtensions.data(); if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateDevice(physicalDevice, &createInfo, nullptr, device.replace()) != VK_SUCCESS) { + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { throw std::runtime_error("failed to create logical device!"); } @@ -352,7 +331,7 @@ class HelloTriangleApplication { createInfo.oldSwapchain = VK_NULL_HANDLE; - if (vkCreateSwapchainKHR(device, &createInfo, nullptr, swapChain.replace()) != VK_SUCCESS) { + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { throw std::runtime_error("failed to create swap chain!"); } @@ -365,9 +344,9 @@ class HelloTriangleApplication { } void createImageViews() { - swapChainImageViews.resize(swapChainImages.size(), VDeleter{device, vkDestroyImageView}); + swapChainImageViews.resize(swapChainImages.size()); - for (uint32_t i = 0; i < swapChainImages.size(); i++) { + for (size_t i = 0; i < swapChainImages.size(); i++) { VkImageViewCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; createInfo.image = swapChainImages[i]; @@ -383,7 +362,7 @@ class HelloTriangleApplication { createInfo.subresourceRange.baseArrayLayer = 0; createInfo.subresourceRange.layerCount = 1; - if (vkCreateImageView(device, &createInfo, nullptr, swapChainImageViews[i].replace()) != VK_SUCCESS) { + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { throw std::runtime_error("failed to create image views!"); } } @@ -426,7 +405,7 @@ class HelloTriangleApplication { renderPassInfo.dependencyCount = 1; renderPassInfo.pDependencies = &dependency; - if (vkCreateRenderPass(device, &renderPassInfo, nullptr, renderPass.replace()) != VK_SUCCESS) { + if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { throw std::runtime_error("failed to create render pass!"); } } @@ -435,10 +414,8 @@ class HelloTriangleApplication { auto vertShaderCode = readFile("shaders/vert.spv"); auto fragShaderCode = readFile("shaders/frag.spv"); - VDeleter vertShaderModule{device, vkDestroyShaderModule}; - VDeleter fragShaderModule{device, vkDestroyShaderModule}; - createShaderModule(vertShaderCode, vertShaderModule); - createShaderModule(fragShaderCode, fragShaderModule); + VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); VkPipelineShaderStageCreateInfo vertShaderStageInfo = {}; vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; @@ -518,7 +495,7 @@ class HelloTriangleApplication { pipelineLayoutInfo.setLayoutCount = 0; pipelineLayoutInfo.pushConstantRangeCount = 0; - if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, pipelineLayout.replace()) != VK_SUCCESS) { + if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { throw std::runtime_error("failed to create pipeline layout!"); } @@ -537,13 +514,16 @@ class HelloTriangleApplication { pipelineInfo.subpass = 0; pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; - if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, graphicsPipeline.replace()) != VK_SUCCESS) { + if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { throw std::runtime_error("failed to create graphics pipeline!"); } + + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); } void createFramebuffers() { - swapChainFramebuffers.resize(swapChainImageViews.size(), VDeleter{device, vkDestroyFramebuffer}); + swapChainFramebuffers.resize(swapChainImageViews.size()); for (size_t i = 0; i < swapChainImageViews.size(); i++) { VkImageView attachments[] = { @@ -559,7 +539,7 @@ class HelloTriangleApplication { framebufferInfo.height = swapChainExtent.height; framebufferInfo.layers = 1; - if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, swapChainFramebuffers[i].replace()) != VK_SUCCESS) { + if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { throw std::runtime_error("failed to create framebuffer!"); } } @@ -572,7 +552,7 @@ class HelloTriangleApplication { poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily; - if (vkCreateCommandPool(device, &poolInfo, nullptr, commandPool.replace()) != VK_SUCCESS) { + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { throw std::runtime_error("failed to create command pool!"); } } @@ -595,7 +575,9 @@ class HelloTriangleApplication { beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; beginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT; - vkBeginCommandBuffer(commandBuffers[i], &beginInfo); + if (vkBeginCommandBuffer(commandBuffers[i], &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } VkRenderPassBeginInfo renderPassInfo = {}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; @@ -621,26 +603,39 @@ class HelloTriangleApplication { } } } - - void createSemaphores() { + + void createSyncObjects() { + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + inFlightFences.resize(MAX_FRAMES_IN_FLIGHT); + VkSemaphoreCreateInfo semaphoreInfo = {}; semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; - if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, imageAvailableSemaphore.replace()) != VK_SUCCESS || - vkCreateSemaphore(device, &semaphoreInfo, nullptr, renderFinishedSemaphore.replace()) != VK_SUCCESS) { + VkFenceCreateInfo fenceInfo = {}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; - throw std::runtime_error("failed to create semaphores!"); + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS || + vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create synchronization objects for a frame!"); + } } } void drawFrame() { + vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, std::numeric_limits::max()); + vkResetFences(device, 1, &inFlightFences[currentFrame]); + uint32_t imageIndex; - vkAcquireNextImageKHR(device, swapChain, std::numeric_limits::max(), imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex); + vkAcquireNextImageKHR(device, swapChain, std::numeric_limits::max(), imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); VkSubmitInfo submitInfo = {}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; - VkSemaphore waitSemaphores[] = {imageAvailableSemaphore}; + VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[currentFrame]}; VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; submitInfo.waitSemaphoreCount = 1; submitInfo.pWaitSemaphores = waitSemaphores; @@ -649,11 +644,11 @@ class HelloTriangleApplication { submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &commandBuffers[imageIndex]; - VkSemaphore signalSemaphores[] = {renderFinishedSemaphore}; + VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[currentFrame]}; submitInfo.signalSemaphoreCount = 1; submitInfo.pSignalSemaphores = signalSemaphores; - if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) { throw std::runtime_error("failed to submit draw command buffer!"); } @@ -670,21 +665,22 @@ class HelloTriangleApplication { presentInfo.pImageIndices = &imageIndex; vkQueuePresentKHR(presentQueue, &presentInfo); + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; } - void createShaderModule(const std::vector& code, VDeleter& shaderModule) { + VkShaderModule createShaderModule(const std::vector& code) { VkShaderModuleCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); - std::vector codeAligned(code.size() / 4 + 1); - memcpy(codeAligned.data(), code.data(), code.size()); - - createInfo.pCode = codeAligned.data(); - - if (vkCreateShaderModule(device, &createInfo, nullptr, shaderModule.replace()) != VK_SUCCESS) { + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { throw std::runtime_error("failed to create shader module!"); } + + return shaderModule; } VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { @@ -815,15 +811,11 @@ class HelloTriangleApplication { } std::vector getRequiredExtensions() { - std::vector extensions; - - unsigned int glfwExtensionCount = 0; + uint32_t glfwExtensionCount = 0; const char** glfwExtensions; glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - for (unsigned int i = 0; i < glfwExtensionCount; i++) { - extensions.push_back(glfwExtensions[i]); - } + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); if (enableValidationLayers) { extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); @@ -887,10 +879,10 @@ int main() { try { app.run(); - } catch (const std::runtime_error& e) { + } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; -} \ No newline at end of file +} diff --git a/code/swap_chain_recreation.cpp b/code/16_swap_chain_recreation.cpp similarity index 82% rename from code/swap_chain_recreation.cpp rename to code/16_swap_chain_recreation.cpp index 21f09e57..62fd5d4a 100644 --- a/code/swap_chain_recreation.cpp +++ b/code/16_swap_chain_recreation.cpp @@ -2,17 +2,19 @@ #include #include -#include -#include #include +#include #include #include #include +#include #include const int WIDTH = 800; const int HEIGHT = 600; +const int MAX_FRAMES_IN_FLIGHT = 2; + const std::vector validationLayers = { "VK_LAYER_LUNARG_standard_validation" }; @@ -43,64 +45,6 @@ void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT } } -template -class VDeleter { -public: - VDeleter() : VDeleter([](T, VkAllocationCallbacks*) {}) {} - - VDeleter(std::function deletef) { - this->deleter = [=](T obj) { deletef(obj, nullptr); }; - } - - VDeleter(const VDeleter& instance, std::function deletef) { - this->deleter = [&instance, deletef](T obj) { deletef(instance, obj, nullptr); }; - } - - VDeleter(const VDeleter& device, std::function deletef) { - this->deleter = [&device, deletef](T obj) { deletef(device, obj, nullptr); }; - } - - ~VDeleter() { - cleanup(); - } - - const T* operator &() const { - return &object; - } - - T* replace() { - cleanup(); - return &object; - } - - operator T() const { - return object; - } - - void operator=(T rhs) { - if (rhs != object) { - cleanup(); - object = rhs; - } - } - - template - bool operator==(V rhs) { - return object == T(rhs); - } - -private: - T object{VK_NULL_HANDLE}; - std::function deleter; - - void cleanup() { - if (object != VK_NULL_HANDLE) { - deleter(object); - } - object = VK_NULL_HANDLE; - } -}; - struct QueueFamilyIndices { int graphicsFamily = -1; int presentFamily = -1; @@ -122,37 +66,42 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugReportCallbackEXT callback; + VkSurfaceKHR surface; VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; - VDeleter device{vkDestroyDevice}; + VkDevice device; VkQueue graphicsQueue; VkQueue presentQueue; - - VDeleter swapChain{device, vkDestroySwapchainKHR}; + + VkSwapchainKHR swapChain; std::vector swapChainImages; VkFormat swapChainImageFormat; VkExtent2D swapChainExtent; - std::vector> swapChainImageViews; - std::vector> swapChainFramebuffers; - - VDeleter renderPass{device, vkDestroyRenderPass}; - VDeleter pipelineLayout{device, vkDestroyPipelineLayout}; - VDeleter graphicsPipeline{device, vkDestroyPipeline}; - - VDeleter commandPool{device, vkDestroyCommandPool}; + std::vector swapChainImageViews; + std::vector swapChainFramebuffers; + + VkRenderPass renderPass; + VkPipelineLayout pipelineLayout; + VkPipeline graphicsPipeline; + + VkCommandPool commandPool; std::vector commandBuffers; - VDeleter imageAvailableSemaphore{device, vkDestroySemaphore}; - VDeleter renderFinishedSemaphore{device, vkDestroySemaphore}; + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + std::vector inFlightFences; + size_t currentFrame = 0; + + bool framebufferResized = false; void initWindow() { glfwInit(); @@ -160,9 +109,13 @@ class HelloTriangleApplication { glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetWindowSizeCallback(window, HelloTriangleApplication::onWindowResized); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; } void initVulkan() { @@ -178,7 +131,7 @@ class HelloTriangleApplication { createFramebuffers(); createCommandPool(); createCommandBuffers(); - createSemaphores(); + createSyncObjects(); } void mainLoop() { @@ -188,22 +141,62 @@ class HelloTriangleApplication { } vkDeviceWaitIdle(device); + } - glfwDestroyWindow(window); + void cleanupSwapChain() { + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } - glfwTerminate(); + vkFreeCommandBuffers(device, commandPool, static_cast(commandBuffers.size()), commandBuffers.data()); + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); } - static void onWindowResized(GLFWwindow* window, int width, int height) { - if (width == 0 || height == 0) return; - - HelloTriangleApplication* app = reinterpret_cast(glfwGetWindowUserPointer(window)); - app->recreateSwapChain(); + void cleanup() { + cleanupSwapChain(); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + vkDestroyFence(device, inFlightFences[i], nullptr); + } + + vkDestroyCommandPool(device, commandPool, nullptr); + + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugReportCallbackEXT(instance, callback, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); } void recreateSwapChain() { + int width = 0, height = 0; + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + vkDeviceWaitIdle(device); + cleanupSwapChain(); + createSwapChain(); createImageViews(); createRenderPass(); @@ -230,17 +223,17 @@ class HelloTriangleApplication { createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } @@ -253,13 +246,13 @@ class HelloTriangleApplication { createInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT; createInfo.pfnCallback = debugCallback; - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { + if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, &callback) != VK_SUCCESS) { throw std::runtime_error("failed to set up debug callback!"); } } void createSurface() { - if (glfwCreateWindowSurface(instance, window, nullptr, surface.replace()) != VK_SUCCESS) { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { throw std::runtime_error("failed to create window surface!"); } } @@ -308,22 +301,22 @@ class HelloTriangleApplication { VkDeviceCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); createInfo.pQueueCreateInfos = queueCreateInfos.data(); - createInfo.queueCreateInfoCount = (uint32_t) queueCreateInfos.size(); createInfo.pEnabledFeatures = &deviceFeatures; - createInfo.enabledExtensionCount = deviceExtensions.size(); + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); createInfo.ppEnabledExtensionNames = deviceExtensions.data(); if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateDevice(physicalDevice, &createInfo, nullptr, device.replace()) != VK_SUCCESS) { + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { throw std::runtime_error("failed to create logical device!"); } @@ -370,16 +363,10 @@ class HelloTriangleApplication { createInfo.presentMode = presentMode; createInfo.clipped = VK_TRUE; - VkSwapchainKHR oldSwapChain = swapChain; - createInfo.oldSwapchain = oldSwapChain; - - VkSwapchainKHR newSwapChain; - if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &newSwapChain) != VK_SUCCESS) { + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { throw std::runtime_error("failed to create swap chain!"); } - swapChain = newSwapChain; - vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); swapChainImages.resize(imageCount); vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); @@ -389,9 +376,9 @@ class HelloTriangleApplication { } void createImageViews() { - swapChainImageViews.resize(swapChainImages.size(), VDeleter{device, vkDestroyImageView}); + swapChainImageViews.resize(swapChainImages.size()); - for (uint32_t i = 0; i < swapChainImages.size(); i++) { + for (size_t i = 0; i < swapChainImages.size(); i++) { VkImageViewCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; createInfo.image = swapChainImages[i]; @@ -407,7 +394,7 @@ class HelloTriangleApplication { createInfo.subresourceRange.baseArrayLayer = 0; createInfo.subresourceRange.layerCount = 1; - if (vkCreateImageView(device, &createInfo, nullptr, swapChainImageViews[i].replace()) != VK_SUCCESS) { + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { throw std::runtime_error("failed to create image views!"); } } @@ -450,7 +437,7 @@ class HelloTriangleApplication { renderPassInfo.dependencyCount = 1; renderPassInfo.pDependencies = &dependency; - if (vkCreateRenderPass(device, &renderPassInfo, nullptr, renderPass.replace()) != VK_SUCCESS) { + if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { throw std::runtime_error("failed to create render pass!"); } } @@ -459,10 +446,8 @@ class HelloTriangleApplication { auto vertShaderCode = readFile("shaders/vert.spv"); auto fragShaderCode = readFile("shaders/frag.spv"); - VDeleter vertShaderModule{device, vkDestroyShaderModule}; - VDeleter fragShaderModule{device, vkDestroyShaderModule}; - createShaderModule(vertShaderCode, vertShaderModule); - createShaderModule(fragShaderCode, fragShaderModule); + VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); VkPipelineShaderStageCreateInfo vertShaderStageInfo = {}; vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; @@ -542,7 +527,7 @@ class HelloTriangleApplication { pipelineLayoutInfo.setLayoutCount = 0; pipelineLayoutInfo.pushConstantRangeCount = 0; - if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, pipelineLayout.replace()) != VK_SUCCESS) { + if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { throw std::runtime_error("failed to create pipeline layout!"); } @@ -561,13 +546,16 @@ class HelloTriangleApplication { pipelineInfo.subpass = 0; pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; - if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, graphicsPipeline.replace()) != VK_SUCCESS) { + if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { throw std::runtime_error("failed to create graphics pipeline!"); } + + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); } void createFramebuffers() { - swapChainFramebuffers.resize(swapChainImageViews.size(), VDeleter{device, vkDestroyFramebuffer}); + swapChainFramebuffers.resize(swapChainImageViews.size()); for (size_t i = 0; i < swapChainImageViews.size(); i++) { VkImageView attachments[] = { @@ -583,7 +571,7 @@ class HelloTriangleApplication { framebufferInfo.height = swapChainExtent.height; framebufferInfo.layers = 1; - if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, swapChainFramebuffers[i].replace()) != VK_SUCCESS) { + if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { throw std::runtime_error("failed to create framebuffer!"); } } @@ -596,16 +584,12 @@ class HelloTriangleApplication { poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily; - if (vkCreateCommandPool(device, &poolInfo, nullptr, commandPool.replace()) != VK_SUCCESS) { + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { throw std::runtime_error("failed to create command pool!"); } } void createCommandBuffers() { - if (commandBuffers.size() > 0) { - vkFreeCommandBuffers(device, commandPool, commandBuffers.size(), commandBuffers.data()); - } - commandBuffers.resize(swapChainFramebuffers.size()); VkCommandBufferAllocateInfo allocInfo = {}; @@ -623,7 +607,9 @@ class HelloTriangleApplication { beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; beginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT; - vkBeginCommandBuffer(commandBuffers[i], &beginInfo); + if (vkBeginCommandBuffer(commandBuffers[i], &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } VkRenderPassBeginInfo renderPassInfo = {}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; @@ -649,21 +635,33 @@ class HelloTriangleApplication { } } } - - void createSemaphores() { + + void createSyncObjects() { + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + inFlightFences.resize(MAX_FRAMES_IN_FLIGHT); + VkSemaphoreCreateInfo semaphoreInfo = {}; semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; - if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, imageAvailableSemaphore.replace()) != VK_SUCCESS || - vkCreateSemaphore(device, &semaphoreInfo, nullptr, renderFinishedSemaphore.replace()) != VK_SUCCESS) { + VkFenceCreateInfo fenceInfo = {}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; - throw std::runtime_error("failed to create semaphores!"); + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS || + vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create synchronization objects for a frame!"); + } } } void drawFrame() { + vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, std::numeric_limits::max()); + uint32_t imageIndex; - VkResult result = vkAcquireNextImageKHR(device, swapChain, std::numeric_limits::max(), imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex); + VkResult result = vkAcquireNextImageKHR(device, swapChain, std::numeric_limits::max(), imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); if (result == VK_ERROR_OUT_OF_DATE_KHR) { recreateSwapChain(); @@ -675,7 +673,7 @@ class HelloTriangleApplication { VkSubmitInfo submitInfo = {}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; - VkSemaphore waitSemaphores[] = {imageAvailableSemaphore}; + VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[currentFrame]}; VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; submitInfo.waitSemaphoreCount = 1; submitInfo.pWaitSemaphores = waitSemaphores; @@ -684,11 +682,13 @@ class HelloTriangleApplication { submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &commandBuffers[imageIndex]; - VkSemaphore signalSemaphores[] = {renderFinishedSemaphore}; + VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[currentFrame]}; submitInfo.signalSemaphoreCount = 1; submitInfo.pSignalSemaphores = signalSemaphores; - if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) { throw std::runtime_error("failed to submit draw command buffer!"); } @@ -706,26 +706,28 @@ class HelloTriangleApplication { result = vkQueuePresentKHR(presentQueue, &presentInfo); - if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { + framebufferResized = false; recreateSwapChain(); } else if (result != VK_SUCCESS) { throw std::runtime_error("failed to present swap chain image!"); } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; } - void createShaderModule(const std::vector& code, VDeleter& shaderModule) { + VkShaderModule createShaderModule(const std::vector& code) { VkShaderModuleCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); - std::vector codeAligned(code.size() / 4 + 1); - memcpy(codeAligned.data(), code.data(), code.size()); - - createInfo.pCode = codeAligned.data(); - - if (vkCreateShaderModule(device, &createInfo, nullptr, shaderModule.replace()) != VK_SUCCESS) { + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { throw std::runtime_error("failed to create shader module!"); } + + return shaderModule; } VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { @@ -761,9 +763,12 @@ class HelloTriangleApplication { return capabilities.currentExtent; } else { int width, height; - glfwGetWindowSize(window, &width, &height); + glfwGetFramebufferSize(window, &width, &height); - VkExtent2D actualExtent = {width, height}; + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; actualExtent.width = std::max(capabilities.minImageExtent.width, std::min(capabilities.maxImageExtent.width, actualExtent.width)); actualExtent.height = std::max(capabilities.minImageExtent.height, std::min(capabilities.maxImageExtent.height, actualExtent.height)); @@ -859,15 +864,11 @@ class HelloTriangleApplication { } std::vector getRequiredExtensions() { - std::vector extensions; - - unsigned int glfwExtensionCount = 0; + uint32_t glfwExtensionCount = 0; const char** glfwExtensions; glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - for (unsigned int i = 0; i < glfwExtensionCount; i++) { - extensions.push_back(glfwExtensions[i]); - } + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); if (enableValidationLayers) { extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); @@ -931,10 +932,10 @@ int main() { try { app.run(); - } catch (const std::runtime_error& e) { + } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; -} \ No newline at end of file +} diff --git a/code/shader_ubo.frag b/code/17_shader_vertexbuffer.frag similarity index 100% rename from code/shader_ubo.frag rename to code/17_shader_vertexbuffer.frag diff --git a/code/shader_vertexbuffer.vert b/code/17_shader_vertexbuffer.vert similarity index 100% rename from code/shader_vertexbuffer.vert rename to code/17_shader_vertexbuffer.vert diff --git a/code/vertex_input.cpp b/code/17_vertex_input.cpp similarity index 83% rename from code/vertex_input.cpp rename to code/17_vertex_input.cpp index c0725821..b8a840f9 100644 --- a/code/vertex_input.cpp +++ b/code/17_vertex_input.cpp @@ -4,18 +4,20 @@ #include #include -#include -#include #include +#include #include #include #include +#include #include #include const int WIDTH = 800; const int HEIGHT = 600; +const int MAX_FRAMES_IN_FLIGHT = 2; + const std::vector validationLayers = { "VK_LAYER_LUNARG_standard_validation" }; @@ -46,64 +48,6 @@ void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT } } -template -class VDeleter { -public: - VDeleter() : VDeleter([](T, VkAllocationCallbacks*) {}) {} - - VDeleter(std::function deletef) { - this->deleter = [=](T obj) { deletef(obj, nullptr); }; - } - - VDeleter(const VDeleter& instance, std::function deletef) { - this->deleter = [&instance, deletef](T obj) { deletef(instance, obj, nullptr); }; - } - - VDeleter(const VDeleter& device, std::function deletef) { - this->deleter = [&device, deletef](T obj) { deletef(device, obj, nullptr); }; - } - - ~VDeleter() { - cleanup(); - } - - const T* operator &() const { - return &object; - } - - T* replace() { - cleanup(); - return &object; - } - - operator T() const { - return object; - } - - void operator=(T rhs) { - if (rhs != object) { - cleanup(); - object = rhs; - } - } - - template - bool operator==(V rhs) { - return object == T(rhs); - } - -private: - T object{VK_NULL_HANDLE}; - std::function deleter; - - void cleanup() { - if (object != VK_NULL_HANDLE) { - deleter(object); - } - object = VK_NULL_HANDLE; - } -}; - struct QueueFamilyIndices { int graphicsFamily = -1; int presentFamily = -1; @@ -161,37 +105,42 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugReportCallbackEXT callback; + VkSurfaceKHR surface; VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; - VDeleter device{vkDestroyDevice}; + VkDevice device; VkQueue graphicsQueue; VkQueue presentQueue; - - VDeleter swapChain{device, vkDestroySwapchainKHR}; + + VkSwapchainKHR swapChain; std::vector swapChainImages; VkFormat swapChainImageFormat; VkExtent2D swapChainExtent; - std::vector> swapChainImageViews; - std::vector> swapChainFramebuffers; - - VDeleter renderPass{device, vkDestroyRenderPass}; - VDeleter pipelineLayout{device, vkDestroyPipelineLayout}; - VDeleter graphicsPipeline{device, vkDestroyPipeline}; - - VDeleter commandPool{device, vkDestroyCommandPool}; + std::vector swapChainImageViews; + std::vector swapChainFramebuffers; + + VkRenderPass renderPass; + VkPipelineLayout pipelineLayout; + VkPipeline graphicsPipeline; + + VkCommandPool commandPool; std::vector commandBuffers; - VDeleter imageAvailableSemaphore{device, vkDestroySemaphore}; - VDeleter renderFinishedSemaphore{device, vkDestroySemaphore}; + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + std::vector inFlightFences; + size_t currentFrame = 0; + + bool framebufferResized = false; void initWindow() { glfwInit(); @@ -199,9 +148,13 @@ class HelloTriangleApplication { glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetWindowSizeCallback(window, HelloTriangleApplication::onWindowResized); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; } void initVulkan() { @@ -217,7 +170,7 @@ class HelloTriangleApplication { createFramebuffers(); createCommandPool(); createCommandBuffers(); - createSemaphores(); + createSyncObjects(); } void mainLoop() { @@ -227,22 +180,62 @@ class HelloTriangleApplication { } vkDeviceWaitIdle(device); + } - glfwDestroyWindow(window); + void cleanupSwapChain() { + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } - glfwTerminate(); + vkFreeCommandBuffers(device, commandPool, static_cast(commandBuffers.size()), commandBuffers.data()); + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); } - static void onWindowResized(GLFWwindow* window, int width, int height) { - if (width == 0 || height == 0) return; - - HelloTriangleApplication* app = reinterpret_cast(glfwGetWindowUserPointer(window)); - app->recreateSwapChain(); + void cleanup() { + cleanupSwapChain(); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + vkDestroyFence(device, inFlightFences[i], nullptr); + } + + vkDestroyCommandPool(device, commandPool, nullptr); + + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugReportCallbackEXT(instance, callback, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); } void recreateSwapChain() { + int width = 0, height = 0; + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + vkDeviceWaitIdle(device); + cleanupSwapChain(); + createSwapChain(); createImageViews(); createRenderPass(); @@ -269,17 +262,17 @@ class HelloTriangleApplication { createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } @@ -292,13 +285,13 @@ class HelloTriangleApplication { createInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT; createInfo.pfnCallback = debugCallback; - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { + if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, &callback) != VK_SUCCESS) { throw std::runtime_error("failed to set up debug callback!"); } } void createSurface() { - if (glfwCreateWindowSurface(instance, window, nullptr, surface.replace()) != VK_SUCCESS) { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { throw std::runtime_error("failed to create window surface!"); } } @@ -347,22 +340,22 @@ class HelloTriangleApplication { VkDeviceCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); createInfo.pQueueCreateInfos = queueCreateInfos.data(); - createInfo.queueCreateInfoCount = (uint32_t) queueCreateInfos.size(); createInfo.pEnabledFeatures = &deviceFeatures; - createInfo.enabledExtensionCount = deviceExtensions.size(); + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); createInfo.ppEnabledExtensionNames = deviceExtensions.data(); if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateDevice(physicalDevice, &createInfo, nullptr, device.replace()) != VK_SUCCESS) { + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { throw std::runtime_error("failed to create logical device!"); } @@ -409,16 +402,10 @@ class HelloTriangleApplication { createInfo.presentMode = presentMode; createInfo.clipped = VK_TRUE; - VkSwapchainKHR oldSwapChain = swapChain; - createInfo.oldSwapchain = oldSwapChain; - - VkSwapchainKHR newSwapChain; - if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &newSwapChain) != VK_SUCCESS) { + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { throw std::runtime_error("failed to create swap chain!"); } - swapChain = newSwapChain; - vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); swapChainImages.resize(imageCount); vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); @@ -428,9 +415,9 @@ class HelloTriangleApplication { } void createImageViews() { - swapChainImageViews.resize(swapChainImages.size(), VDeleter{device, vkDestroyImageView}); + swapChainImageViews.resize(swapChainImages.size()); - for (uint32_t i = 0; i < swapChainImages.size(); i++) { + for (size_t i = 0; i < swapChainImages.size(); i++) { VkImageViewCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; createInfo.image = swapChainImages[i]; @@ -446,7 +433,7 @@ class HelloTriangleApplication { createInfo.subresourceRange.baseArrayLayer = 0; createInfo.subresourceRange.layerCount = 1; - if (vkCreateImageView(device, &createInfo, nullptr, swapChainImageViews[i].replace()) != VK_SUCCESS) { + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { throw std::runtime_error("failed to create image views!"); } } @@ -489,7 +476,7 @@ class HelloTriangleApplication { renderPassInfo.dependencyCount = 1; renderPassInfo.pDependencies = &dependency; - if (vkCreateRenderPass(device, &renderPassInfo, nullptr, renderPass.replace()) != VK_SUCCESS) { + if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { throw std::runtime_error("failed to create render pass!"); } } @@ -498,10 +485,8 @@ class HelloTriangleApplication { auto vertShaderCode = readFile("shaders/vert.spv"); auto fragShaderCode = readFile("shaders/frag.spv"); - VDeleter vertShaderModule{device, vkDestroyShaderModule}; - VDeleter fragShaderModule{device, vkDestroyShaderModule}; - createShaderModule(vertShaderCode, vertShaderModule); - createShaderModule(fragShaderCode, fragShaderModule); + VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); VkPipelineShaderStageCreateInfo vertShaderStageInfo = {}; vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; @@ -524,7 +509,7 @@ class HelloTriangleApplication { auto attributeDescriptions = Vertex::getAttributeDescriptions(); vertexInputInfo.vertexBindingDescriptionCount = 1; - vertexInputInfo.vertexAttributeDescriptionCount = attributeDescriptions.size(); + vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()); vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); @@ -587,7 +572,7 @@ class HelloTriangleApplication { pipelineLayoutInfo.setLayoutCount = 0; pipelineLayoutInfo.pushConstantRangeCount = 0; - if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, pipelineLayout.replace()) != VK_SUCCESS) { + if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { throw std::runtime_error("failed to create pipeline layout!"); } @@ -606,13 +591,16 @@ class HelloTriangleApplication { pipelineInfo.subpass = 0; pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; - if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, graphicsPipeline.replace()) != VK_SUCCESS) { + if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { throw std::runtime_error("failed to create graphics pipeline!"); } + + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); } void createFramebuffers() { - swapChainFramebuffers.resize(swapChainImageViews.size(), VDeleter{device, vkDestroyFramebuffer}); + swapChainFramebuffers.resize(swapChainImageViews.size()); for (size_t i = 0; i < swapChainImageViews.size(); i++) { VkImageView attachments[] = { @@ -628,7 +616,7 @@ class HelloTriangleApplication { framebufferInfo.height = swapChainExtent.height; framebufferInfo.layers = 1; - if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, swapChainFramebuffers[i].replace()) != VK_SUCCESS) { + if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { throw std::runtime_error("failed to create framebuffer!"); } } @@ -641,16 +629,12 @@ class HelloTriangleApplication { poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily; - if (vkCreateCommandPool(device, &poolInfo, nullptr, commandPool.replace()) != VK_SUCCESS) { + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { throw std::runtime_error("failed to create command pool!"); } } void createCommandBuffers() { - if (commandBuffers.size() > 0) { - vkFreeCommandBuffers(device, commandPool, commandBuffers.size(), commandBuffers.data()); - } - commandBuffers.resize(swapChainFramebuffers.size()); VkCommandBufferAllocateInfo allocInfo = {}; @@ -668,7 +652,9 @@ class HelloTriangleApplication { beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; beginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT; - vkBeginCommandBuffer(commandBuffers[i], &beginInfo); + if (vkBeginCommandBuffer(commandBuffers[i], &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } VkRenderPassBeginInfo renderPassInfo = {}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; @@ -695,20 +681,32 @@ class HelloTriangleApplication { } } - void createSemaphores() { + void createSyncObjects() { + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + inFlightFences.resize(MAX_FRAMES_IN_FLIGHT); + VkSemaphoreCreateInfo semaphoreInfo = {}; semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; - if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, imageAvailableSemaphore.replace()) != VK_SUCCESS || - vkCreateSemaphore(device, &semaphoreInfo, nullptr, renderFinishedSemaphore.replace()) != VK_SUCCESS) { + VkFenceCreateInfo fenceInfo = {}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; - throw std::runtime_error("failed to create semaphores!"); + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS || + vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create synchronization objects for a frame!"); + } } } void drawFrame() { + vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, std::numeric_limits::max()); + uint32_t imageIndex; - VkResult result = vkAcquireNextImageKHR(device, swapChain, std::numeric_limits::max(), imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex); + VkResult result = vkAcquireNextImageKHR(device, swapChain, std::numeric_limits::max(), imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); if (result == VK_ERROR_OUT_OF_DATE_KHR) { recreateSwapChain(); @@ -720,7 +718,7 @@ class HelloTriangleApplication { VkSubmitInfo submitInfo = {}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; - VkSemaphore waitSemaphores[] = {imageAvailableSemaphore}; + VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[currentFrame]}; VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; submitInfo.waitSemaphoreCount = 1; submitInfo.pWaitSemaphores = waitSemaphores; @@ -729,11 +727,13 @@ class HelloTriangleApplication { submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &commandBuffers[imageIndex]; - VkSemaphore signalSemaphores[] = {renderFinishedSemaphore}; + VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[currentFrame]}; submitInfo.signalSemaphoreCount = 1; submitInfo.pSignalSemaphores = signalSemaphores; - if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) { throw std::runtime_error("failed to submit draw command buffer!"); } @@ -751,26 +751,28 @@ class HelloTriangleApplication { result = vkQueuePresentKHR(presentQueue, &presentInfo); - if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { + framebufferResized = false; recreateSwapChain(); } else if (result != VK_SUCCESS) { throw std::runtime_error("failed to present swap chain image!"); } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; } - void createShaderModule(const std::vector& code, VDeleter& shaderModule) { + VkShaderModule createShaderModule(const std::vector& code) { VkShaderModuleCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); - std::vector codeAligned(code.size() / 4 + 1); - memcpy(codeAligned.data(), code.data(), code.size()); - - createInfo.pCode = codeAligned.data(); - - if (vkCreateShaderModule(device, &createInfo, nullptr, shaderModule.replace()) != VK_SUCCESS) { + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { throw std::runtime_error("failed to create shader module!"); } + + return shaderModule; } VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { @@ -806,9 +808,12 @@ class HelloTriangleApplication { return capabilities.currentExtent; } else { int width, height; - glfwGetWindowSize(window, &width, &height); + glfwGetFramebufferSize(window, &width, &height); - VkExtent2D actualExtent = {width, height}; + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; actualExtent.width = std::max(capabilities.minImageExtent.width, std::min(capabilities.maxImageExtent.width, actualExtent.width)); actualExtent.height = std::max(capabilities.minImageExtent.height, std::min(capabilities.maxImageExtent.height, actualExtent.height)); @@ -904,15 +909,11 @@ class HelloTriangleApplication { } std::vector getRequiredExtensions() { - std::vector extensions; - - unsigned int glfwExtensionCount = 0; + uint32_t glfwExtensionCount = 0; const char** glfwExtensions; glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - for (unsigned int i = 0; i < glfwExtensionCount; i++) { - extensions.push_back(glfwExtensions[i]); - } + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); if (enableValidationLayers) { extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); @@ -976,10 +977,10 @@ int main() { try { app.run(); - } catch (const std::runtime_error& e) { + } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; -} \ No newline at end of file +} diff --git a/code/vertex_buffer.cpp b/code/18_vertex_buffer.cpp similarity index 83% rename from code/vertex_buffer.cpp rename to code/18_vertex_buffer.cpp index b3253243..525eaca5 100644 --- a/code/vertex_buffer.cpp +++ b/code/18_vertex_buffer.cpp @@ -4,18 +4,20 @@ #include #include -#include -#include #include +#include #include #include #include +#include #include #include const int WIDTH = 800; const int HEIGHT = 600; +const int MAX_FRAMES_IN_FLIGHT = 2; + const std::vector validationLayers = { "VK_LAYER_LUNARG_standard_validation" }; @@ -46,64 +48,6 @@ void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT } } -template -class VDeleter { -public: - VDeleter() : VDeleter([](T, VkAllocationCallbacks*) {}) {} - - VDeleter(std::function deletef) { - this->deleter = [=](T obj) { deletef(obj, nullptr); }; - } - - VDeleter(const VDeleter& instance, std::function deletef) { - this->deleter = [&instance, deletef](T obj) { deletef(instance, obj, nullptr); }; - } - - VDeleter(const VDeleter& device, std::function deletef) { - this->deleter = [&device, deletef](T obj) { deletef(device, obj, nullptr); }; - } - - ~VDeleter() { - cleanup(); - } - - const T* operator &() const { - return &object; - } - - T* replace() { - cleanup(); - return &object; - } - - operator T() const { - return object; - } - - void operator=(T rhs) { - if (rhs != object) { - cleanup(); - object = rhs; - } - } - - template - bool operator==(V rhs) { - return object == T(rhs); - } - -private: - T object{VK_NULL_HANDLE}; - std::function deleter; - - void cleanup() { - if (object != VK_NULL_HANDLE) { - deleter(object); - } - object = VK_NULL_HANDLE; - } -}; - struct QueueFamilyIndices { int graphicsFamily = -1; int presentFamily = -1; @@ -161,41 +105,46 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugReportCallbackEXT callback; + VkSurfaceKHR surface; VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; - VDeleter device{vkDestroyDevice}; + VkDevice device; VkQueue graphicsQueue; VkQueue presentQueue; - - VDeleter swapChain{device, vkDestroySwapchainKHR}; + + VkSwapchainKHR swapChain; std::vector swapChainImages; VkFormat swapChainImageFormat; VkExtent2D swapChainExtent; - std::vector> swapChainImageViews; - std::vector> swapChainFramebuffers; - - VDeleter renderPass{device, vkDestroyRenderPass}; - VDeleter pipelineLayout{device, vkDestroyPipelineLayout}; - VDeleter graphicsPipeline{device, vkDestroyPipeline}; - - VDeleter commandPool{device, vkDestroyCommandPool}; - - VDeleter vertexBuffer{device, vkDestroyBuffer}; - VDeleter vertexBufferMemory{device, vkFreeMemory}; - + std::vector swapChainImageViews; + std::vector swapChainFramebuffers; + + VkRenderPass renderPass; + VkPipelineLayout pipelineLayout; + VkPipeline graphicsPipeline; + + VkCommandPool commandPool; + + VkBuffer vertexBuffer; + VkDeviceMemory vertexBufferMemory; + std::vector commandBuffers; - VDeleter imageAvailableSemaphore{device, vkDestroySemaphore}; - VDeleter renderFinishedSemaphore{device, vkDestroySemaphore}; + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + std::vector inFlightFences; + size_t currentFrame = 0; + + bool framebufferResized = false; void initWindow() { glfwInit(); @@ -203,9 +152,13 @@ class HelloTriangleApplication { glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetWindowSizeCallback(window, HelloTriangleApplication::onWindowResized); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; } void initVulkan() { @@ -222,7 +175,7 @@ class HelloTriangleApplication { createCommandPool(); createVertexBuffer(); createCommandBuffers(); - createSemaphores(); + createSyncObjects(); } void mainLoop() { @@ -232,22 +185,65 @@ class HelloTriangleApplication { } vkDeviceWaitIdle(device); + } - glfwDestroyWindow(window); + void cleanupSwapChain() { + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } - glfwTerminate(); + vkFreeCommandBuffers(device, commandPool, static_cast(commandBuffers.size()), commandBuffers.data()); + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); } - static void onWindowResized(GLFWwindow* window, int width, int height) { - if (width == 0 || height == 0) return; - - HelloTriangleApplication* app = reinterpret_cast(glfwGetWindowUserPointer(window)); - app->recreateSwapChain(); + void cleanup() { + cleanupSwapChain(); + + vkDestroyBuffer(device, vertexBuffer, nullptr); + vkFreeMemory(device, vertexBufferMemory, nullptr); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + vkDestroyFence(device, inFlightFences[i], nullptr); + } + + vkDestroyCommandPool(device, commandPool, nullptr); + + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugReportCallbackEXT(instance, callback, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); } void recreateSwapChain() { + int width = 0, height = 0; + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + vkDeviceWaitIdle(device); + cleanupSwapChain(); + createSwapChain(); createImageViews(); createRenderPass(); @@ -274,17 +270,17 @@ class HelloTriangleApplication { createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } @@ -297,13 +293,13 @@ class HelloTriangleApplication { createInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT; createInfo.pfnCallback = debugCallback; - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { + if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, &callback) != VK_SUCCESS) { throw std::runtime_error("failed to set up debug callback!"); } } void createSurface() { - if (glfwCreateWindowSurface(instance, window, nullptr, surface.replace()) != VK_SUCCESS) { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { throw std::runtime_error("failed to create window surface!"); } } @@ -352,22 +348,22 @@ class HelloTriangleApplication { VkDeviceCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); createInfo.pQueueCreateInfos = queueCreateInfos.data(); - createInfo.queueCreateInfoCount = (uint32_t) queueCreateInfos.size(); createInfo.pEnabledFeatures = &deviceFeatures; - createInfo.enabledExtensionCount = deviceExtensions.size(); + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); createInfo.ppEnabledExtensionNames = deviceExtensions.data(); if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateDevice(physicalDevice, &createInfo, nullptr, device.replace()) != VK_SUCCESS) { + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { throw std::runtime_error("failed to create logical device!"); } @@ -414,16 +410,10 @@ class HelloTriangleApplication { createInfo.presentMode = presentMode; createInfo.clipped = VK_TRUE; - VkSwapchainKHR oldSwapChain = swapChain; - createInfo.oldSwapchain = oldSwapChain; - - VkSwapchainKHR newSwapChain; - if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &newSwapChain) != VK_SUCCESS) { + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { throw std::runtime_error("failed to create swap chain!"); } - swapChain = newSwapChain; - vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); swapChainImages.resize(imageCount); vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); @@ -433,9 +423,9 @@ class HelloTriangleApplication { } void createImageViews() { - swapChainImageViews.resize(swapChainImages.size(), VDeleter{device, vkDestroyImageView}); + swapChainImageViews.resize(swapChainImages.size()); - for (uint32_t i = 0; i < swapChainImages.size(); i++) { + for (size_t i = 0; i < swapChainImages.size(); i++) { VkImageViewCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; createInfo.image = swapChainImages[i]; @@ -451,7 +441,7 @@ class HelloTriangleApplication { createInfo.subresourceRange.baseArrayLayer = 0; createInfo.subresourceRange.layerCount = 1; - if (vkCreateImageView(device, &createInfo, nullptr, swapChainImageViews[i].replace()) != VK_SUCCESS) { + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { throw std::runtime_error("failed to create image views!"); } } @@ -494,7 +484,7 @@ class HelloTriangleApplication { renderPassInfo.dependencyCount = 1; renderPassInfo.pDependencies = &dependency; - if (vkCreateRenderPass(device, &renderPassInfo, nullptr, renderPass.replace()) != VK_SUCCESS) { + if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { throw std::runtime_error("failed to create render pass!"); } } @@ -503,10 +493,8 @@ class HelloTriangleApplication { auto vertShaderCode = readFile("shaders/vert.spv"); auto fragShaderCode = readFile("shaders/frag.spv"); - VDeleter vertShaderModule{device, vkDestroyShaderModule}; - VDeleter fragShaderModule{device, vkDestroyShaderModule}; - createShaderModule(vertShaderCode, vertShaderModule); - createShaderModule(fragShaderCode, fragShaderModule); + VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); VkPipelineShaderStageCreateInfo vertShaderStageInfo = {}; vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; @@ -529,7 +517,7 @@ class HelloTriangleApplication { auto attributeDescriptions = Vertex::getAttributeDescriptions(); vertexInputInfo.vertexBindingDescriptionCount = 1; - vertexInputInfo.vertexAttributeDescriptionCount = attributeDescriptions.size(); + vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()); vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); @@ -592,7 +580,7 @@ class HelloTriangleApplication { pipelineLayoutInfo.setLayoutCount = 0; pipelineLayoutInfo.pushConstantRangeCount = 0; - if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, pipelineLayout.replace()) != VK_SUCCESS) { + if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { throw std::runtime_error("failed to create pipeline layout!"); } @@ -611,13 +599,16 @@ class HelloTriangleApplication { pipelineInfo.subpass = 0; pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; - if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, graphicsPipeline.replace()) != VK_SUCCESS) { + if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { throw std::runtime_error("failed to create graphics pipeline!"); } + + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); } void createFramebuffers() { - swapChainFramebuffers.resize(swapChainImageViews.size(), VDeleter{device, vkDestroyFramebuffer}); + swapChainFramebuffers.resize(swapChainImageViews.size()); for (size_t i = 0; i < swapChainImageViews.size(); i++) { VkImageView attachments[] = { @@ -633,7 +624,7 @@ class HelloTriangleApplication { framebufferInfo.height = swapChainExtent.height; framebufferInfo.layers = 1; - if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, swapChainFramebuffers[i].replace()) != VK_SUCCESS) { + if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { throw std::runtime_error("failed to create framebuffer!"); } } @@ -646,7 +637,7 @@ class HelloTriangleApplication { poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily; - if (vkCreateCommandPool(device, &poolInfo, nullptr, commandPool.replace()) != VK_SUCCESS) { + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { throw std::runtime_error("failed to create command pool!"); } } @@ -658,7 +649,7 @@ class HelloTriangleApplication { bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - if (vkCreateBuffer(device, &bufferInfo, nullptr, vertexBuffer.replace()) != VK_SUCCESS) { + if (vkCreateBuffer(device, &bufferInfo, nullptr, &vertexBuffer) != VK_SUCCESS) { throw std::runtime_error("failed to create vertex buffer!"); } @@ -670,7 +661,7 @@ class HelloTriangleApplication { allocInfo.allocationSize = memRequirements.size; allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); - if (vkAllocateMemory(device, &allocInfo, nullptr, vertexBufferMemory.replace()) != VK_SUCCESS) { + if (vkAllocateMemory(device, &allocInfo, nullptr, &vertexBufferMemory) != VK_SUCCESS) { throw std::runtime_error("failed to allocate vertex buffer memory!"); } @@ -696,10 +687,6 @@ class HelloTriangleApplication { } void createCommandBuffers() { - if (commandBuffers.size() > 0) { - vkFreeCommandBuffers(device, commandPool, commandBuffers.size(), commandBuffers.data()); - } - commandBuffers.resize(swapChainFramebuffers.size()); VkCommandBufferAllocateInfo allocInfo = {}; @@ -717,7 +704,9 @@ class HelloTriangleApplication { beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; beginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT; - vkBeginCommandBuffer(commandBuffers[i], &beginInfo); + if (vkBeginCommandBuffer(commandBuffers[i], &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } VkRenderPassBeginInfo renderPassInfo = {}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; @@ -738,7 +727,7 @@ class HelloTriangleApplication { VkDeviceSize offsets[] = {0}; vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets); - vkCmdDraw(commandBuffers[i], vertices.size(), 1, 0, 0); + vkCmdDraw(commandBuffers[i], static_cast(vertices.size()), 1, 0, 0); vkCmdEndRenderPass(commandBuffers[i]); @@ -748,20 +737,32 @@ class HelloTriangleApplication { } } - void createSemaphores() { + void createSyncObjects() { + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + inFlightFences.resize(MAX_FRAMES_IN_FLIGHT); + VkSemaphoreCreateInfo semaphoreInfo = {}; semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; - if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, imageAvailableSemaphore.replace()) != VK_SUCCESS || - vkCreateSemaphore(device, &semaphoreInfo, nullptr, renderFinishedSemaphore.replace()) != VK_SUCCESS) { + VkFenceCreateInfo fenceInfo = {}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; - throw std::runtime_error("failed to create semaphores!"); + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS || + vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create synchronization objects for a frame!"); + } } } void drawFrame() { + vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, std::numeric_limits::max()); + uint32_t imageIndex; - VkResult result = vkAcquireNextImageKHR(device, swapChain, std::numeric_limits::max(), imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex); + VkResult result = vkAcquireNextImageKHR(device, swapChain, std::numeric_limits::max(), imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); if (result == VK_ERROR_OUT_OF_DATE_KHR) { recreateSwapChain(); @@ -773,7 +774,7 @@ class HelloTriangleApplication { VkSubmitInfo submitInfo = {}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; - VkSemaphore waitSemaphores[] = {imageAvailableSemaphore}; + VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[currentFrame]}; VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; submitInfo.waitSemaphoreCount = 1; submitInfo.pWaitSemaphores = waitSemaphores; @@ -782,11 +783,13 @@ class HelloTriangleApplication { submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &commandBuffers[imageIndex]; - VkSemaphore signalSemaphores[] = {renderFinishedSemaphore}; + VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[currentFrame]}; submitInfo.signalSemaphoreCount = 1; submitInfo.pSignalSemaphores = signalSemaphores; - if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) { throw std::runtime_error("failed to submit draw command buffer!"); } @@ -804,26 +807,28 @@ class HelloTriangleApplication { result = vkQueuePresentKHR(presentQueue, &presentInfo); - if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { + framebufferResized = false; recreateSwapChain(); } else if (result != VK_SUCCESS) { throw std::runtime_error("failed to present swap chain image!"); } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; } - void createShaderModule(const std::vector& code, VDeleter& shaderModule) { + VkShaderModule createShaderModule(const std::vector& code) { VkShaderModuleCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); - std::vector codeAligned(code.size() / 4 + 1); - memcpy(codeAligned.data(), code.data(), code.size()); - - createInfo.pCode = codeAligned.data(); - - if (vkCreateShaderModule(device, &createInfo, nullptr, shaderModule.replace()) != VK_SUCCESS) { + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { throw std::runtime_error("failed to create shader module!"); } + + return shaderModule; } VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { @@ -859,9 +864,12 @@ class HelloTriangleApplication { return capabilities.currentExtent; } else { int width, height; - glfwGetWindowSize(window, &width, &height); + glfwGetFramebufferSize(window, &width, &height); - VkExtent2D actualExtent = {width, height}; + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; actualExtent.width = std::max(capabilities.minImageExtent.width, std::min(capabilities.maxImageExtent.width, actualExtent.width)); actualExtent.height = std::max(capabilities.minImageExtent.height, std::min(capabilities.maxImageExtent.height, actualExtent.height)); @@ -957,15 +965,11 @@ class HelloTriangleApplication { } std::vector getRequiredExtensions() { - std::vector extensions; - - unsigned int glfwExtensionCount = 0; + uint32_t glfwExtensionCount = 0; const char** glfwExtensions; glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - for (unsigned int i = 0; i < glfwExtensionCount; i++) { - extensions.push_back(glfwExtensions[i]); - } + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); if (enableValidationLayers) { extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); @@ -1029,10 +1033,10 @@ int main() { try { app.run(); - } catch (const std::runtime_error& e) { + } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; -} \ No newline at end of file +} diff --git a/code/staging_buffer.cpp b/code/19_staging_buffer.cpp similarity index 83% rename from code/staging_buffer.cpp rename to code/19_staging_buffer.cpp index 63ef25d5..b873c910 100644 --- a/code/staging_buffer.cpp +++ b/code/19_staging_buffer.cpp @@ -4,18 +4,20 @@ #include #include -#include -#include #include +#include #include #include #include +#include #include #include const int WIDTH = 800; const int HEIGHT = 600; +const int MAX_FRAMES_IN_FLIGHT = 2; + const std::vector validationLayers = { "VK_LAYER_LUNARG_standard_validation" }; @@ -46,64 +48,6 @@ void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT } } -template -class VDeleter { -public: - VDeleter() : VDeleter([](T, VkAllocationCallbacks*) {}) {} - - VDeleter(std::function deletef) { - this->deleter = [=](T obj) { deletef(obj, nullptr); }; - } - - VDeleter(const VDeleter& instance, std::function deletef) { - this->deleter = [&instance, deletef](T obj) { deletef(instance, obj, nullptr); }; - } - - VDeleter(const VDeleter& device, std::function deletef) { - this->deleter = [&device, deletef](T obj) { deletef(device, obj, nullptr); }; - } - - ~VDeleter() { - cleanup(); - } - - const T* operator &() const { - return &object; - } - - T* replace() { - cleanup(); - return &object; - } - - operator T() const { - return object; - } - - void operator=(T rhs) { - if (rhs != object) { - cleanup(); - object = rhs; - } - } - - template - bool operator==(V rhs) { - return object == T(rhs); - } - -private: - T object{VK_NULL_HANDLE}; - std::function deleter; - - void cleanup() { - if (object != VK_NULL_HANDLE) { - deleter(object); - } - object = VK_NULL_HANDLE; - } -}; - struct QueueFamilyIndices { int graphicsFamily = -1; int presentFamily = -1; @@ -161,41 +105,46 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugReportCallbackEXT callback; + VkSurfaceKHR surface; VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; - VDeleter device{vkDestroyDevice}; + VkDevice device; VkQueue graphicsQueue; VkQueue presentQueue; - - VDeleter swapChain{device, vkDestroySwapchainKHR}; + + VkSwapchainKHR swapChain; std::vector swapChainImages; VkFormat swapChainImageFormat; VkExtent2D swapChainExtent; - std::vector> swapChainImageViews; - std::vector> swapChainFramebuffers; - - VDeleter renderPass{device, vkDestroyRenderPass}; - VDeleter pipelineLayout{device, vkDestroyPipelineLayout}; - VDeleter graphicsPipeline{device, vkDestroyPipeline}; - - VDeleter commandPool{device, vkDestroyCommandPool}; - - VDeleter vertexBuffer{device, vkDestroyBuffer}; - VDeleter vertexBufferMemory{device, vkFreeMemory}; - + std::vector swapChainImageViews; + std::vector swapChainFramebuffers; + + VkRenderPass renderPass; + VkPipelineLayout pipelineLayout; + VkPipeline graphicsPipeline; + + VkCommandPool commandPool; + + VkBuffer vertexBuffer; + VkDeviceMemory vertexBufferMemory; + std::vector commandBuffers; - VDeleter imageAvailableSemaphore{device, vkDestroySemaphore}; - VDeleter renderFinishedSemaphore{device, vkDestroySemaphore}; + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + std::vector inFlightFences; + size_t currentFrame = 0; + + bool framebufferResized = false; void initWindow() { glfwInit(); @@ -203,9 +152,13 @@ class HelloTriangleApplication { glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetWindowSizeCallback(window, HelloTriangleApplication::onWindowResized); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; } void initVulkan() { @@ -222,7 +175,7 @@ class HelloTriangleApplication { createCommandPool(); createVertexBuffer(); createCommandBuffers(); - createSemaphores(); + createSyncObjects(); } void mainLoop() { @@ -232,22 +185,65 @@ class HelloTriangleApplication { } vkDeviceWaitIdle(device); + } - glfwDestroyWindow(window); + void cleanupSwapChain() { + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } - glfwTerminate(); + vkFreeCommandBuffers(device, commandPool, static_cast(commandBuffers.size()), commandBuffers.data()); + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); } - static void onWindowResized(GLFWwindow* window, int width, int height) { - if (width == 0 || height == 0) return; - - HelloTriangleApplication* app = reinterpret_cast(glfwGetWindowUserPointer(window)); - app->recreateSwapChain(); + void cleanup() { + cleanupSwapChain(); + + vkDestroyBuffer(device, vertexBuffer, nullptr); + vkFreeMemory(device, vertexBufferMemory, nullptr); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + vkDestroyFence(device, inFlightFences[i], nullptr); + } + + vkDestroyCommandPool(device, commandPool, nullptr); + + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugReportCallbackEXT(instance, callback, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); } void recreateSwapChain() { + int width = 0, height = 0; + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + vkDeviceWaitIdle(device); + cleanupSwapChain(); + createSwapChain(); createImageViews(); createRenderPass(); @@ -274,17 +270,17 @@ class HelloTriangleApplication { createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } @@ -297,13 +293,13 @@ class HelloTriangleApplication { createInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT; createInfo.pfnCallback = debugCallback; - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { + if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, &callback) != VK_SUCCESS) { throw std::runtime_error("failed to set up debug callback!"); } } void createSurface() { - if (glfwCreateWindowSurface(instance, window, nullptr, surface.replace()) != VK_SUCCESS) { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { throw std::runtime_error("failed to create window surface!"); } } @@ -352,22 +348,22 @@ class HelloTriangleApplication { VkDeviceCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); createInfo.pQueueCreateInfos = queueCreateInfos.data(); - createInfo.queueCreateInfoCount = (uint32_t) queueCreateInfos.size(); createInfo.pEnabledFeatures = &deviceFeatures; - createInfo.enabledExtensionCount = deviceExtensions.size(); + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); createInfo.ppEnabledExtensionNames = deviceExtensions.data(); if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateDevice(physicalDevice, &createInfo, nullptr, device.replace()) != VK_SUCCESS) { + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { throw std::runtime_error("failed to create logical device!"); } @@ -414,16 +410,10 @@ class HelloTriangleApplication { createInfo.presentMode = presentMode; createInfo.clipped = VK_TRUE; - VkSwapchainKHR oldSwapChain = swapChain; - createInfo.oldSwapchain = oldSwapChain; - - VkSwapchainKHR newSwapChain; - if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &newSwapChain) != VK_SUCCESS) { + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { throw std::runtime_error("failed to create swap chain!"); } - swapChain = newSwapChain; - vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); swapChainImages.resize(imageCount); vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); @@ -433,9 +423,9 @@ class HelloTriangleApplication { } void createImageViews() { - swapChainImageViews.resize(swapChainImages.size(), VDeleter{device, vkDestroyImageView}); + swapChainImageViews.resize(swapChainImages.size()); - for (uint32_t i = 0; i < swapChainImages.size(); i++) { + for (size_t i = 0; i < swapChainImages.size(); i++) { VkImageViewCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; createInfo.image = swapChainImages[i]; @@ -451,7 +441,7 @@ class HelloTriangleApplication { createInfo.subresourceRange.baseArrayLayer = 0; createInfo.subresourceRange.layerCount = 1; - if (vkCreateImageView(device, &createInfo, nullptr, swapChainImageViews[i].replace()) != VK_SUCCESS) { + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { throw std::runtime_error("failed to create image views!"); } } @@ -494,7 +484,7 @@ class HelloTriangleApplication { renderPassInfo.dependencyCount = 1; renderPassInfo.pDependencies = &dependency; - if (vkCreateRenderPass(device, &renderPassInfo, nullptr, renderPass.replace()) != VK_SUCCESS) { + if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { throw std::runtime_error("failed to create render pass!"); } } @@ -503,10 +493,8 @@ class HelloTriangleApplication { auto vertShaderCode = readFile("shaders/vert.spv"); auto fragShaderCode = readFile("shaders/frag.spv"); - VDeleter vertShaderModule{device, vkDestroyShaderModule}; - VDeleter fragShaderModule{device, vkDestroyShaderModule}; - createShaderModule(vertShaderCode, vertShaderModule); - createShaderModule(fragShaderCode, fragShaderModule); + VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); VkPipelineShaderStageCreateInfo vertShaderStageInfo = {}; vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; @@ -529,7 +517,7 @@ class HelloTriangleApplication { auto attributeDescriptions = Vertex::getAttributeDescriptions(); vertexInputInfo.vertexBindingDescriptionCount = 1; - vertexInputInfo.vertexAttributeDescriptionCount = attributeDescriptions.size(); + vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()); vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); @@ -592,7 +580,7 @@ class HelloTriangleApplication { pipelineLayoutInfo.setLayoutCount = 0; pipelineLayoutInfo.pushConstantRangeCount = 0; - if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, pipelineLayout.replace()) != VK_SUCCESS) { + if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { throw std::runtime_error("failed to create pipeline layout!"); } @@ -611,13 +599,16 @@ class HelloTriangleApplication { pipelineInfo.subpass = 0; pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; - if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, graphicsPipeline.replace()) != VK_SUCCESS) { + if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { throw std::runtime_error("failed to create graphics pipeline!"); } + + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); } void createFramebuffers() { - swapChainFramebuffers.resize(swapChainImageViews.size(), VDeleter{device, vkDestroyFramebuffer}); + swapChainFramebuffers.resize(swapChainImageViews.size()); for (size_t i = 0; i < swapChainImageViews.size(); i++) { VkImageView attachments[] = { @@ -633,7 +624,7 @@ class HelloTriangleApplication { framebufferInfo.height = swapChainExtent.height; framebufferInfo.layers = 1; - if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, swapChainFramebuffers[i].replace()) != VK_SUCCESS) { + if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { throw std::runtime_error("failed to create framebuffer!"); } } @@ -646,7 +637,7 @@ class HelloTriangleApplication { poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily; - if (vkCreateCommandPool(device, &poolInfo, nullptr, commandPool.replace()) != VK_SUCCESS) { + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { throw std::runtime_error("failed to create graphics command pool!"); } } @@ -654,8 +645,8 @@ class HelloTriangleApplication { void createVertexBuffer() { VkDeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); - VDeleter stagingBuffer{device, vkDestroyBuffer}; - VDeleter stagingBufferMemory{device, vkFreeMemory}; + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); void* data; @@ -666,16 +657,19 @@ class HelloTriangleApplication { createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, vertexBuffer, vertexBufferMemory); copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); } - void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VDeleter& buffer, VDeleter& bufferMemory) { + void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) { VkBufferCreateInfo bufferInfo = {}; bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; bufferInfo.size = size; bufferInfo.usage = usage; - bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - if (vkCreateBuffer(device, &bufferInfo, nullptr, buffer.replace()) != VK_SUCCESS) { + if (vkCreateBuffer(device, &bufferInfo, nullptr, &buffer) != VK_SUCCESS) { throw std::runtime_error("failed to create buffer!"); } @@ -687,7 +681,7 @@ class HelloTriangleApplication { allocInfo.allocationSize = memRequirements.size; allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); - if (vkAllocateMemory(device, &allocInfo, nullptr, bufferMemory.replace()) != VK_SUCCESS) { + if (vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) { throw std::runtime_error("failed to allocate buffer memory!"); } @@ -741,10 +735,6 @@ class HelloTriangleApplication { } void createCommandBuffers() { - if (commandBuffers.size() > 0) { - vkFreeCommandBuffers(device, commandPool, commandBuffers.size(), commandBuffers.data()); - } - commandBuffers.resize(swapChainFramebuffers.size()); VkCommandBufferAllocateInfo allocInfo = {}; @@ -762,7 +752,9 @@ class HelloTriangleApplication { beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; beginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT; - vkBeginCommandBuffer(commandBuffers[i], &beginInfo); + if (vkBeginCommandBuffer(commandBuffers[i], &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } VkRenderPassBeginInfo renderPassInfo = {}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; @@ -783,7 +775,7 @@ class HelloTriangleApplication { VkDeviceSize offsets[] = {0}; vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets); - vkCmdDraw(commandBuffers[i], vertices.size(), 1, 0, 0); + vkCmdDraw(commandBuffers[i], static_cast(vertices.size()), 1, 0, 0); vkCmdEndRenderPass(commandBuffers[i]); @@ -793,20 +785,32 @@ class HelloTriangleApplication { } } - void createSemaphores() { + void createSyncObjects() { + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + inFlightFences.resize(MAX_FRAMES_IN_FLIGHT); + VkSemaphoreCreateInfo semaphoreInfo = {}; semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; - if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, imageAvailableSemaphore.replace()) != VK_SUCCESS || - vkCreateSemaphore(device, &semaphoreInfo, nullptr, renderFinishedSemaphore.replace()) != VK_SUCCESS) { + VkFenceCreateInfo fenceInfo = {}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; - throw std::runtime_error("failed to create semaphores!"); + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS || + vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create synchronization objects for a frame!"); + } } } void drawFrame() { + vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, std::numeric_limits::max()); + uint32_t imageIndex; - VkResult result = vkAcquireNextImageKHR(device, swapChain, std::numeric_limits::max(), imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex); + VkResult result = vkAcquireNextImageKHR(device, swapChain, std::numeric_limits::max(), imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); if (result == VK_ERROR_OUT_OF_DATE_KHR) { recreateSwapChain(); @@ -818,7 +822,7 @@ class HelloTriangleApplication { VkSubmitInfo submitInfo = {}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; - VkSemaphore waitSemaphores[] = {imageAvailableSemaphore}; + VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[currentFrame]}; VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; submitInfo.waitSemaphoreCount = 1; submitInfo.pWaitSemaphores = waitSemaphores; @@ -827,11 +831,13 @@ class HelloTriangleApplication { submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &commandBuffers[imageIndex]; - VkSemaphore signalSemaphores[] = {renderFinishedSemaphore}; + VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[currentFrame]}; submitInfo.signalSemaphoreCount = 1; submitInfo.pSignalSemaphores = signalSemaphores; - if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) { throw std::runtime_error("failed to submit draw command buffer!"); } @@ -849,26 +855,28 @@ class HelloTriangleApplication { result = vkQueuePresentKHR(presentQueue, &presentInfo); - if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { + framebufferResized = false; recreateSwapChain(); } else if (result != VK_SUCCESS) { throw std::runtime_error("failed to present swap chain image!"); } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; } - void createShaderModule(const std::vector& code, VDeleter& shaderModule) { + VkShaderModule createShaderModule(const std::vector& code) { VkShaderModuleCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); - std::vector codeAligned(code.size() / 4 + 1); - memcpy(codeAligned.data(), code.data(), code.size()); - - createInfo.pCode = codeAligned.data(); - - if (vkCreateShaderModule(device, &createInfo, nullptr, shaderModule.replace()) != VK_SUCCESS) { + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { throw std::runtime_error("failed to create shader module!"); } + + return shaderModule; } VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { @@ -904,9 +912,12 @@ class HelloTriangleApplication { return capabilities.currentExtent; } else { int width, height; - glfwGetWindowSize(window, &width, &height); + glfwGetFramebufferSize(window, &width, &height); - VkExtent2D actualExtent = {width, height}; + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; actualExtent.width = std::max(capabilities.minImageExtent.width, std::min(capabilities.maxImageExtent.width, actualExtent.width)); actualExtent.height = std::max(capabilities.minImageExtent.height, std::min(capabilities.maxImageExtent.height, actualExtent.height)); @@ -1002,15 +1013,11 @@ class HelloTriangleApplication { } std::vector getRequiredExtensions() { - std::vector extensions; - - unsigned int glfwExtensionCount = 0; + uint32_t glfwExtensionCount = 0; const char** glfwExtensions; glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - for (unsigned int i = 0; i < glfwExtensionCount; i++) { - extensions.push_back(glfwExtensions[i]); - } + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); if (enableValidationLayers) { extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); @@ -1074,10 +1081,10 @@ int main() { try { app.run(); - } catch (const std::runtime_error& e) { + } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; -} \ No newline at end of file +} diff --git a/code/index_buffer.cpp b/code/20_index_buffer.cpp similarity index 82% rename from code/index_buffer.cpp rename to code/20_index_buffer.cpp index ad87d999..babbfdad 100644 --- a/code/index_buffer.cpp +++ b/code/20_index_buffer.cpp @@ -4,18 +4,20 @@ #include #include -#include -#include #include +#include #include #include #include +#include #include #include const int WIDTH = 800; const int HEIGHT = 600; +const int MAX_FRAMES_IN_FLIGHT = 2; + const std::vector validationLayers = { "VK_LAYER_LUNARG_standard_validation" }; @@ -46,64 +48,6 @@ void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT } } -template -class VDeleter { -public: - VDeleter() : VDeleter([](T, VkAllocationCallbacks*) {}) {} - - VDeleter(std::function deletef) { - this->deleter = [=](T obj) { deletef(obj, nullptr); }; - } - - VDeleter(const VDeleter& instance, std::function deletef) { - this->deleter = [&instance, deletef](T obj) { deletef(instance, obj, nullptr); }; - } - - VDeleter(const VDeleter& device, std::function deletef) { - this->deleter = [&device, deletef](T obj) { deletef(device, obj, nullptr); }; - } - - ~VDeleter() { - cleanup(); - } - - const T* operator &() const { - return &object; - } - - T* replace() { - cleanup(); - return &object; - } - - operator T() const { - return object; - } - - void operator=(T rhs) { - if (rhs != object) { - cleanup(); - object = rhs; - } - } - - template - bool operator==(V rhs) { - return object == T(rhs); - } - -private: - T object{VK_NULL_HANDLE}; - std::function deleter; - - void cleanup() { - if (object != VK_NULL_HANDLE) { - deleter(object); - } - object = VK_NULL_HANDLE; - } -}; - struct QueueFamilyIndices { int graphicsFamily = -1; int presentFamily = -1; @@ -166,43 +110,48 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugReportCallbackEXT callback; + VkSurfaceKHR surface; VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; - VDeleter device{vkDestroyDevice}; + VkDevice device; VkQueue graphicsQueue; VkQueue presentQueue; - - VDeleter swapChain{device, vkDestroySwapchainKHR}; + + VkSwapchainKHR swapChain; std::vector swapChainImages; VkFormat swapChainImageFormat; VkExtent2D swapChainExtent; - std::vector> swapChainImageViews; - std::vector> swapChainFramebuffers; - - VDeleter renderPass{device, vkDestroyRenderPass}; - VDeleter pipelineLayout{device, vkDestroyPipelineLayout}; - VDeleter graphicsPipeline{device, vkDestroyPipeline}; - - VDeleter commandPool{device, vkDestroyCommandPool}; - - VDeleter vertexBuffer{device, vkDestroyBuffer}; - VDeleter vertexBufferMemory{device, vkFreeMemory}; - VDeleter indexBuffer{device, vkDestroyBuffer}; - VDeleter indexBufferMemory{device, vkFreeMemory}; + std::vector swapChainImageViews; + std::vector swapChainFramebuffers; + + VkRenderPass renderPass; + VkPipelineLayout pipelineLayout; + VkPipeline graphicsPipeline; + + VkCommandPool commandPool; + + VkBuffer vertexBuffer; + VkDeviceMemory vertexBufferMemory; + VkBuffer indexBuffer; + VkDeviceMemory indexBufferMemory; std::vector commandBuffers; - VDeleter imageAvailableSemaphore{device, vkDestroySemaphore}; - VDeleter renderFinishedSemaphore{device, vkDestroySemaphore}; + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + std::vector inFlightFences; + size_t currentFrame = 0; + + bool framebufferResized = false; void initWindow() { glfwInit(); @@ -210,9 +159,13 @@ class HelloTriangleApplication { glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetWindowSizeCallback(window, HelloTriangleApplication::onWindowResized); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; } void initVulkan() { @@ -230,7 +183,7 @@ class HelloTriangleApplication { createVertexBuffer(); createIndexBuffer(); createCommandBuffers(); - createSemaphores(); + createSyncObjects(); } void mainLoop() { @@ -240,22 +193,68 @@ class HelloTriangleApplication { } vkDeviceWaitIdle(device); + } - glfwDestroyWindow(window); + void cleanupSwapChain() { + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } - glfwTerminate(); + vkFreeCommandBuffers(device, commandPool, static_cast(commandBuffers.size()), commandBuffers.data()); + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); } - static void onWindowResized(GLFWwindow* window, int width, int height) { - if (width == 0 || height == 0) return; - - HelloTriangleApplication* app = reinterpret_cast(glfwGetWindowUserPointer(window)); - app->recreateSwapChain(); + void cleanup() { + cleanupSwapChain(); + + vkDestroyBuffer(device, indexBuffer, nullptr); + vkFreeMemory(device, indexBufferMemory, nullptr); + + vkDestroyBuffer(device, vertexBuffer, nullptr); + vkFreeMemory(device, vertexBufferMemory, nullptr); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + vkDestroyFence(device, inFlightFences[i], nullptr); + } + + vkDestroyCommandPool(device, commandPool, nullptr); + + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugReportCallbackEXT(instance, callback, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); } void recreateSwapChain() { + int width = 0, height = 0; + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + vkDeviceWaitIdle(device); + cleanupSwapChain(); + createSwapChain(); createImageViews(); createRenderPass(); @@ -282,17 +281,17 @@ class HelloTriangleApplication { createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } @@ -305,13 +304,13 @@ class HelloTriangleApplication { createInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT; createInfo.pfnCallback = debugCallback; - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { + if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, &callback) != VK_SUCCESS) { throw std::runtime_error("failed to set up debug callback!"); } } void createSurface() { - if (glfwCreateWindowSurface(instance, window, nullptr, surface.replace()) != VK_SUCCESS) { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { throw std::runtime_error("failed to create window surface!"); } } @@ -360,22 +359,22 @@ class HelloTriangleApplication { VkDeviceCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); createInfo.pQueueCreateInfos = queueCreateInfos.data(); - createInfo.queueCreateInfoCount = (uint32_t) queueCreateInfos.size(); createInfo.pEnabledFeatures = &deviceFeatures; - createInfo.enabledExtensionCount = deviceExtensions.size(); + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); createInfo.ppEnabledExtensionNames = deviceExtensions.data(); if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateDevice(physicalDevice, &createInfo, nullptr, device.replace()) != VK_SUCCESS) { + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { throw std::runtime_error("failed to create logical device!"); } @@ -422,16 +421,10 @@ class HelloTriangleApplication { createInfo.presentMode = presentMode; createInfo.clipped = VK_TRUE; - VkSwapchainKHR oldSwapChain = swapChain; - createInfo.oldSwapchain = oldSwapChain; - - VkSwapchainKHR newSwapChain; - if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &newSwapChain) != VK_SUCCESS) { + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { throw std::runtime_error("failed to create swap chain!"); } - swapChain = newSwapChain; - vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); swapChainImages.resize(imageCount); vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); @@ -441,9 +434,9 @@ class HelloTriangleApplication { } void createImageViews() { - swapChainImageViews.resize(swapChainImages.size(), VDeleter{device, vkDestroyImageView}); + swapChainImageViews.resize(swapChainImages.size()); - for (uint32_t i = 0; i < swapChainImages.size(); i++) { + for (size_t i = 0; i < swapChainImages.size(); i++) { VkImageViewCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; createInfo.image = swapChainImages[i]; @@ -459,7 +452,7 @@ class HelloTriangleApplication { createInfo.subresourceRange.baseArrayLayer = 0; createInfo.subresourceRange.layerCount = 1; - if (vkCreateImageView(device, &createInfo, nullptr, swapChainImageViews[i].replace()) != VK_SUCCESS) { + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { throw std::runtime_error("failed to create image views!"); } } @@ -502,7 +495,7 @@ class HelloTriangleApplication { renderPassInfo.dependencyCount = 1; renderPassInfo.pDependencies = &dependency; - if (vkCreateRenderPass(device, &renderPassInfo, nullptr, renderPass.replace()) != VK_SUCCESS) { + if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { throw std::runtime_error("failed to create render pass!"); } } @@ -511,10 +504,8 @@ class HelloTriangleApplication { auto vertShaderCode = readFile("shaders/vert.spv"); auto fragShaderCode = readFile("shaders/frag.spv"); - VDeleter vertShaderModule{device, vkDestroyShaderModule}; - VDeleter fragShaderModule{device, vkDestroyShaderModule}; - createShaderModule(vertShaderCode, vertShaderModule); - createShaderModule(fragShaderCode, fragShaderModule); + VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); VkPipelineShaderStageCreateInfo vertShaderStageInfo = {}; vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; @@ -537,7 +528,7 @@ class HelloTriangleApplication { auto attributeDescriptions = Vertex::getAttributeDescriptions(); vertexInputInfo.vertexBindingDescriptionCount = 1; - vertexInputInfo.vertexAttributeDescriptionCount = attributeDescriptions.size(); + vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()); vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); @@ -600,7 +591,7 @@ class HelloTriangleApplication { pipelineLayoutInfo.setLayoutCount = 0; pipelineLayoutInfo.pushConstantRangeCount = 0; - if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, pipelineLayout.replace()) != VK_SUCCESS) { + if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { throw std::runtime_error("failed to create pipeline layout!"); } @@ -619,13 +610,16 @@ class HelloTriangleApplication { pipelineInfo.subpass = 0; pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; - if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, graphicsPipeline.replace()) != VK_SUCCESS) { + if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { throw std::runtime_error("failed to create graphics pipeline!"); } + + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); } void createFramebuffers() { - swapChainFramebuffers.resize(swapChainImageViews.size(), VDeleter{device, vkDestroyFramebuffer}); + swapChainFramebuffers.resize(swapChainImageViews.size()); for (size_t i = 0; i < swapChainImageViews.size(); i++) { VkImageView attachments[] = { @@ -641,7 +635,7 @@ class HelloTriangleApplication { framebufferInfo.height = swapChainExtent.height; framebufferInfo.layers = 1; - if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, swapChainFramebuffers[i].replace()) != VK_SUCCESS) { + if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { throw std::runtime_error("failed to create framebuffer!"); } } @@ -654,7 +648,7 @@ class HelloTriangleApplication { poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily; - if (vkCreateCommandPool(device, &poolInfo, nullptr, commandPool.replace()) != VK_SUCCESS) { + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { throw std::runtime_error("failed to create graphics command pool!"); } } @@ -662,8 +656,8 @@ class HelloTriangleApplication { void createVertexBuffer() { VkDeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); - VDeleter stagingBuffer{device, vkDestroyBuffer}; - VDeleter stagingBufferMemory{device, vkFreeMemory}; + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); void* data; @@ -674,13 +668,16 @@ class HelloTriangleApplication { createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, vertexBuffer, vertexBufferMemory); copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); } void createIndexBuffer() { VkDeviceSize bufferSize = sizeof(indices[0]) * indices.size(); - VDeleter stagingBuffer{device, vkDestroyBuffer}; - VDeleter stagingBufferMemory{device, vkFreeMemory}; + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); void* data; @@ -691,16 +688,19 @@ class HelloTriangleApplication { createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, indexBuffer, indexBufferMemory); copyBuffer(stagingBuffer, indexBuffer, bufferSize); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); } - void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VDeleter& buffer, VDeleter& bufferMemory) { + void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) { VkBufferCreateInfo bufferInfo = {}; bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; bufferInfo.size = size; bufferInfo.usage = usage; - bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - if (vkCreateBuffer(device, &bufferInfo, nullptr, buffer.replace()) != VK_SUCCESS) { + if (vkCreateBuffer(device, &bufferInfo, nullptr, &buffer) != VK_SUCCESS) { throw std::runtime_error("failed to create buffer!"); } @@ -712,7 +712,7 @@ class HelloTriangleApplication { allocInfo.allocationSize = memRequirements.size; allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); - if (vkAllocateMemory(device, &allocInfo, nullptr, bufferMemory.replace()) != VK_SUCCESS) { + if (vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) { throw std::runtime_error("failed to allocate buffer memory!"); } @@ -766,10 +766,6 @@ class HelloTriangleApplication { } void createCommandBuffers() { - if (commandBuffers.size() > 0) { - vkFreeCommandBuffers(device, commandPool, commandBuffers.size(), commandBuffers.data()); - } - commandBuffers.resize(swapChainFramebuffers.size()); VkCommandBufferAllocateInfo allocInfo = {}; @@ -787,7 +783,9 @@ class HelloTriangleApplication { beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; beginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT; - vkBeginCommandBuffer(commandBuffers[i], &beginInfo); + if (vkBeginCommandBuffer(commandBuffers[i], &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } VkRenderPassBeginInfo renderPassInfo = {}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; @@ -810,7 +808,7 @@ class HelloTriangleApplication { vkCmdBindIndexBuffer(commandBuffers[i], indexBuffer, 0, VK_INDEX_TYPE_UINT16); - vkCmdDrawIndexed(commandBuffers[i], indices.size(), 1, 0, 0, 0); + vkCmdDrawIndexed(commandBuffers[i], static_cast(indices.size()), 1, 0, 0, 0); vkCmdEndRenderPass(commandBuffers[i]); @@ -820,20 +818,32 @@ class HelloTriangleApplication { } } - void createSemaphores() { + void createSyncObjects() { + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + inFlightFences.resize(MAX_FRAMES_IN_FLIGHT); + VkSemaphoreCreateInfo semaphoreInfo = {}; semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; - if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, imageAvailableSemaphore.replace()) != VK_SUCCESS || - vkCreateSemaphore(device, &semaphoreInfo, nullptr, renderFinishedSemaphore.replace()) != VK_SUCCESS) { + VkFenceCreateInfo fenceInfo = {}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; - throw std::runtime_error("failed to create semaphores!"); + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS || + vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create synchronization objects for a frame!"); + } } } void drawFrame() { + vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, std::numeric_limits::max()); + uint32_t imageIndex; - VkResult result = vkAcquireNextImageKHR(device, swapChain, std::numeric_limits::max(), imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex); + VkResult result = vkAcquireNextImageKHR(device, swapChain, std::numeric_limits::max(), imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); if (result == VK_ERROR_OUT_OF_DATE_KHR) { recreateSwapChain(); @@ -845,7 +855,7 @@ class HelloTriangleApplication { VkSubmitInfo submitInfo = {}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; - VkSemaphore waitSemaphores[] = {imageAvailableSemaphore}; + VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[currentFrame]}; VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; submitInfo.waitSemaphoreCount = 1; submitInfo.pWaitSemaphores = waitSemaphores; @@ -854,11 +864,13 @@ class HelloTriangleApplication { submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &commandBuffers[imageIndex]; - VkSemaphore signalSemaphores[] = {renderFinishedSemaphore}; + VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[currentFrame]}; submitInfo.signalSemaphoreCount = 1; submitInfo.pSignalSemaphores = signalSemaphores; - if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) { throw std::runtime_error("failed to submit draw command buffer!"); } @@ -876,26 +888,28 @@ class HelloTriangleApplication { result = vkQueuePresentKHR(presentQueue, &presentInfo); - if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { + framebufferResized = false; recreateSwapChain(); } else if (result != VK_SUCCESS) { throw std::runtime_error("failed to present swap chain image!"); } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; } - void createShaderModule(const std::vector& code, VDeleter& shaderModule) { + VkShaderModule createShaderModule(const std::vector& code) { VkShaderModuleCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); - std::vector codeAligned(code.size() / 4 + 1); - memcpy(codeAligned.data(), code.data(), code.size()); - - createInfo.pCode = codeAligned.data(); - - if (vkCreateShaderModule(device, &createInfo, nullptr, shaderModule.replace()) != VK_SUCCESS) { + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { throw std::runtime_error("failed to create shader module!"); } + + return shaderModule; } VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { @@ -931,9 +945,12 @@ class HelloTriangleApplication { return capabilities.currentExtent; } else { int width, height; - glfwGetWindowSize(window, &width, &height); + glfwGetFramebufferSize(window, &width, &height); - VkExtent2D actualExtent = {width, height}; + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; actualExtent.width = std::max(capabilities.minImageExtent.width, std::min(capabilities.maxImageExtent.width, actualExtent.width)); actualExtent.height = std::max(capabilities.minImageExtent.height, std::min(capabilities.maxImageExtent.height, actualExtent.height)); @@ -1029,15 +1046,11 @@ class HelloTriangleApplication { } std::vector getRequiredExtensions() { - std::vector extensions; - - unsigned int glfwExtensionCount = 0; + uint32_t glfwExtensionCount = 0; const char** glfwExtensions; glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - for (unsigned int i = 0; i < glfwExtensionCount; i++) { - extensions.push_back(glfwExtensions[i]); - } + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); if (enableValidationLayers) { extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); @@ -1101,10 +1114,10 @@ int main() { try { app.run(); - } catch (const std::runtime_error& e) { + } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; -} \ No newline at end of file +} diff --git a/code/descriptor_layout.cpp b/code/21_descriptor_layout.cpp similarity index 80% rename from code/descriptor_layout.cpp rename to code/21_descriptor_layout.cpp index 6b40847a..edab95bf 100644 --- a/code/descriptor_layout.cpp +++ b/code/21_descriptor_layout.cpp @@ -6,19 +6,21 @@ #include #include -#include -#include -#include #include +#include #include +#include #include #include +#include #include #include const int WIDTH = 800; const int HEIGHT = 600; +const int MAX_FRAMES_IN_FLIGHT = 2; + const std::vector validationLayers = { "VK_LAYER_LUNARG_standard_validation" }; @@ -49,64 +51,6 @@ void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT } } -template -class VDeleter { -public: - VDeleter() : VDeleter([](T, VkAllocationCallbacks*) {}) {} - - VDeleter(std::function deletef) { - this->deleter = [=](T obj) { deletef(obj, nullptr); }; - } - - VDeleter(const VDeleter& instance, std::function deletef) { - this->deleter = [&instance, deletef](T obj) { deletef(instance, obj, nullptr); }; - } - - VDeleter(const VDeleter& device, std::function deletef) { - this->deleter = [&device, deletef](T obj) { deletef(device, obj, nullptr); }; - } - - ~VDeleter() { - cleanup(); - } - - const T* operator &() const { - return &object; - } - - T* replace() { - cleanup(); - return &object; - } - - operator T() const { - return object; - } - - void operator=(T rhs) { - if (rhs != object) { - cleanup(); - object = rhs; - } - } - - template - bool operator==(V rhs) { - return object == T(rhs); - } - -private: - T object{VK_NULL_HANDLE}; - std::function deleter; - - void cleanup() { - if (object != VK_NULL_HANDLE) { - deleter(object); - } - object = VK_NULL_HANDLE; - } -}; - struct QueueFamilyIndices { int graphicsFamily = -1; int presentFamily = -1; @@ -175,49 +119,52 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugReportCallbackEXT callback; + VkSurfaceKHR surface; VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; - VDeleter device{vkDestroyDevice}; + VkDevice device; VkQueue graphicsQueue; VkQueue presentQueue; - - VDeleter swapChain{device, vkDestroySwapchainKHR}; + + VkSwapchainKHR swapChain; std::vector swapChainImages; VkFormat swapChainImageFormat; VkExtent2D swapChainExtent; - std::vector> swapChainImageViews; - std::vector> swapChainFramebuffers; - - VDeleter renderPass{device, vkDestroyRenderPass}; - VDeleter descriptorSetLayout{device, vkDestroyDescriptorSetLayout}; - VDeleter pipelineLayout{device, vkDestroyPipelineLayout}; - VDeleter graphicsPipeline{device, vkDestroyPipeline}; - - VDeleter commandPool{device, vkDestroyCommandPool}; - - VDeleter vertexBuffer{device, vkDestroyBuffer}; - VDeleter vertexBufferMemory{device, vkFreeMemory}; - VDeleter indexBuffer{device, vkDestroyBuffer}; - VDeleter indexBufferMemory{device, vkFreeMemory}; - - VDeleter uniformStagingBuffer{device, vkDestroyBuffer}; - VDeleter uniformStagingBufferMemory{device, vkFreeMemory}; - VDeleter uniformBuffer{device, vkDestroyBuffer}; - VDeleter uniformBufferMemory{device, vkFreeMemory}; + std::vector swapChainImageViews; + std::vector swapChainFramebuffers; + + VkRenderPass renderPass; + VkDescriptorSetLayout descriptorSetLayout; + VkPipelineLayout pipelineLayout; + VkPipeline graphicsPipeline; + + VkCommandPool commandPool; + + VkBuffer vertexBuffer; + VkDeviceMemory vertexBufferMemory; + VkBuffer indexBuffer; + VkDeviceMemory indexBufferMemory; + + std::vector uniformBuffers; + std::vector uniformBuffersMemory; std::vector commandBuffers; - VDeleter imageAvailableSemaphore{device, vkDestroySemaphore}; - VDeleter renderFinishedSemaphore{device, vkDestroySemaphore}; + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + std::vector inFlightFences; + size_t currentFrame = 0; + + bool framebufferResized = false; void initWindow() { glfwInit(); @@ -225,9 +172,13 @@ class HelloTriangleApplication { glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetWindowSizeCallback(window, HelloTriangleApplication::onWindowResized); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; } void initVulkan() { @@ -245,36 +196,87 @@ class HelloTriangleApplication { createCommandPool(); createVertexBuffer(); createIndexBuffer(); - createUniformBuffer(); + createUniformBuffers(); createCommandBuffers(); - createSemaphores(); + createSyncObjects(); } void mainLoop() { while (!glfwWindowShouldClose(window)) { glfwPollEvents(); - - updateUniformBuffer(); drawFrame(); } vkDeviceWaitIdle(device); + } - glfwDestroyWindow(window); + void cleanupSwapChain() { + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } - glfwTerminate(); + vkFreeCommandBuffers(device, commandPool, static_cast(commandBuffers.size()), commandBuffers.data()); + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); } - static void onWindowResized(GLFWwindow* window, int width, int height) { - if (width == 0 || height == 0) return; - - HelloTriangleApplication* app = reinterpret_cast(glfwGetWindowUserPointer(window)); - app->recreateSwapChain(); + void cleanup() { + cleanupSwapChain(); + + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + vkDestroyBuffer(device, uniformBuffers[i], nullptr); + vkFreeMemory(device, uniformBuffersMemory[i], nullptr); + } + + vkDestroyBuffer(device, indexBuffer, nullptr); + vkFreeMemory(device, indexBufferMemory, nullptr); + + vkDestroyBuffer(device, vertexBuffer, nullptr); + vkFreeMemory(device, vertexBufferMemory, nullptr); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + vkDestroyFence(device, inFlightFences[i], nullptr); + } + + vkDestroyCommandPool(device, commandPool, nullptr); + + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugReportCallbackEXT(instance, callback, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); } void recreateSwapChain() { + int width = 0, height = 0; + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + vkDeviceWaitIdle(device); + cleanupSwapChain(); + createSwapChain(); createImageViews(); createRenderPass(); @@ -301,17 +303,17 @@ class HelloTriangleApplication { createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } @@ -324,13 +326,13 @@ class HelloTriangleApplication { createInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT; createInfo.pfnCallback = debugCallback; - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { + if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, &callback) != VK_SUCCESS) { throw std::runtime_error("failed to set up debug callback!"); } } void createSurface() { - if (glfwCreateWindowSurface(instance, window, nullptr, surface.replace()) != VK_SUCCESS) { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { throw std::runtime_error("failed to create window surface!"); } } @@ -379,22 +381,22 @@ class HelloTriangleApplication { VkDeviceCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); createInfo.pQueueCreateInfos = queueCreateInfos.data(); - createInfo.queueCreateInfoCount = (uint32_t) queueCreateInfos.size(); createInfo.pEnabledFeatures = &deviceFeatures; - createInfo.enabledExtensionCount = deviceExtensions.size(); + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); createInfo.ppEnabledExtensionNames = deviceExtensions.data(); if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateDevice(physicalDevice, &createInfo, nullptr, device.replace()) != VK_SUCCESS) { + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { throw std::runtime_error("failed to create logical device!"); } @@ -441,16 +443,10 @@ class HelloTriangleApplication { createInfo.presentMode = presentMode; createInfo.clipped = VK_TRUE; - VkSwapchainKHR oldSwapChain = swapChain; - createInfo.oldSwapchain = oldSwapChain; - - VkSwapchainKHR newSwapChain; - if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &newSwapChain) != VK_SUCCESS) { + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { throw std::runtime_error("failed to create swap chain!"); } - swapChain = newSwapChain; - vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); swapChainImages.resize(imageCount); vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); @@ -460,9 +456,9 @@ class HelloTriangleApplication { } void createImageViews() { - swapChainImageViews.resize(swapChainImages.size(), VDeleter{device, vkDestroyImageView}); + swapChainImageViews.resize(swapChainImages.size()); - for (uint32_t i = 0; i < swapChainImages.size(); i++) { + for (size_t i = 0; i < swapChainImages.size(); i++) { VkImageViewCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; createInfo.image = swapChainImages[i]; @@ -478,7 +474,7 @@ class HelloTriangleApplication { createInfo.subresourceRange.baseArrayLayer = 0; createInfo.subresourceRange.layerCount = 1; - if (vkCreateImageView(device, &createInfo, nullptr, swapChainImageViews[i].replace()) != VK_SUCCESS) { + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { throw std::runtime_error("failed to create image views!"); } } @@ -521,7 +517,7 @@ class HelloTriangleApplication { renderPassInfo.dependencyCount = 1; renderPassInfo.pDependencies = &dependency; - if (vkCreateRenderPass(device, &renderPassInfo, nullptr, renderPass.replace()) != VK_SUCCESS) { + if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { throw std::runtime_error("failed to create render pass!"); } } @@ -539,7 +535,7 @@ class HelloTriangleApplication { layoutInfo.bindingCount = 1; layoutInfo.pBindings = &uboLayoutBinding; - if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, descriptorSetLayout.replace()) != VK_SUCCESS) { + if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &descriptorSetLayout) != VK_SUCCESS) { throw std::runtime_error("failed to create descriptor set layout!"); } } @@ -548,10 +544,8 @@ class HelloTriangleApplication { auto vertShaderCode = readFile("shaders/vert.spv"); auto fragShaderCode = readFile("shaders/frag.spv"); - VDeleter vertShaderModule{device, vkDestroyShaderModule}; - VDeleter fragShaderModule{device, vkDestroyShaderModule}; - createShaderModule(vertShaderCode, vertShaderModule); - createShaderModule(fragShaderCode, fragShaderModule); + VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); VkPipelineShaderStageCreateInfo vertShaderStageInfo = {}; vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; @@ -574,7 +568,7 @@ class HelloTriangleApplication { auto attributeDescriptions = Vertex::getAttributeDescriptions(); vertexInputInfo.vertexBindingDescriptionCount = 1; - vertexInputInfo.vertexAttributeDescriptionCount = attributeDescriptions.size(); + vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()); vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); @@ -632,13 +626,12 @@ class HelloTriangleApplication { colorBlending.blendConstants[2] = 0.0f; colorBlending.blendConstants[3] = 0.0f; - VkDescriptorSetLayout setLayouts[] = {descriptorSetLayout}; VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; pipelineLayoutInfo.setLayoutCount = 1; - pipelineLayoutInfo.pSetLayouts = setLayouts; + pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout; - if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, pipelineLayout.replace()) != VK_SUCCESS) { + if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { throw std::runtime_error("failed to create pipeline layout!"); } @@ -657,13 +650,16 @@ class HelloTriangleApplication { pipelineInfo.subpass = 0; pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; - if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, graphicsPipeline.replace()) != VK_SUCCESS) { + if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { throw std::runtime_error("failed to create graphics pipeline!"); } + + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); } void createFramebuffers() { - swapChainFramebuffers.resize(swapChainImageViews.size(), VDeleter{device, vkDestroyFramebuffer}); + swapChainFramebuffers.resize(swapChainImageViews.size()); for (size_t i = 0; i < swapChainImageViews.size(); i++) { VkImageView attachments[] = { @@ -679,7 +675,7 @@ class HelloTriangleApplication { framebufferInfo.height = swapChainExtent.height; framebufferInfo.layers = 1; - if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, swapChainFramebuffers[i].replace()) != VK_SUCCESS) { + if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { throw std::runtime_error("failed to create framebuffer!"); } } @@ -692,7 +688,7 @@ class HelloTriangleApplication { poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily; - if (vkCreateCommandPool(device, &poolInfo, nullptr, commandPool.replace()) != VK_SUCCESS) { + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { throw std::runtime_error("failed to create graphics command pool!"); } } @@ -700,8 +696,8 @@ class HelloTriangleApplication { void createVertexBuffer() { VkDeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); - VDeleter stagingBuffer{device, vkDestroyBuffer}; - VDeleter stagingBufferMemory{device, vkFreeMemory}; + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); void* data; @@ -712,13 +708,16 @@ class HelloTriangleApplication { createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, vertexBuffer, vertexBufferMemory); copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); } void createIndexBuffer() { VkDeviceSize bufferSize = sizeof(indices[0]) * indices.size(); - VDeleter stagingBuffer{device, vkDestroyBuffer}; - VDeleter stagingBufferMemory{device, vkFreeMemory}; + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); void* data; @@ -729,23 +728,30 @@ class HelloTriangleApplication { createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, indexBuffer, indexBufferMemory); copyBuffer(stagingBuffer, indexBuffer, bufferSize); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); } - void createUniformBuffer() { + void createUniformBuffers() { VkDeviceSize bufferSize = sizeof(UniformBufferObject); - createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformStagingBuffer, uniformStagingBufferMemory); - createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, uniformBuffer, uniformBufferMemory); + uniformBuffers.resize(swapChainImages.size()); + uniformBuffersMemory.resize(swapChainImages.size()); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + createBuffer(bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformBuffers[i], uniformBuffersMemory[i]); + } } - void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VDeleter& buffer, VDeleter& bufferMemory) { + void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) { VkBufferCreateInfo bufferInfo = {}; bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; bufferInfo.size = size; bufferInfo.usage = usage; - bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - if (vkCreateBuffer(device, &bufferInfo, nullptr, buffer.replace()) != VK_SUCCESS) { + if (vkCreateBuffer(device, &bufferInfo, nullptr, &buffer) != VK_SUCCESS) { throw std::runtime_error("failed to create buffer!"); } @@ -757,7 +763,7 @@ class HelloTriangleApplication { allocInfo.allocationSize = memRequirements.size; allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); - if (vkAllocateMemory(device, &allocInfo, nullptr, bufferMemory.replace()) != VK_SUCCESS) { + if (vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) { throw std::runtime_error("failed to allocate buffer memory!"); } @@ -811,10 +817,6 @@ class HelloTriangleApplication { } void createCommandBuffers() { - if (commandBuffers.size() > 0) { - vkFreeCommandBuffers(device, commandPool, commandBuffers.size(), commandBuffers.data()); - } - commandBuffers.resize(swapChainFramebuffers.size()); VkCommandBufferAllocateInfo allocInfo = {}; @@ -832,7 +834,9 @@ class HelloTriangleApplication { beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; beginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT; - vkBeginCommandBuffer(commandBuffers[i], &beginInfo); + if (vkBeginCommandBuffer(commandBuffers[i], &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } VkRenderPassBeginInfo renderPassInfo = {}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; @@ -855,7 +859,7 @@ class HelloTriangleApplication { vkCmdBindIndexBuffer(commandBuffers[i], indexBuffer, 0, VK_INDEX_TYPE_UINT16); - vkCmdDrawIndexed(commandBuffers[i], indices.size(), 1, 0, 0, 0); + vkCmdDrawIndexed(commandBuffers[i], static_cast(indices.size()), 1, 0, 0, 0); vkCmdEndRenderPass(commandBuffers[i]); @@ -865,40 +869,50 @@ class HelloTriangleApplication { } } - void createSemaphores() { + void createSyncObjects() { + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + inFlightFences.resize(MAX_FRAMES_IN_FLIGHT); + VkSemaphoreCreateInfo semaphoreInfo = {}; semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; - if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, imageAvailableSemaphore.replace()) != VK_SUCCESS || - vkCreateSemaphore(device, &semaphoreInfo, nullptr, renderFinishedSemaphore.replace()) != VK_SUCCESS) { + VkFenceCreateInfo fenceInfo = {}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; - throw std::runtime_error("failed to create semaphores!"); + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS || + vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create synchronization objects for a frame!"); + } } } - void updateUniformBuffer() { + 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_cast(currentTime - startTime).count() / 1000.0f; + float time = std::chrono::duration(currentTime - startTime).count(); UniformBufferObject ubo = {}; - ubo.model = glm::rotate(glm::mat4(), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + 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; - vkMapMemory(device, uniformStagingBufferMemory, 0, sizeof(ubo), 0, &data); + vkMapMemory(device, uniformBuffersMemory[currentImage], 0, sizeof(ubo), 0, &data); memcpy(data, &ubo, sizeof(ubo)); - vkUnmapMemory(device, uniformStagingBufferMemory); - - copyBuffer(uniformStagingBuffer, uniformBuffer, sizeof(ubo)); + vkUnmapMemory(device, uniformBuffersMemory[currentImage]); } void drawFrame() { + vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, std::numeric_limits::max()); + uint32_t imageIndex; - VkResult result = vkAcquireNextImageKHR(device, swapChain, std::numeric_limits::max(), imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex); + VkResult result = vkAcquireNextImageKHR(device, swapChain, std::numeric_limits::max(), imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); if (result == VK_ERROR_OUT_OF_DATE_KHR) { recreateSwapChain(); @@ -907,10 +921,12 @@ class HelloTriangleApplication { throw std::runtime_error("failed to acquire swap chain image!"); } + updateUniformBuffer(imageIndex); + VkSubmitInfo submitInfo = {}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; - VkSemaphore waitSemaphores[] = {imageAvailableSemaphore}; + VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[currentFrame]}; VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; submitInfo.waitSemaphoreCount = 1; submitInfo.pWaitSemaphores = waitSemaphores; @@ -919,11 +935,13 @@ class HelloTriangleApplication { submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &commandBuffers[imageIndex]; - VkSemaphore signalSemaphores[] = {renderFinishedSemaphore}; + VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[currentFrame]}; submitInfo.signalSemaphoreCount = 1; submitInfo.pSignalSemaphores = signalSemaphores; - if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) { throw std::runtime_error("failed to submit draw command buffer!"); } @@ -941,26 +959,28 @@ class HelloTriangleApplication { result = vkQueuePresentKHR(presentQueue, &presentInfo); - if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { + framebufferResized = false; recreateSwapChain(); } else if (result != VK_SUCCESS) { throw std::runtime_error("failed to present swap chain image!"); } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; } - void createShaderModule(const std::vector& code, VDeleter& shaderModule) { + VkShaderModule createShaderModule(const std::vector& code) { VkShaderModuleCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); - std::vector codeAligned(code.size() / 4 + 1); - memcpy(codeAligned.data(), code.data(), code.size()); - - createInfo.pCode = codeAligned.data(); - - if (vkCreateShaderModule(device, &createInfo, nullptr, shaderModule.replace()) != VK_SUCCESS) { + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { throw std::runtime_error("failed to create shader module!"); } + + return shaderModule; } VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { @@ -996,9 +1016,12 @@ class HelloTriangleApplication { return capabilities.currentExtent; } else { int width, height; - glfwGetWindowSize(window, &width, &height); + glfwGetFramebufferSize(window, &width, &height); - VkExtent2D actualExtent = {width, height}; + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; actualExtent.width = std::max(capabilities.minImageExtent.width, std::min(capabilities.maxImageExtent.width, actualExtent.width)); actualExtent.height = std::max(capabilities.minImageExtent.height, std::min(capabilities.maxImageExtent.height, actualExtent.height)); @@ -1094,15 +1117,11 @@ class HelloTriangleApplication { } std::vector getRequiredExtensions() { - std::vector extensions; - - unsigned int glfwExtensionCount = 0; + uint32_t glfwExtensionCount = 0; const char** glfwExtensions; glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - for (unsigned int i = 0; i < glfwExtensionCount; i++) { - extensions.push_back(glfwExtensions[i]); - } + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); if (enableValidationLayers) { extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); @@ -1166,10 +1185,10 @@ int main() { try { app.run(); - } catch (const std::runtime_error& e) { + } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; -} \ No newline at end of file +} diff --git a/code/shader_vertexbuffer.frag b/code/21_shader_ubo.frag similarity index 100% rename from code/shader_vertexbuffer.frag rename to code/21_shader_ubo.frag diff --git a/code/shader_ubo.vert b/code/21_shader_ubo.vert similarity index 100% rename from code/shader_ubo.vert rename to code/21_shader_ubo.vert diff --git a/code/descriptor_set.cpp b/code/22_descriptor_sets.cpp similarity index 78% rename from code/descriptor_set.cpp rename to code/22_descriptor_sets.cpp index e66d8d90..92b90b37 100644 --- a/code/descriptor_set.cpp +++ b/code/22_descriptor_sets.cpp @@ -6,19 +6,21 @@ #include #include -#include -#include -#include #include +#include #include +#include #include #include +#include #include #include const int WIDTH = 800; const int HEIGHT = 600; +const int MAX_FRAMES_IN_FLIGHT = 2; + const std::vector validationLayers = { "VK_LAYER_LUNARG_standard_validation" }; @@ -49,64 +51,6 @@ void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT } } -template -class VDeleter { -public: - VDeleter() : VDeleter([](T, VkAllocationCallbacks*) {}) {} - - VDeleter(std::function deletef) { - this->deleter = [=](T obj) { deletef(obj, nullptr); }; - } - - VDeleter(const VDeleter& instance, std::function deletef) { - this->deleter = [&instance, deletef](T obj) { deletef(instance, obj, nullptr); }; - } - - VDeleter(const VDeleter& device, std::function deletef) { - this->deleter = [&device, deletef](T obj) { deletef(device, obj, nullptr); }; - } - - ~VDeleter() { - cleanup(); - } - - const T* operator &() const { - return &object; - } - - T* replace() { - cleanup(); - return &object; - } - - operator T() const { - return object; - } - - void operator=(T rhs) { - if (rhs != object) { - cleanup(); - object = rhs; - } - } - - template - bool operator==(V rhs) { - return object == T(rhs); - } - -private: - T object{VK_NULL_HANDLE}; - std::function deleter; - - void cleanup() { - if (object != VK_NULL_HANDLE) { - deleter(object); - } - object = VK_NULL_HANDLE; - } -}; - struct QueueFamilyIndices { int graphicsFamily = -1; int presentFamily = -1; @@ -175,52 +119,55 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugReportCallbackEXT callback; + VkSurfaceKHR surface; VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; - VDeleter device{vkDestroyDevice}; + VkDevice device; VkQueue graphicsQueue; VkQueue presentQueue; - - VDeleter swapChain{device, vkDestroySwapchainKHR}; + + VkSwapchainKHR swapChain; std::vector swapChainImages; VkFormat swapChainImageFormat; VkExtent2D swapChainExtent; - std::vector> swapChainImageViews; - std::vector> swapChainFramebuffers; - - VDeleter renderPass{device, vkDestroyRenderPass}; - VDeleter descriptorSetLayout{device, vkDestroyDescriptorSetLayout}; - VDeleter pipelineLayout{device, vkDestroyPipelineLayout}; - VDeleter graphicsPipeline{device, vkDestroyPipeline}; - - VDeleter commandPool{device, vkDestroyCommandPool}; - - VDeleter vertexBuffer{device, vkDestroyBuffer}; - VDeleter vertexBufferMemory{device, vkFreeMemory}; - VDeleter indexBuffer{device, vkDestroyBuffer}; - VDeleter indexBufferMemory{device, vkFreeMemory}; - - VDeleter uniformStagingBuffer{device, vkDestroyBuffer}; - VDeleter uniformStagingBufferMemory{device, vkFreeMemory}; - VDeleter uniformBuffer{device, vkDestroyBuffer}; - VDeleter uniformBufferMemory{device, vkFreeMemory}; - - VDeleter descriptorPool{device, vkDestroyDescriptorPool}; - VkDescriptorSet descriptorSet; + std::vector swapChainImageViews; + std::vector swapChainFramebuffers; + + VkRenderPass renderPass; + VkDescriptorSetLayout descriptorSetLayout; + VkPipelineLayout pipelineLayout; + VkPipeline graphicsPipeline; + + VkCommandPool commandPool; + + VkBuffer vertexBuffer; + VkDeviceMemory vertexBufferMemory; + VkBuffer indexBuffer; + VkDeviceMemory indexBufferMemory; + + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + + VkDescriptorPool descriptorPool; + std::vector descriptorSets; std::vector commandBuffers; - VDeleter imageAvailableSemaphore{device, vkDestroySemaphore}; - VDeleter renderFinishedSemaphore{device, vkDestroySemaphore}; + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + std::vector inFlightFences; + size_t currentFrame = 0; + + bool framebufferResized = false; void initWindow() { glfwInit(); @@ -228,9 +175,13 @@ class HelloTriangleApplication { glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetWindowSizeCallback(window, HelloTriangleApplication::onWindowResized); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; } void initVulkan() { @@ -248,38 +199,91 @@ class HelloTriangleApplication { createCommandPool(); createVertexBuffer(); createIndexBuffer(); - createUniformBuffer(); + createUniformBuffers(); createDescriptorPool(); - createDescriptorSet(); + createDescriptorSets(); createCommandBuffers(); - createSemaphores(); + createSyncObjects(); } void mainLoop() { while (!glfwWindowShouldClose(window)) { glfwPollEvents(); - - updateUniformBuffer(); drawFrame(); } vkDeviceWaitIdle(device); + } - glfwDestroyWindow(window); + void cleanupSwapChain() { + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } - glfwTerminate(); + vkFreeCommandBuffers(device, commandPool, static_cast(commandBuffers.size()), commandBuffers.data()); + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); } - static void onWindowResized(GLFWwindow* window, int width, int height) { - if (width == 0 || height == 0) return; - - HelloTriangleApplication* app = reinterpret_cast(glfwGetWindowUserPointer(window)); - app->recreateSwapChain(); + void cleanup() { + cleanupSwapChain(); + + vkDestroyDescriptorPool(device, descriptorPool, nullptr); + + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + vkDestroyBuffer(device, uniformBuffers[i], nullptr); + vkFreeMemory(device, uniformBuffersMemory[i], nullptr); + } + + vkDestroyBuffer(device, indexBuffer, nullptr); + vkFreeMemory(device, indexBufferMemory, nullptr); + + vkDestroyBuffer(device, vertexBuffer, nullptr); + vkFreeMemory(device, vertexBufferMemory, nullptr); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + vkDestroyFence(device, inFlightFences[i], nullptr); + } + + vkDestroyCommandPool(device, commandPool, nullptr); + + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugReportCallbackEXT(instance, callback, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); } void recreateSwapChain() { + int width = 0, height = 0; + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + vkDeviceWaitIdle(device); + cleanupSwapChain(); + createSwapChain(); createImageViews(); createRenderPass(); @@ -306,17 +310,17 @@ class HelloTriangleApplication { createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } @@ -329,13 +333,13 @@ class HelloTriangleApplication { createInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT; createInfo.pfnCallback = debugCallback; - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { + if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, &callback) != VK_SUCCESS) { throw std::runtime_error("failed to set up debug callback!"); } } void createSurface() { - if (glfwCreateWindowSurface(instance, window, nullptr, surface.replace()) != VK_SUCCESS) { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { throw std::runtime_error("failed to create window surface!"); } } @@ -384,22 +388,22 @@ class HelloTriangleApplication { VkDeviceCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); createInfo.pQueueCreateInfos = queueCreateInfos.data(); - createInfo.queueCreateInfoCount = (uint32_t) queueCreateInfos.size(); createInfo.pEnabledFeatures = &deviceFeatures; - createInfo.enabledExtensionCount = deviceExtensions.size(); + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); createInfo.ppEnabledExtensionNames = deviceExtensions.data(); if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateDevice(physicalDevice, &createInfo, nullptr, device.replace()) != VK_SUCCESS) { + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { throw std::runtime_error("failed to create logical device!"); } @@ -446,16 +450,10 @@ class HelloTriangleApplication { createInfo.presentMode = presentMode; createInfo.clipped = VK_TRUE; - VkSwapchainKHR oldSwapChain = swapChain; - createInfo.oldSwapchain = oldSwapChain; - - VkSwapchainKHR newSwapChain; - if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &newSwapChain) != VK_SUCCESS) { + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { throw std::runtime_error("failed to create swap chain!"); } - swapChain = newSwapChain; - vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); swapChainImages.resize(imageCount); vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); @@ -465,9 +463,9 @@ class HelloTriangleApplication { } void createImageViews() { - swapChainImageViews.resize(swapChainImages.size(), VDeleter{device, vkDestroyImageView}); + swapChainImageViews.resize(swapChainImages.size()); - for (uint32_t i = 0; i < swapChainImages.size(); i++) { + for (size_t i = 0; i < swapChainImages.size(); i++) { VkImageViewCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; createInfo.image = swapChainImages[i]; @@ -483,7 +481,7 @@ class HelloTriangleApplication { createInfo.subresourceRange.baseArrayLayer = 0; createInfo.subresourceRange.layerCount = 1; - if (vkCreateImageView(device, &createInfo, nullptr, swapChainImageViews[i].replace()) != VK_SUCCESS) { + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { throw std::runtime_error("failed to create image views!"); } } @@ -526,7 +524,7 @@ class HelloTriangleApplication { renderPassInfo.dependencyCount = 1; renderPassInfo.pDependencies = &dependency; - if (vkCreateRenderPass(device, &renderPassInfo, nullptr, renderPass.replace()) != VK_SUCCESS) { + if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { throw std::runtime_error("failed to create render pass!"); } } @@ -544,7 +542,7 @@ class HelloTriangleApplication { layoutInfo.bindingCount = 1; layoutInfo.pBindings = &uboLayoutBinding; - if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, descriptorSetLayout.replace()) != VK_SUCCESS) { + if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &descriptorSetLayout) != VK_SUCCESS) { throw std::runtime_error("failed to create descriptor set layout!"); } } @@ -553,10 +551,8 @@ class HelloTriangleApplication { auto vertShaderCode = readFile("shaders/vert.spv"); auto fragShaderCode = readFile("shaders/frag.spv"); - VDeleter vertShaderModule{device, vkDestroyShaderModule}; - VDeleter fragShaderModule{device, vkDestroyShaderModule}; - createShaderModule(vertShaderCode, vertShaderModule); - createShaderModule(fragShaderCode, fragShaderModule); + VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); VkPipelineShaderStageCreateInfo vertShaderStageInfo = {}; vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; @@ -579,7 +575,7 @@ class HelloTriangleApplication { auto attributeDescriptions = Vertex::getAttributeDescriptions(); vertexInputInfo.vertexBindingDescriptionCount = 1; - vertexInputInfo.vertexAttributeDescriptionCount = attributeDescriptions.size(); + vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()); vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); @@ -637,13 +633,12 @@ class HelloTriangleApplication { colorBlending.blendConstants[2] = 0.0f; colorBlending.blendConstants[3] = 0.0f; - VkDescriptorSetLayout setLayouts[] = {descriptorSetLayout}; VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; pipelineLayoutInfo.setLayoutCount = 1; - pipelineLayoutInfo.pSetLayouts = setLayouts; + pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout; - if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, pipelineLayout.replace()) != VK_SUCCESS) { + if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { throw std::runtime_error("failed to create pipeline layout!"); } @@ -662,13 +657,16 @@ class HelloTriangleApplication { pipelineInfo.subpass = 0; pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; - if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, graphicsPipeline.replace()) != VK_SUCCESS) { + if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { throw std::runtime_error("failed to create graphics pipeline!"); } + + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); } void createFramebuffers() { - swapChainFramebuffers.resize(swapChainImageViews.size(), VDeleter{device, vkDestroyFramebuffer}); + swapChainFramebuffers.resize(swapChainImageViews.size()); for (size_t i = 0; i < swapChainImageViews.size(); i++) { VkImageView attachments[] = { @@ -684,7 +682,7 @@ class HelloTriangleApplication { framebufferInfo.height = swapChainExtent.height; framebufferInfo.layers = 1; - if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, swapChainFramebuffers[i].replace()) != VK_SUCCESS) { + if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { throw std::runtime_error("failed to create framebuffer!"); } } @@ -697,7 +695,7 @@ class HelloTriangleApplication { poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily; - if (vkCreateCommandPool(device, &poolInfo, nullptr, commandPool.replace()) != VK_SUCCESS) { + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { throw std::runtime_error("failed to create graphics command pool!"); } } @@ -705,8 +703,8 @@ class HelloTriangleApplication { void createVertexBuffer() { VkDeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); - VDeleter stagingBuffer{device, vkDestroyBuffer}; - VDeleter stagingBufferMemory{device, vkFreeMemory}; + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); void* data; @@ -717,13 +715,16 @@ class HelloTriangleApplication { createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, vertexBuffer, vertexBufferMemory); copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); } void createIndexBuffer() { VkDeviceSize bufferSize = sizeof(indices[0]) * indices.size(); - VDeleter stagingBuffer{device, vkDestroyBuffer}; - VDeleter stagingBufferMemory{device, vkFreeMemory}; + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); void* data; @@ -734,68 +735,78 @@ class HelloTriangleApplication { createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, indexBuffer, indexBufferMemory); copyBuffer(stagingBuffer, indexBuffer, bufferSize); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); } - void createUniformBuffer() { + void createUniformBuffers() { VkDeviceSize bufferSize = sizeof(UniformBufferObject); - createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformStagingBuffer, uniformStagingBufferMemory); - createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, uniformBuffer, uniformBufferMemory); + uniformBuffers.resize(swapChainImages.size()); + uniformBuffersMemory.resize(swapChainImages.size()); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + createBuffer(bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformBuffers[i], uniformBuffersMemory[i]); + } } void createDescriptorPool() { VkDescriptorPoolSize poolSize = {}; poolSize.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - poolSize.descriptorCount = 1; + poolSize.descriptorCount = static_cast(swapChainImages.size()); VkDescriptorPoolCreateInfo poolInfo = {}; poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; poolInfo.poolSizeCount = 1; poolInfo.pPoolSizes = &poolSize; - poolInfo.maxSets = 1; + poolInfo.maxSets = static_cast(swapChainImages.size()); - if (vkCreateDescriptorPool(device, &poolInfo, nullptr, descriptorPool.replace()) != VK_SUCCESS) { + if (vkCreateDescriptorPool(device, &poolInfo, nullptr, &descriptorPool) != VK_SUCCESS) { throw std::runtime_error("failed to create descriptor pool!"); } } - void createDescriptorSet() { - VkDescriptorSetLayout layouts[] = {descriptorSetLayout}; + void createDescriptorSets() { + std::vector layouts(swapChainImages.size(), descriptorSetLayout); VkDescriptorSetAllocateInfo allocInfo = {}; allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; allocInfo.descriptorPool = descriptorPool; - allocInfo.descriptorSetCount = 1; - allocInfo.pSetLayouts = layouts; + allocInfo.descriptorSetCount = static_cast(swapChainImages.size()); + allocInfo.pSetLayouts = layouts.data(); - if (vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet) != VK_SUCCESS) { - throw std::runtime_error("failed to allocate descriptor set!"); + descriptorSets.resize(swapChainImages.size()); + if (vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets[0]) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate descriptor sets!"); } - VkDescriptorBufferInfo bufferInfo = {}; - bufferInfo.buffer = uniformBuffer; - bufferInfo.offset = 0; - bufferInfo.range = sizeof(UniformBufferObject); - - VkWriteDescriptorSet descriptorWrite = {}; - descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; - descriptorWrite.dstSet = descriptorSet; - descriptorWrite.dstBinding = 0; - descriptorWrite.dstArrayElement = 0; - descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - descriptorWrite.descriptorCount = 1; - descriptorWrite.pBufferInfo = &bufferInfo; - - vkUpdateDescriptorSets(device, 1, &descriptorWrite, 0, nullptr); + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkDescriptorBufferInfo bufferInfo = {}; + bufferInfo.buffer = uniformBuffers[i]; + bufferInfo.offset = 0; + bufferInfo.range = sizeof(UniformBufferObject); + + VkWriteDescriptorSet descriptorWrite = {}; + descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrite.dstSet = descriptorSets[i]; + descriptorWrite.dstBinding = 0; + descriptorWrite.dstArrayElement = 0; + descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptorWrite.descriptorCount = 1; + descriptorWrite.pBufferInfo = &bufferInfo; + + vkUpdateDescriptorSets(device, 1, &descriptorWrite, 0, nullptr); + } } - void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VDeleter& buffer, VDeleter& bufferMemory) { + void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) { VkBufferCreateInfo bufferInfo = {}; bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; bufferInfo.size = size; bufferInfo.usage = usage; - bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - if (vkCreateBuffer(device, &bufferInfo, nullptr, buffer.replace()) != VK_SUCCESS) { + if (vkCreateBuffer(device, &bufferInfo, nullptr, &buffer) != VK_SUCCESS) { throw std::runtime_error("failed to create buffer!"); } @@ -807,7 +818,7 @@ class HelloTriangleApplication { allocInfo.allocationSize = memRequirements.size; allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); - if (vkAllocateMemory(device, &allocInfo, nullptr, bufferMemory.replace()) != VK_SUCCESS) { + if (vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) { throw std::runtime_error("failed to allocate buffer memory!"); } @@ -861,10 +872,6 @@ class HelloTriangleApplication { } void createCommandBuffers() { - if (commandBuffers.size() > 0) { - vkFreeCommandBuffers(device, commandPool, commandBuffers.size(), commandBuffers.data()); - } - commandBuffers.resize(swapChainFramebuffers.size()); VkCommandBufferAllocateInfo allocInfo = {}; @@ -882,7 +889,9 @@ class HelloTriangleApplication { beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; beginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT; - vkBeginCommandBuffer(commandBuffers[i], &beginInfo); + if (vkBeginCommandBuffer(commandBuffers[i], &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } VkRenderPassBeginInfo renderPassInfo = {}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; @@ -905,9 +914,9 @@ class HelloTriangleApplication { vkCmdBindIndexBuffer(commandBuffers[i], indexBuffer, 0, VK_INDEX_TYPE_UINT16); - vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr); + vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets[i], 0, nullptr); - vkCmdDrawIndexed(commandBuffers[i], indices.size(), 1, 0, 0, 0); + vkCmdDrawIndexed(commandBuffers[i], static_cast(indices.size()), 1, 0, 0, 0); vkCmdEndRenderPass(commandBuffers[i]); @@ -917,40 +926,50 @@ class HelloTriangleApplication { } } - void createSemaphores() { + void createSyncObjects() { + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + inFlightFences.resize(MAX_FRAMES_IN_FLIGHT); + VkSemaphoreCreateInfo semaphoreInfo = {}; semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; - if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, imageAvailableSemaphore.replace()) != VK_SUCCESS || - vkCreateSemaphore(device, &semaphoreInfo, nullptr, renderFinishedSemaphore.replace()) != VK_SUCCESS) { + VkFenceCreateInfo fenceInfo = {}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; - throw std::runtime_error("failed to create semaphores!"); + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS || + vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create synchronization objects for a frame!"); + } } } - void updateUniformBuffer() { + 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_cast(currentTime - startTime).count() / 1000.0f; + float time = std::chrono::duration(currentTime - startTime).count(); UniformBufferObject ubo = {}; - ubo.model = glm::rotate(glm::mat4(), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + 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; - vkMapMemory(device, uniformStagingBufferMemory, 0, sizeof(ubo), 0, &data); + vkMapMemory(device, uniformBuffersMemory[currentImage], 0, sizeof(ubo), 0, &data); memcpy(data, &ubo, sizeof(ubo)); - vkUnmapMemory(device, uniformStagingBufferMemory); - - copyBuffer(uniformStagingBuffer, uniformBuffer, sizeof(ubo)); + vkUnmapMemory(device, uniformBuffersMemory[currentImage]); } void drawFrame() { + vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, std::numeric_limits::max()); + uint32_t imageIndex; - VkResult result = vkAcquireNextImageKHR(device, swapChain, std::numeric_limits::max(), imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex); + VkResult result = vkAcquireNextImageKHR(device, swapChain, std::numeric_limits::max(), imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); if (result == VK_ERROR_OUT_OF_DATE_KHR) { recreateSwapChain(); @@ -959,10 +978,12 @@ class HelloTriangleApplication { throw std::runtime_error("failed to acquire swap chain image!"); } + updateUniformBuffer(imageIndex); + VkSubmitInfo submitInfo = {}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; - VkSemaphore waitSemaphores[] = {imageAvailableSemaphore}; + VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[currentFrame]}; VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; submitInfo.waitSemaphoreCount = 1; submitInfo.pWaitSemaphores = waitSemaphores; @@ -971,11 +992,13 @@ class HelloTriangleApplication { submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &commandBuffers[imageIndex]; - VkSemaphore signalSemaphores[] = {renderFinishedSemaphore}; + VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[currentFrame]}; submitInfo.signalSemaphoreCount = 1; submitInfo.pSignalSemaphores = signalSemaphores; - if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) { throw std::runtime_error("failed to submit draw command buffer!"); } @@ -993,26 +1016,28 @@ class HelloTriangleApplication { result = vkQueuePresentKHR(presentQueue, &presentInfo); - if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { + framebufferResized = false; recreateSwapChain(); } else if (result != VK_SUCCESS) { throw std::runtime_error("failed to present swap chain image!"); } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; } - void createShaderModule(const std::vector& code, VDeleter& shaderModule) { + VkShaderModule createShaderModule(const std::vector& code) { VkShaderModuleCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); - std::vector codeAligned(code.size() / 4 + 1); - memcpy(codeAligned.data(), code.data(), code.size()); - - createInfo.pCode = codeAligned.data(); - - if (vkCreateShaderModule(device, &createInfo, nullptr, shaderModule.replace()) != VK_SUCCESS) { + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { throw std::runtime_error("failed to create shader module!"); } + + return shaderModule; } VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { @@ -1048,9 +1073,12 @@ class HelloTriangleApplication { return capabilities.currentExtent; } else { int width, height; - glfwGetWindowSize(window, &width, &height); + glfwGetFramebufferSize(window, &width, &height); - VkExtent2D actualExtent = {width, height}; + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; actualExtent.width = std::max(capabilities.minImageExtent.width, std::min(capabilities.maxImageExtent.width, actualExtent.width)); actualExtent.height = std::max(capabilities.minImageExtent.height, std::min(capabilities.maxImageExtent.height, actualExtent.height)); @@ -1146,15 +1174,11 @@ class HelloTriangleApplication { } std::vector getRequiredExtensions() { - std::vector extensions; - - unsigned int glfwExtensionCount = 0; + uint32_t glfwExtensionCount = 0; const char** glfwExtensions; glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - for (unsigned int i = 0; i < glfwExtensionCount; i++) { - extensions.push_back(glfwExtensions[i]); - } + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); if (enableValidationLayers) { extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); @@ -1218,10 +1242,10 @@ int main() { try { app.run(); - } catch (const std::runtime_error& e) { + } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; -} \ No newline at end of file +} diff --git a/code/texture_image.cpp b/code/23_texture_image.cpp similarity index 76% rename from code/texture_image.cpp rename to code/23_texture_image.cpp index e1fdfff8..1f23a27c 100644 --- a/code/texture_image.cpp +++ b/code/23_texture_image.cpp @@ -9,19 +9,21 @@ #include #include -#include -#include -#include #include +#include #include +#include #include #include +#include #include #include const int WIDTH = 800; const int HEIGHT = 600; +const int MAX_FRAMES_IN_FLIGHT = 2; + const std::vector validationLayers = { "VK_LAYER_LUNARG_standard_validation" }; @@ -52,64 +54,6 @@ void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT } } -template -class VDeleter { -public: - VDeleter() : VDeleter([](T, VkAllocationCallbacks*) {}) {} - - VDeleter(std::function deletef) { - this->deleter = [=](T obj) { deletef(obj, nullptr); }; - } - - VDeleter(const VDeleter& instance, std::function deletef) { - this->deleter = [&instance, deletef](T obj) { deletef(instance, obj, nullptr); }; - } - - VDeleter(const VDeleter& device, std::function deletef) { - this->deleter = [&device, deletef](T obj) { deletef(device, obj, nullptr); }; - } - - ~VDeleter() { - cleanup(); - } - - const T* operator &() const { - return &object; - } - - T* replace() { - cleanup(); - return &object; - } - - operator T() const { - return object; - } - - void operator=(T rhs) { - if (rhs != object) { - cleanup(); - object = rhs; - } - } - - template - bool operator==(V rhs) { - return object == T(rhs); - } - -private: - T object{VK_NULL_HANDLE}; - std::function deleter; - - void cleanup() { - if (object != VK_NULL_HANDLE) { - deleter(object); - } - object = VK_NULL_HANDLE; - } -}; - struct QueueFamilyIndices { int graphicsFamily = -1; int presentFamily = -1; @@ -178,55 +122,58 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugReportCallbackEXT callback; + VkSurfaceKHR surface; VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; - VDeleter device{vkDestroyDevice}; + VkDevice device; VkQueue graphicsQueue; VkQueue presentQueue; - - VDeleter swapChain{device, vkDestroySwapchainKHR}; + + VkSwapchainKHR swapChain; std::vector swapChainImages; VkFormat swapChainImageFormat; VkExtent2D swapChainExtent; - std::vector> swapChainImageViews; - std::vector> swapChainFramebuffers; - - VDeleter renderPass{device, vkDestroyRenderPass}; - VDeleter descriptorSetLayout{device, vkDestroyDescriptorSetLayout}; - VDeleter pipelineLayout{device, vkDestroyPipelineLayout}; - VDeleter graphicsPipeline{device, vkDestroyPipeline}; - - VDeleter commandPool{device, vkDestroyCommandPool}; - - VDeleter textureImage{device, vkDestroyImage}; - VDeleter textureImageMemory{device, vkFreeMemory}; - - VDeleter vertexBuffer{device, vkDestroyBuffer}; - VDeleter vertexBufferMemory{device, vkFreeMemory}; - VDeleter indexBuffer{device, vkDestroyBuffer}; - VDeleter indexBufferMemory{device, vkFreeMemory}; - - VDeleter uniformStagingBuffer{device, vkDestroyBuffer}; - VDeleter uniformStagingBufferMemory{device, vkFreeMemory}; - VDeleter uniformBuffer{device, vkDestroyBuffer}; - VDeleter uniformBufferMemory{device, vkFreeMemory}; - - VDeleter descriptorPool{device, vkDestroyDescriptorPool}; - VkDescriptorSet descriptorSet; + std::vector swapChainImageViews; + std::vector swapChainFramebuffers; + + VkRenderPass renderPass; + VkDescriptorSetLayout descriptorSetLayout; + VkPipelineLayout pipelineLayout; + VkPipeline graphicsPipeline; + + VkCommandPool commandPool; + + VkImage textureImage; + VkDeviceMemory textureImageMemory; + + VkBuffer vertexBuffer; + VkDeviceMemory vertexBufferMemory; + VkBuffer indexBuffer; + VkDeviceMemory indexBufferMemory; + + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + + VkDescriptorPool descriptorPool; + std::vector descriptorSets; std::vector commandBuffers; - VDeleter imageAvailableSemaphore{device, vkDestroySemaphore}; - VDeleter renderFinishedSemaphore{device, vkDestroySemaphore}; + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + std::vector inFlightFences; + size_t currentFrame = 0; + + bool framebufferResized = false; void initWindow() { glfwInit(); @@ -234,9 +181,13 @@ class HelloTriangleApplication { glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetWindowSizeCallback(window, HelloTriangleApplication::onWindowResized); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; } void initVulkan() { @@ -255,38 +206,94 @@ class HelloTriangleApplication { createTextureImage(); createVertexBuffer(); createIndexBuffer(); - createUniformBuffer(); + createUniformBuffers(); createDescriptorPool(); - createDescriptorSet(); + createDescriptorSets(); createCommandBuffers(); - createSemaphores(); + createSyncObjects(); } void mainLoop() { while (!glfwWindowShouldClose(window)) { glfwPollEvents(); - - updateUniformBuffer(); drawFrame(); } vkDeviceWaitIdle(device); + } - glfwDestroyWindow(window); + void cleanupSwapChain() { + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } - glfwTerminate(); + vkFreeCommandBuffers(device, commandPool, static_cast(commandBuffers.size()), commandBuffers.data()); + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); } - static void onWindowResized(GLFWwindow* window, int width, int height) { - if (width == 0 || height == 0) return; - - HelloTriangleApplication* app = reinterpret_cast(glfwGetWindowUserPointer(window)); - app->recreateSwapChain(); + void cleanup() { + cleanupSwapChain(); + + vkDestroyImage(device, textureImage, nullptr); + vkFreeMemory(device, textureImageMemory, nullptr); + + vkDestroyDescriptorPool(device, descriptorPool, nullptr); + + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + vkDestroyBuffer(device, uniformBuffers[i], nullptr); + vkFreeMemory(device, uniformBuffersMemory[i], nullptr); + } + + vkDestroyBuffer(device, indexBuffer, nullptr); + vkFreeMemory(device, indexBufferMemory, nullptr); + + vkDestroyBuffer(device, vertexBuffer, nullptr); + vkFreeMemory(device, vertexBufferMemory, nullptr); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + vkDestroyFence(device, inFlightFences[i], nullptr); + } + + vkDestroyCommandPool(device, commandPool, nullptr); + + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugReportCallbackEXT(instance, callback, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); } void recreateSwapChain() { + int width = 0, height = 0; + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + vkDeviceWaitIdle(device); + cleanupSwapChain(); + createSwapChain(); createImageViews(); createRenderPass(); @@ -313,17 +320,17 @@ class HelloTriangleApplication { createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } @@ -336,13 +343,13 @@ class HelloTriangleApplication { createInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT; createInfo.pfnCallback = debugCallback; - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { + if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, &callback) != VK_SUCCESS) { throw std::runtime_error("failed to set up debug callback!"); } } void createSurface() { - if (glfwCreateWindowSurface(instance, window, nullptr, surface.replace()) != VK_SUCCESS) { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { throw std::runtime_error("failed to create window surface!"); } } @@ -391,22 +398,22 @@ class HelloTriangleApplication { VkDeviceCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); createInfo.pQueueCreateInfos = queueCreateInfos.data(); - createInfo.queueCreateInfoCount = (uint32_t) queueCreateInfos.size(); createInfo.pEnabledFeatures = &deviceFeatures; - createInfo.enabledExtensionCount = deviceExtensions.size(); + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); createInfo.ppEnabledExtensionNames = deviceExtensions.data(); if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateDevice(physicalDevice, &createInfo, nullptr, device.replace()) != VK_SUCCESS) { + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { throw std::runtime_error("failed to create logical device!"); } @@ -453,16 +460,10 @@ class HelloTriangleApplication { createInfo.presentMode = presentMode; createInfo.clipped = VK_TRUE; - VkSwapchainKHR oldSwapChain = swapChain; - createInfo.oldSwapchain = oldSwapChain; - - VkSwapchainKHR newSwapChain; - if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &newSwapChain) != VK_SUCCESS) { + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { throw std::runtime_error("failed to create swap chain!"); } - swapChain = newSwapChain; - vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); swapChainImages.resize(imageCount); vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); @@ -472,9 +473,9 @@ class HelloTriangleApplication { } void createImageViews() { - swapChainImageViews.resize(swapChainImages.size(), VDeleter{device, vkDestroyImageView}); + swapChainImageViews.resize(swapChainImages.size()); - for (uint32_t i = 0; i < swapChainImages.size(); i++) { + for (size_t i = 0; i < swapChainImages.size(); i++) { VkImageViewCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; createInfo.image = swapChainImages[i]; @@ -490,7 +491,7 @@ class HelloTriangleApplication { createInfo.subresourceRange.baseArrayLayer = 0; createInfo.subresourceRange.layerCount = 1; - if (vkCreateImageView(device, &createInfo, nullptr, swapChainImageViews[i].replace()) != VK_SUCCESS) { + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { throw std::runtime_error("failed to create image views!"); } } @@ -533,7 +534,7 @@ class HelloTriangleApplication { renderPassInfo.dependencyCount = 1; renderPassInfo.pDependencies = &dependency; - if (vkCreateRenderPass(device, &renderPassInfo, nullptr, renderPass.replace()) != VK_SUCCESS) { + if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { throw std::runtime_error("failed to create render pass!"); } } @@ -551,7 +552,7 @@ class HelloTriangleApplication { layoutInfo.bindingCount = 1; layoutInfo.pBindings = &uboLayoutBinding; - if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, descriptorSetLayout.replace()) != VK_SUCCESS) { + if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &descriptorSetLayout) != VK_SUCCESS) { throw std::runtime_error("failed to create descriptor set layout!"); } } @@ -560,10 +561,8 @@ class HelloTriangleApplication { auto vertShaderCode = readFile("shaders/vert.spv"); auto fragShaderCode = readFile("shaders/frag.spv"); - VDeleter vertShaderModule{device, vkDestroyShaderModule}; - VDeleter fragShaderModule{device, vkDestroyShaderModule}; - createShaderModule(vertShaderCode, vertShaderModule); - createShaderModule(fragShaderCode, fragShaderModule); + VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); VkPipelineShaderStageCreateInfo vertShaderStageInfo = {}; vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; @@ -586,7 +585,7 @@ class HelloTriangleApplication { auto attributeDescriptions = Vertex::getAttributeDescriptions(); vertexInputInfo.vertexBindingDescriptionCount = 1; - vertexInputInfo.vertexAttributeDescriptionCount = attributeDescriptions.size(); + vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()); vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); @@ -644,13 +643,12 @@ class HelloTriangleApplication { colorBlending.blendConstants[2] = 0.0f; colorBlending.blendConstants[3] = 0.0f; - VkDescriptorSetLayout setLayouts[] = {descriptorSetLayout}; VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; pipelineLayoutInfo.setLayoutCount = 1; - pipelineLayoutInfo.pSetLayouts = setLayouts; + pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout; - if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, pipelineLayout.replace()) != VK_SUCCESS) { + if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { throw std::runtime_error("failed to create pipeline layout!"); } @@ -669,13 +667,16 @@ class HelloTriangleApplication { pipelineInfo.subpass = 0; pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; - if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, graphicsPipeline.replace()) != VK_SUCCESS) { + if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { throw std::runtime_error("failed to create graphics pipeline!"); } + + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); } void createFramebuffers() { - swapChainFramebuffers.resize(swapChainImageViews.size(), VDeleter{device, vkDestroyFramebuffer}); + swapChainFramebuffers.resize(swapChainImageViews.size()); for (size_t i = 0; i < swapChainImageViews.size(); i++) { VkImageView attachments[] = { @@ -691,7 +692,7 @@ class HelloTriangleApplication { framebufferInfo.height = swapChainExtent.height; framebufferInfo.layers = 1; - if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, swapChainFramebuffers[i].replace()) != VK_SUCCESS) { + if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { throw std::runtime_error("failed to create framebuffer!"); } } @@ -704,7 +705,7 @@ class HelloTriangleApplication { poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily; - if (vkCreateCommandPool(device, &poolInfo, nullptr, commandPool.replace()) != VK_SUCCESS) { + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { throw std::runtime_error("failed to create graphics command pool!"); } } @@ -718,45 +719,28 @@ class HelloTriangleApplication { throw std::runtime_error("failed to load texture image!"); } - VDeleter stagingImage{device, vkDestroyImage}; - VDeleter stagingImageMemory{device, vkFreeMemory}; - createImage(texWidth, texHeight, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_LINEAR, VK_IMAGE_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingImage, stagingImageMemory); - - VkImageSubresource subresource = {}; - subresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - subresource.mipLevel = 0; - subresource.arrayLayer = 0; - - VkSubresourceLayout stagingImageLayout; - vkGetImageSubresourceLayout(device, stagingImage, &subresource, &stagingImageLayout); + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); void* data; - vkMapMemory(device, stagingImageMemory, 0, imageSize, 0, &data); - - if (stagingImageLayout.rowPitch == texWidth * 4) { - memcpy(data, pixels, (size_t) imageSize); - } else { - uint8_t* dataBytes = reinterpret_cast(data); - - for (int y = 0; y < texHeight; y++) { - memcpy(&dataBytes[y * stagingImageLayout.rowPitch], &pixels[y * texWidth * 4], texWidth * 4); - } - } + vkMapMemory(device, stagingBufferMemory, 0, imageSize, 0, &data); + memcpy(data, pixels, static_cast(imageSize)); + vkUnmapMemory(device, stagingBufferMemory); - vkUnmapMemory(device, stagingImageMemory); - stbi_image_free(pixels); createImage(texWidth, texHeight, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, textureImage, textureImageMemory); - transitionImageLayout(stagingImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_PREINITIALIZED, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); - transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_PREINITIALIZED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); - copyImage(stagingImage, textureImage, texWidth, texHeight); - + transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); } - void createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VDeleter& image, VDeleter& imageMemory) { + void createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage& image, VkDeviceMemory& imageMemory) { VkImageCreateInfo imageInfo = {}; imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; imageInfo.imageType = VK_IMAGE_TYPE_2D; @@ -767,12 +751,12 @@ class HelloTriangleApplication { imageInfo.arrayLayers = 1; imageInfo.format = format; imageInfo.tiling = tiling; - imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED; + imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; imageInfo.usage = usage; imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; - imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - if (vkCreateImage(device, &imageInfo, nullptr, image.replace()) != VK_SUCCESS) { + if (vkCreateImage(device, &imageInfo, nullptr, &image) != VK_SUCCESS) { throw std::runtime_error("failed to create image!"); } @@ -784,7 +768,7 @@ class HelloTriangleApplication { allocInfo.allocationSize = memRequirements.size; allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); - if (vkAllocateMemory(device, &allocInfo, nullptr, imageMemory.replace()) != VK_SUCCESS) { + if (vkAllocateMemory(device, &allocInfo, nullptr, &imageMemory) != VK_SUCCESS) { throw std::runtime_error("failed to allocate image memory!"); } @@ -807,22 +791,28 @@ class HelloTriangleApplication { barrier.subresourceRange.baseArrayLayer = 0; barrier.subresourceRange.layerCount = 1; - if (oldLayout == VK_IMAGE_LAYOUT_PREINITIALIZED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL) { - barrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT; - barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; - } else if (oldLayout == VK_IMAGE_LAYOUT_PREINITIALIZED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { - barrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT; + VkPipelineStageFlags sourceStage; + VkPipelineStageFlags destinationStage; + + if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { + barrier.srcAccessMask = 0; barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + + sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT; } else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + + sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; } else { throw std::invalid_argument("unsupported layout transition!"); } - + vkCmdPipelineBarrier( commandBuffer, - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + sourceStage, destinationStage, 0, 0, nullptr, 0, nullptr, @@ -832,30 +822,25 @@ class HelloTriangleApplication { endSingleTimeCommands(commandBuffer); } - void copyImage(VkImage srcImage, VkImage dstImage, uint32_t width, uint32_t height) { + void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) { VkCommandBuffer commandBuffer = beginSingleTimeCommands(); - VkImageSubresourceLayers subResource = {}; - subResource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - subResource.baseArrayLayer = 0; - subResource.mipLevel = 0; - subResource.layerCount = 1; - - VkImageCopy region = {}; - region.srcSubresource = subResource; - region.dstSubresource = subResource; - region.srcOffset = {0, 0, 0}; - region.dstOffset = {0, 0, 0}; - region.extent.width = width; - region.extent.height = height; - region.extent.depth = 1; - - vkCmdCopyImage( - commandBuffer, - srcImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - dstImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - 1, ®ion - ); + VkBufferImageCopy region = {}; + region.bufferOffset = 0; + region.bufferRowLength = 0; + region.bufferImageHeight = 0; + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.mipLevel = 0; + region.imageSubresource.baseArrayLayer = 0; + region.imageSubresource.layerCount = 1; + region.imageOffset = {0, 0, 0}; + region.imageExtent = { + width, + height, + 1 + }; + + vkCmdCopyBufferToImage(commandBuffer, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); endSingleTimeCommands(commandBuffer); } @@ -863,8 +848,8 @@ class HelloTriangleApplication { void createVertexBuffer() { VkDeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); - VDeleter stagingBuffer{device, vkDestroyBuffer}; - VDeleter stagingBufferMemory{device, vkFreeMemory}; + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); void* data; @@ -875,13 +860,16 @@ class HelloTriangleApplication { createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, vertexBuffer, vertexBufferMemory); copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); } void createIndexBuffer() { VkDeviceSize bufferSize = sizeof(indices[0]) * indices.size(); - VDeleter stagingBuffer{device, vkDestroyBuffer}; - VDeleter stagingBufferMemory{device, vkFreeMemory}; + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); void* data; @@ -892,68 +880,78 @@ class HelloTriangleApplication { createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, indexBuffer, indexBufferMemory); copyBuffer(stagingBuffer, indexBuffer, bufferSize); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); } - void createUniformBuffer() { + void createUniformBuffers() { VkDeviceSize bufferSize = sizeof(UniformBufferObject); - createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformStagingBuffer, uniformStagingBufferMemory); - createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, uniformBuffer, uniformBufferMemory); + uniformBuffers.resize(swapChainImages.size()); + uniformBuffersMemory.resize(swapChainImages.size()); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + createBuffer(bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformBuffers[i], uniformBuffersMemory[i]); + } } void createDescriptorPool() { VkDescriptorPoolSize poolSize = {}; poolSize.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - poolSize.descriptorCount = 1; + poolSize.descriptorCount = static_cast(swapChainImages.size()); VkDescriptorPoolCreateInfo poolInfo = {}; poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; poolInfo.poolSizeCount = 1; poolInfo.pPoolSizes = &poolSize; - poolInfo.maxSets = 1; + poolInfo.maxSets = static_cast(swapChainImages.size()); - if (vkCreateDescriptorPool(device, &poolInfo, nullptr, descriptorPool.replace()) != VK_SUCCESS) { + if (vkCreateDescriptorPool(device, &poolInfo, nullptr, &descriptorPool) != VK_SUCCESS) { throw std::runtime_error("failed to create descriptor pool!"); } } - void createDescriptorSet() { - VkDescriptorSetLayout layouts[] = {descriptorSetLayout}; + void createDescriptorSets() { + std::vector layouts(swapChainImages.size(), descriptorSetLayout); VkDescriptorSetAllocateInfo allocInfo = {}; allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; allocInfo.descriptorPool = descriptorPool; - allocInfo.descriptorSetCount = 1; - allocInfo.pSetLayouts = layouts; + allocInfo.descriptorSetCount = static_cast(swapChainImages.size()); + allocInfo.pSetLayouts = layouts.data(); - if (vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet) != VK_SUCCESS) { - throw std::runtime_error("failed to allocate descriptor set!"); + descriptorSets.resize(swapChainImages.size()); + if (vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets[0]) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate descriptor sets!"); } - VkDescriptorBufferInfo bufferInfo = {}; - bufferInfo.buffer = uniformBuffer; - bufferInfo.offset = 0; - bufferInfo.range = sizeof(UniformBufferObject); - - VkWriteDescriptorSet descriptorWrite = {}; - descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; - descriptorWrite.dstSet = descriptorSet; - descriptorWrite.dstBinding = 0; - descriptorWrite.dstArrayElement = 0; - descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - descriptorWrite.descriptorCount = 1; - descriptorWrite.pBufferInfo = &bufferInfo; - - vkUpdateDescriptorSets(device, 1, &descriptorWrite, 0, nullptr); + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkDescriptorBufferInfo bufferInfo = {}; + bufferInfo.buffer = uniformBuffers[i]; + bufferInfo.offset = 0; + bufferInfo.range = sizeof(UniformBufferObject); + + VkWriteDescriptorSet descriptorWrite = {}; + descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrite.dstSet = descriptorSets[i]; + descriptorWrite.dstBinding = 0; + descriptorWrite.dstArrayElement = 0; + descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptorWrite.descriptorCount = 1; + descriptorWrite.pBufferInfo = &bufferInfo; + + vkUpdateDescriptorSets(device, 1, &descriptorWrite, 0, nullptr); + } } - void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VDeleter& buffer, VDeleter& bufferMemory) { + void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) { VkBufferCreateInfo bufferInfo = {}; bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; bufferInfo.size = size; bufferInfo.usage = usage; - bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - if (vkCreateBuffer(device, &bufferInfo, nullptr, buffer.replace()) != VK_SUCCESS) { + if (vkCreateBuffer(device, &bufferInfo, nullptr, &buffer) != VK_SUCCESS) { throw std::runtime_error("failed to create buffer!"); } @@ -965,7 +963,7 @@ class HelloTriangleApplication { allocInfo.allocationSize = memRequirements.size; allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); - if (vkAllocateMemory(device, &allocInfo, nullptr, bufferMemory.replace()) != VK_SUCCESS) { + if (vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) { throw std::runtime_error("failed to allocate buffer memory!"); } @@ -1029,10 +1027,6 @@ class HelloTriangleApplication { } void createCommandBuffers() { - if (commandBuffers.size() > 0) { - vkFreeCommandBuffers(device, commandPool, commandBuffers.size(), commandBuffers.data()); - } - commandBuffers.resize(swapChainFramebuffers.size()); VkCommandBufferAllocateInfo allocInfo = {}; @@ -1050,7 +1044,9 @@ class HelloTriangleApplication { beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; beginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT; - vkBeginCommandBuffer(commandBuffers[i], &beginInfo); + if (vkBeginCommandBuffer(commandBuffers[i], &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } VkRenderPassBeginInfo renderPassInfo = {}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; @@ -1073,9 +1069,9 @@ class HelloTriangleApplication { vkCmdBindIndexBuffer(commandBuffers[i], indexBuffer, 0, VK_INDEX_TYPE_UINT16); - vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr); + vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets[i], 0, nullptr); - vkCmdDrawIndexed(commandBuffers[i], indices.size(), 1, 0, 0, 0); + vkCmdDrawIndexed(commandBuffers[i], static_cast(indices.size()), 1, 0, 0, 0); vkCmdEndRenderPass(commandBuffers[i]); @@ -1085,40 +1081,50 @@ class HelloTriangleApplication { } } - void createSemaphores() { + void createSyncObjects() { + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + inFlightFences.resize(MAX_FRAMES_IN_FLIGHT); + VkSemaphoreCreateInfo semaphoreInfo = {}; semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; - if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, imageAvailableSemaphore.replace()) != VK_SUCCESS || - vkCreateSemaphore(device, &semaphoreInfo, nullptr, renderFinishedSemaphore.replace()) != VK_SUCCESS) { + VkFenceCreateInfo fenceInfo = {}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; - throw std::runtime_error("failed to create semaphores!"); + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS || + vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create synchronization objects for a frame!"); + } } } - void updateUniformBuffer() { + 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_cast(currentTime - startTime).count() / 1000.0f; + float time = std::chrono::duration(currentTime - startTime).count(); UniformBufferObject ubo = {}; - ubo.model = glm::rotate(glm::mat4(), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + 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; - vkMapMemory(device, uniformStagingBufferMemory, 0, sizeof(ubo), 0, &data); + vkMapMemory(device, uniformBuffersMemory[currentImage], 0, sizeof(ubo), 0, &data); memcpy(data, &ubo, sizeof(ubo)); - vkUnmapMemory(device, uniformStagingBufferMemory); - - copyBuffer(uniformStagingBuffer, uniformBuffer, sizeof(ubo)); + vkUnmapMemory(device, uniformBuffersMemory[currentImage]); } void drawFrame() { + vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, std::numeric_limits::max()); + uint32_t imageIndex; - VkResult result = vkAcquireNextImageKHR(device, swapChain, std::numeric_limits::max(), imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex); + VkResult result = vkAcquireNextImageKHR(device, swapChain, std::numeric_limits::max(), imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); if (result == VK_ERROR_OUT_OF_DATE_KHR) { recreateSwapChain(); @@ -1127,10 +1133,12 @@ class HelloTriangleApplication { throw std::runtime_error("failed to acquire swap chain image!"); } + updateUniformBuffer(imageIndex); + VkSubmitInfo submitInfo = {}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; - VkSemaphore waitSemaphores[] = {imageAvailableSemaphore}; + VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[currentFrame]}; VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; submitInfo.waitSemaphoreCount = 1; submitInfo.pWaitSemaphores = waitSemaphores; @@ -1139,11 +1147,13 @@ class HelloTriangleApplication { submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &commandBuffers[imageIndex]; - VkSemaphore signalSemaphores[] = {renderFinishedSemaphore}; + VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[currentFrame]}; submitInfo.signalSemaphoreCount = 1; submitInfo.pSignalSemaphores = signalSemaphores; - if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) { throw std::runtime_error("failed to submit draw command buffer!"); } @@ -1161,26 +1171,28 @@ class HelloTriangleApplication { result = vkQueuePresentKHR(presentQueue, &presentInfo); - if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { + framebufferResized = false; recreateSwapChain(); } else if (result != VK_SUCCESS) { throw std::runtime_error("failed to present swap chain image!"); } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; } - void createShaderModule(const std::vector& code, VDeleter& shaderModule) { + VkShaderModule createShaderModule(const std::vector& code) { VkShaderModuleCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); - std::vector codeAligned(code.size() / 4 + 1); - memcpy(codeAligned.data(), code.data(), code.size()); - - createInfo.pCode = codeAligned.data(); - - if (vkCreateShaderModule(device, &createInfo, nullptr, shaderModule.replace()) != VK_SUCCESS) { + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { throw std::runtime_error("failed to create shader module!"); } + + return shaderModule; } VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { @@ -1216,9 +1228,12 @@ class HelloTriangleApplication { return capabilities.currentExtent; } else { int width, height; - glfwGetWindowSize(window, &width, &height); + glfwGetFramebufferSize(window, &width, &height); - VkExtent2D actualExtent = {width, height}; + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; actualExtent.width = std::max(capabilities.minImageExtent.width, std::min(capabilities.maxImageExtent.width, actualExtent.width)); actualExtent.height = std::max(capabilities.minImageExtent.height, std::min(capabilities.maxImageExtent.height, actualExtent.height)); @@ -1314,15 +1329,11 @@ class HelloTriangleApplication { } std::vector getRequiredExtensions() { - std::vector extensions; - - unsigned int glfwExtensionCount = 0; + uint32_t glfwExtensionCount = 0; const char** glfwExtensions; glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - for (unsigned int i = 0; i < glfwExtensionCount; i++) { - extensions.push_back(glfwExtensions[i]); - } + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); if (enableValidationLayers) { extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); @@ -1386,10 +1397,10 @@ int main() { try { app.run(); - } catch (const std::runtime_error& e) { + } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; -} \ No newline at end of file +} diff --git a/code/sampler.cpp b/code/24_sampler.cpp similarity index 75% rename from code/sampler.cpp rename to code/24_sampler.cpp index e24a4fa8..22356531 100644 --- a/code/sampler.cpp +++ b/code/24_sampler.cpp @@ -9,19 +9,21 @@ #include #include -#include -#include -#include #include +#include #include +#include #include #include +#include #include #include const int WIDTH = 800; const int HEIGHT = 600; +const int MAX_FRAMES_IN_FLIGHT = 2; + const std::vector validationLayers = { "VK_LAYER_LUNARG_standard_validation" }; @@ -52,64 +54,6 @@ void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT } } -template -class VDeleter { -public: - VDeleter() : VDeleter([](T, VkAllocationCallbacks*) {}) {} - - VDeleter(std::function deletef) { - this->deleter = [=](T obj) { deletef(obj, nullptr); }; - } - - VDeleter(const VDeleter& instance, std::function deletef) { - this->deleter = [&instance, deletef](T obj) { deletef(instance, obj, nullptr); }; - } - - VDeleter(const VDeleter& device, std::function deletef) { - this->deleter = [&device, deletef](T obj) { deletef(device, obj, nullptr); }; - } - - ~VDeleter() { - cleanup(); - } - - const T* operator &() const { - return &object; - } - - T* replace() { - cleanup(); - return &object; - } - - operator T() const { - return object; - } - - void operator=(T rhs) { - if (rhs != object) { - cleanup(); - object = rhs; - } - } - - template - bool operator==(V rhs) { - return object == T(rhs); - } - -private: - T object{VK_NULL_HANDLE}; - std::function deleter; - - void cleanup() { - if (object != VK_NULL_HANDLE) { - deleter(object); - } - object = VK_NULL_HANDLE; - } -}; - struct QueueFamilyIndices { int graphicsFamily = -1; int presentFamily = -1; @@ -178,57 +122,60 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugReportCallbackEXT callback; + VkSurfaceKHR surface; VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; - VDeleter device{vkDestroyDevice}; + VkDevice device; VkQueue graphicsQueue; VkQueue presentQueue; - - VDeleter swapChain{device, vkDestroySwapchainKHR}; + + VkSwapchainKHR swapChain; std::vector swapChainImages; VkFormat swapChainImageFormat; VkExtent2D swapChainExtent; - std::vector> swapChainImageViews; - std::vector> swapChainFramebuffers; - - VDeleter renderPass{device, vkDestroyRenderPass}; - VDeleter descriptorSetLayout{device, vkDestroyDescriptorSetLayout}; - VDeleter pipelineLayout{device, vkDestroyPipelineLayout}; - VDeleter graphicsPipeline{device, vkDestroyPipeline}; - - VDeleter commandPool{device, vkDestroyCommandPool}; - - VDeleter textureImage{device, vkDestroyImage}; - VDeleter textureImageMemory{device, vkFreeMemory}; - VDeleter textureImageView{device, vkDestroyImageView}; - VDeleter textureSampler{device, vkDestroySampler}; - - VDeleter vertexBuffer{device, vkDestroyBuffer}; - VDeleter vertexBufferMemory{device, vkFreeMemory}; - VDeleter indexBuffer{device, vkDestroyBuffer}; - VDeleter indexBufferMemory{device, vkFreeMemory}; - - VDeleter uniformStagingBuffer{device, vkDestroyBuffer}; - VDeleter uniformStagingBufferMemory{device, vkFreeMemory}; - VDeleter uniformBuffer{device, vkDestroyBuffer}; - VDeleter uniformBufferMemory{device, vkFreeMemory}; - - VDeleter descriptorPool{device, vkDestroyDescriptorPool}; - VkDescriptorSet descriptorSet; + std::vector swapChainImageViews; + std::vector swapChainFramebuffers; + + VkRenderPass renderPass; + VkDescriptorSetLayout descriptorSetLayout; + VkPipelineLayout pipelineLayout; + VkPipeline graphicsPipeline; + + VkCommandPool commandPool; + + VkImage textureImage; + VkDeviceMemory textureImageMemory; + VkImageView textureImageView; + VkSampler textureSampler; + + VkBuffer vertexBuffer; + VkDeviceMemory vertexBufferMemory; + VkBuffer indexBuffer; + VkDeviceMemory indexBufferMemory; + + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + + VkDescriptorPool descriptorPool; + std::vector descriptorSets; std::vector commandBuffers; - VDeleter imageAvailableSemaphore{device, vkDestroySemaphore}; - VDeleter renderFinishedSemaphore{device, vkDestroySemaphore}; + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + std::vector inFlightFences; + size_t currentFrame = 0; + + bool framebufferResized = false; void initWindow() { glfwInit(); @@ -236,9 +183,13 @@ class HelloTriangleApplication { glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetWindowSizeCallback(window, HelloTriangleApplication::onWindowResized); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; } void initVulkan() { @@ -259,38 +210,97 @@ class HelloTriangleApplication { createTextureSampler(); createVertexBuffer(); createIndexBuffer(); - createUniformBuffer(); + createUniformBuffers(); createDescriptorPool(); - createDescriptorSet(); + createDescriptorSets(); createCommandBuffers(); - createSemaphores(); + createSyncObjects(); } void mainLoop() { while (!glfwWindowShouldClose(window)) { glfwPollEvents(); - - updateUniformBuffer(); drawFrame(); } vkDeviceWaitIdle(device); + } - glfwDestroyWindow(window); + void cleanupSwapChain() { + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } - glfwTerminate(); + vkFreeCommandBuffers(device, commandPool, static_cast(commandBuffers.size()), commandBuffers.data()); + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); } - static void onWindowResized(GLFWwindow* window, int width, int height) { - if (width == 0 || height == 0) return; - - HelloTriangleApplication* app = reinterpret_cast(glfwGetWindowUserPointer(window)); - app->recreateSwapChain(); + void cleanup() { + cleanupSwapChain(); + + vkDestroySampler(device, textureSampler, nullptr); + vkDestroyImageView(device, textureImageView, nullptr); + + vkDestroyImage(device, textureImage, nullptr); + vkFreeMemory(device, textureImageMemory, nullptr); + + vkDestroyDescriptorPool(device, descriptorPool, nullptr); + + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + vkDestroyBuffer(device, uniformBuffers[i], nullptr); + vkFreeMemory(device, uniformBuffersMemory[i], nullptr); + } + + vkDestroyBuffer(device, indexBuffer, nullptr); + vkFreeMemory(device, indexBufferMemory, nullptr); + + vkDestroyBuffer(device, vertexBuffer, nullptr); + vkFreeMemory(device, vertexBufferMemory, nullptr); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + vkDestroyFence(device, inFlightFences[i], nullptr); + } + + vkDestroyCommandPool(device, commandPool, nullptr); + + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugReportCallbackEXT(instance, callback, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); } void recreateSwapChain() { + int width = 0, height = 0; + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + vkDeviceWaitIdle(device); + cleanupSwapChain(); + createSwapChain(); createImageViews(); createRenderPass(); @@ -317,17 +327,17 @@ class HelloTriangleApplication { createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } @@ -340,13 +350,13 @@ class HelloTriangleApplication { createInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT; createInfo.pfnCallback = debugCallback; - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { + if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, &callback) != VK_SUCCESS) { throw std::runtime_error("failed to set up debug callback!"); } } void createSurface() { - if (glfwCreateWindowSurface(instance, window, nullptr, surface.replace()) != VK_SUCCESS) { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { throw std::runtime_error("failed to create window surface!"); } } @@ -391,26 +401,27 @@ class HelloTriangleApplication { } VkPhysicalDeviceFeatures deviceFeatures = {}; + deviceFeatures.samplerAnisotropy = VK_TRUE; VkDeviceCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); createInfo.pQueueCreateInfos = queueCreateInfos.data(); - createInfo.queueCreateInfoCount = (uint32_t) queueCreateInfos.size(); createInfo.pEnabledFeatures = &deviceFeatures; - createInfo.enabledExtensionCount = deviceExtensions.size(); + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); createInfo.ppEnabledExtensionNames = deviceExtensions.data(); if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateDevice(physicalDevice, &createInfo, nullptr, device.replace()) != VK_SUCCESS) { + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { throw std::runtime_error("failed to create logical device!"); } @@ -457,16 +468,10 @@ class HelloTriangleApplication { createInfo.presentMode = presentMode; createInfo.clipped = VK_TRUE; - VkSwapchainKHR oldSwapChain = swapChain; - createInfo.oldSwapchain = oldSwapChain; - - VkSwapchainKHR newSwapChain; - if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &newSwapChain) != VK_SUCCESS) { + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { throw std::runtime_error("failed to create swap chain!"); } - swapChain = newSwapChain; - vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); swapChainImages.resize(imageCount); vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); @@ -476,10 +481,10 @@ class HelloTriangleApplication { } void createImageViews() { - swapChainImageViews.resize(swapChainImages.size(), VDeleter{device, vkDestroyImageView}); + swapChainImageViews.resize(swapChainImages.size()); - for (uint32_t i = 0; i < swapChainImages.size(); i++) { - createImageView(swapChainImages[i], swapChainImageFormat, swapChainImageViews[i]); + for (size_t i = 0; i < swapChainImages.size(); i++) { + swapChainImageViews[i] = createImageView(swapChainImages[i], swapChainImageFormat); } } @@ -520,7 +525,7 @@ class HelloTriangleApplication { renderPassInfo.dependencyCount = 1; renderPassInfo.pDependencies = &dependency; - if (vkCreateRenderPass(device, &renderPassInfo, nullptr, renderPass.replace()) != VK_SUCCESS) { + if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { throw std::runtime_error("failed to create render pass!"); } } @@ -538,7 +543,7 @@ class HelloTriangleApplication { layoutInfo.bindingCount = 1; layoutInfo.pBindings = &uboLayoutBinding; - if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, descriptorSetLayout.replace()) != VK_SUCCESS) { + if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &descriptorSetLayout) != VK_SUCCESS) { throw std::runtime_error("failed to create descriptor set layout!"); } } @@ -547,10 +552,8 @@ class HelloTriangleApplication { auto vertShaderCode = readFile("shaders/vert.spv"); auto fragShaderCode = readFile("shaders/frag.spv"); - VDeleter vertShaderModule{device, vkDestroyShaderModule}; - VDeleter fragShaderModule{device, vkDestroyShaderModule}; - createShaderModule(vertShaderCode, vertShaderModule); - createShaderModule(fragShaderCode, fragShaderModule); + VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); VkPipelineShaderStageCreateInfo vertShaderStageInfo = {}; vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; @@ -573,7 +576,7 @@ class HelloTriangleApplication { auto attributeDescriptions = Vertex::getAttributeDescriptions(); vertexInputInfo.vertexBindingDescriptionCount = 1; - vertexInputInfo.vertexAttributeDescriptionCount = attributeDescriptions.size(); + vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()); vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); @@ -631,13 +634,12 @@ class HelloTriangleApplication { colorBlending.blendConstants[2] = 0.0f; colorBlending.blendConstants[3] = 0.0f; - VkDescriptorSetLayout setLayouts[] = {descriptorSetLayout}; VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; pipelineLayoutInfo.setLayoutCount = 1; - pipelineLayoutInfo.pSetLayouts = setLayouts; + pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout; - if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, pipelineLayout.replace()) != VK_SUCCESS) { + if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { throw std::runtime_error("failed to create pipeline layout!"); } @@ -656,13 +658,16 @@ class HelloTriangleApplication { pipelineInfo.subpass = 0; pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; - if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, graphicsPipeline.replace()) != VK_SUCCESS) { + if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { throw std::runtime_error("failed to create graphics pipeline!"); } + + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); } void createFramebuffers() { - swapChainFramebuffers.resize(swapChainImageViews.size(), VDeleter{device, vkDestroyFramebuffer}); + swapChainFramebuffers.resize(swapChainImageViews.size()); for (size_t i = 0; i < swapChainImageViews.size(); i++) { VkImageView attachments[] = { @@ -678,7 +683,7 @@ class HelloTriangleApplication { framebufferInfo.height = swapChainExtent.height; framebufferInfo.layers = 1; - if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, swapChainFramebuffers[i].replace()) != VK_SUCCESS) { + if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { throw std::runtime_error("failed to create framebuffer!"); } } @@ -691,7 +696,7 @@ class HelloTriangleApplication { poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily; - if (vkCreateCommandPool(device, &poolInfo, nullptr, commandPool.replace()) != VK_SUCCESS) { + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { throw std::runtime_error("failed to create graphics command pool!"); } } @@ -705,46 +710,29 @@ class HelloTriangleApplication { throw std::runtime_error("failed to load texture image!"); } - VDeleter stagingImage{device, vkDestroyImage}; - VDeleter stagingImageMemory{device, vkFreeMemory}; - createImage(texWidth, texHeight, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_LINEAR, VK_IMAGE_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingImage, stagingImageMemory); - - VkImageSubresource subresource = {}; - subresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - subresource.mipLevel = 0; - subresource.arrayLayer = 0; - - VkSubresourceLayout stagingImageLayout; - vkGetImageSubresourceLayout(device, stagingImage, &subresource, &stagingImageLayout); + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); void* data; - vkMapMemory(device, stagingImageMemory, 0, imageSize, 0, &data); - - if (stagingImageLayout.rowPitch == texWidth * 4) { - memcpy(data, pixels, (size_t) imageSize); - } else { - uint8_t* dataBytes = reinterpret_cast(data); - - for (int y = 0; y < texHeight; y++) { - memcpy(&dataBytes[y * stagingImageLayout.rowPitch], &pixels[y * texWidth * 4], texWidth * 4); - } - } + vkMapMemory(device, stagingBufferMemory, 0, imageSize, 0, &data); + memcpy(data, pixels, static_cast(imageSize)); + vkUnmapMemory(device, stagingBufferMemory); - vkUnmapMemory(device, stagingImageMemory); - stbi_image_free(pixels); createImage(texWidth, texHeight, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, textureImage, textureImageMemory); - transitionImageLayout(stagingImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_PREINITIALIZED, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); - transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_PREINITIALIZED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); - copyImage(stagingImage, textureImage, texWidth, texHeight); - + transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); } void createTextureImageView() { - createImageView(textureImage, VK_FORMAT_R8G8B8A8_UNORM, textureImageView); + textureImageView = createImageView(textureImage, VK_FORMAT_R8G8B8A8_UNORM); } void createTextureSampler() { @@ -763,12 +751,12 @@ class HelloTriangleApplication { samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS; samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; - if (vkCreateSampler(device, &samplerInfo, nullptr, textureSampler.replace()) != VK_SUCCESS) { + if (vkCreateSampler(device, &samplerInfo, nullptr, &textureSampler) != VK_SUCCESS) { throw std::runtime_error("failed to create texture sampler!"); } } - void createImageView(VkImage image, VkFormat format, VDeleter& imageView) { + VkImageView createImageView(VkImage image, VkFormat format) { VkImageViewCreateInfo viewInfo = {}; viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; viewInfo.image = image; @@ -780,12 +768,15 @@ class HelloTriangleApplication { viewInfo.subresourceRange.baseArrayLayer = 0; viewInfo.subresourceRange.layerCount = 1; - if (vkCreateImageView(device, &viewInfo, nullptr, imageView.replace()) != VK_SUCCESS) { + VkImageView imageView; + if (vkCreateImageView(device, &viewInfo, nullptr, &imageView) != VK_SUCCESS) { throw std::runtime_error("failed to create texture image view!"); } + + return imageView; } - void createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VDeleter& image, VDeleter& imageMemory) { + void createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage& image, VkDeviceMemory& imageMemory) { VkImageCreateInfo imageInfo = {}; imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; imageInfo.imageType = VK_IMAGE_TYPE_2D; @@ -796,12 +787,12 @@ class HelloTriangleApplication { imageInfo.arrayLayers = 1; imageInfo.format = format; imageInfo.tiling = tiling; - imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED; + imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; imageInfo.usage = usage; imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; - imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - if (vkCreateImage(device, &imageInfo, nullptr, image.replace()) != VK_SUCCESS) { + if (vkCreateImage(device, &imageInfo, nullptr, &image) != VK_SUCCESS) { throw std::runtime_error("failed to create image!"); } @@ -813,7 +804,7 @@ class HelloTriangleApplication { allocInfo.allocationSize = memRequirements.size; allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); - if (vkAllocateMemory(device, &allocInfo, nullptr, imageMemory.replace()) != VK_SUCCESS) { + if (vkAllocateMemory(device, &allocInfo, nullptr, &imageMemory) != VK_SUCCESS) { throw std::runtime_error("failed to allocate image memory!"); } @@ -836,22 +827,28 @@ class HelloTriangleApplication { barrier.subresourceRange.baseArrayLayer = 0; barrier.subresourceRange.layerCount = 1; - if (oldLayout == VK_IMAGE_LAYOUT_PREINITIALIZED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL) { - barrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT; - barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; - } else if (oldLayout == VK_IMAGE_LAYOUT_PREINITIALIZED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { - barrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT; + VkPipelineStageFlags sourceStage; + VkPipelineStageFlags destinationStage; + + if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { + barrier.srcAccessMask = 0; barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + + sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT; } else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + + sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; } else { throw std::invalid_argument("unsupported layout transition!"); } - + vkCmdPipelineBarrier( commandBuffer, - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + sourceStage, destinationStage, 0, 0, nullptr, 0, nullptr, @@ -861,30 +858,25 @@ class HelloTriangleApplication { endSingleTimeCommands(commandBuffer); } - void copyImage(VkImage srcImage, VkImage dstImage, uint32_t width, uint32_t height) { + void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) { VkCommandBuffer commandBuffer = beginSingleTimeCommands(); - VkImageSubresourceLayers subResource = {}; - subResource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - subResource.baseArrayLayer = 0; - subResource.mipLevel = 0; - subResource.layerCount = 1; - - VkImageCopy region = {}; - region.srcSubresource = subResource; - region.dstSubresource = subResource; - region.srcOffset = {0, 0, 0}; - region.dstOffset = {0, 0, 0}; - region.extent.width = width; - region.extent.height = height; - region.extent.depth = 1; - - vkCmdCopyImage( - commandBuffer, - srcImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - dstImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - 1, ®ion - ); + VkBufferImageCopy region = {}; + region.bufferOffset = 0; + region.bufferRowLength = 0; + region.bufferImageHeight = 0; + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.mipLevel = 0; + region.imageSubresource.baseArrayLayer = 0; + region.imageSubresource.layerCount = 1; + region.imageOffset = {0, 0, 0}; + region.imageExtent = { + width, + height, + 1 + }; + + vkCmdCopyBufferToImage(commandBuffer, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); endSingleTimeCommands(commandBuffer); } @@ -892,8 +884,8 @@ class HelloTriangleApplication { void createVertexBuffer() { VkDeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); - VDeleter stagingBuffer{device, vkDestroyBuffer}; - VDeleter stagingBufferMemory{device, vkFreeMemory}; + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); void* data; @@ -904,13 +896,16 @@ class HelloTriangleApplication { createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, vertexBuffer, vertexBufferMemory); copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); } void createIndexBuffer() { VkDeviceSize bufferSize = sizeof(indices[0]) * indices.size(); - VDeleter stagingBuffer{device, vkDestroyBuffer}; - VDeleter stagingBufferMemory{device, vkFreeMemory}; + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); void* data; @@ -921,68 +916,78 @@ class HelloTriangleApplication { createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, indexBuffer, indexBufferMemory); copyBuffer(stagingBuffer, indexBuffer, bufferSize); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); } - void createUniformBuffer() { + void createUniformBuffers() { VkDeviceSize bufferSize = sizeof(UniformBufferObject); - createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformStagingBuffer, uniformStagingBufferMemory); - createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, uniformBuffer, uniformBufferMemory); + uniformBuffers.resize(swapChainImages.size()); + uniformBuffersMemory.resize(swapChainImages.size()); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + createBuffer(bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformBuffers[i], uniformBuffersMemory[i]); + } } void createDescriptorPool() { VkDescriptorPoolSize poolSize = {}; poolSize.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - poolSize.descriptorCount = 1; + poolSize.descriptorCount = static_cast(swapChainImages.size()); VkDescriptorPoolCreateInfo poolInfo = {}; poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; poolInfo.poolSizeCount = 1; poolInfo.pPoolSizes = &poolSize; - poolInfo.maxSets = 1; + poolInfo.maxSets = static_cast(swapChainImages.size()); - if (vkCreateDescriptorPool(device, &poolInfo, nullptr, descriptorPool.replace()) != VK_SUCCESS) { + if (vkCreateDescriptorPool(device, &poolInfo, nullptr, &descriptorPool) != VK_SUCCESS) { throw std::runtime_error("failed to create descriptor pool!"); } } - void createDescriptorSet() { - VkDescriptorSetLayout layouts[] = {descriptorSetLayout}; + void createDescriptorSets() { + std::vector layouts(swapChainImages.size(), descriptorSetLayout); VkDescriptorSetAllocateInfo allocInfo = {}; allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; allocInfo.descriptorPool = descriptorPool; - allocInfo.descriptorSetCount = 1; - allocInfo.pSetLayouts = layouts; + allocInfo.descriptorSetCount = static_cast(swapChainImages.size()); + allocInfo.pSetLayouts = layouts.data(); - if (vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet) != VK_SUCCESS) { - throw std::runtime_error("failed to allocate descriptor set!"); + descriptorSets.resize(swapChainImages.size()); + if (vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets[0]) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate descriptor sets!"); } - VkDescriptorBufferInfo bufferInfo = {}; - bufferInfo.buffer = uniformBuffer; - bufferInfo.offset = 0; - bufferInfo.range = sizeof(UniformBufferObject); - - VkWriteDescriptorSet descriptorWrite = {}; - descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; - descriptorWrite.dstSet = descriptorSet; - descriptorWrite.dstBinding = 0; - descriptorWrite.dstArrayElement = 0; - descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - descriptorWrite.descriptorCount = 1; - descriptorWrite.pBufferInfo = &bufferInfo; - - vkUpdateDescriptorSets(device, 1, &descriptorWrite, 0, nullptr); + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkDescriptorBufferInfo bufferInfo = {}; + bufferInfo.buffer = uniformBuffers[i]; + bufferInfo.offset = 0; + bufferInfo.range = sizeof(UniformBufferObject); + + VkWriteDescriptorSet descriptorWrite = {}; + descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrite.dstSet = descriptorSets[i]; + descriptorWrite.dstBinding = 0; + descriptorWrite.dstArrayElement = 0; + descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptorWrite.descriptorCount = 1; + descriptorWrite.pBufferInfo = &bufferInfo; + + vkUpdateDescriptorSets(device, 1, &descriptorWrite, 0, nullptr); + } } - void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VDeleter& buffer, VDeleter& bufferMemory) { + void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) { VkBufferCreateInfo bufferInfo = {}; bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; bufferInfo.size = size; bufferInfo.usage = usage; - bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - if (vkCreateBuffer(device, &bufferInfo, nullptr, buffer.replace()) != VK_SUCCESS) { + if (vkCreateBuffer(device, &bufferInfo, nullptr, &buffer) != VK_SUCCESS) { throw std::runtime_error("failed to create buffer!"); } @@ -994,7 +999,7 @@ class HelloTriangleApplication { allocInfo.allocationSize = memRequirements.size; allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); - if (vkAllocateMemory(device, &allocInfo, nullptr, bufferMemory.replace()) != VK_SUCCESS) { + if (vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) { throw std::runtime_error("failed to allocate buffer memory!"); } @@ -1058,10 +1063,6 @@ class HelloTriangleApplication { } void createCommandBuffers() { - if (commandBuffers.size() > 0) { - vkFreeCommandBuffers(device, commandPool, commandBuffers.size(), commandBuffers.data()); - } - commandBuffers.resize(swapChainFramebuffers.size()); VkCommandBufferAllocateInfo allocInfo = {}; @@ -1079,7 +1080,9 @@ class HelloTriangleApplication { beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; beginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT; - vkBeginCommandBuffer(commandBuffers[i], &beginInfo); + if (vkBeginCommandBuffer(commandBuffers[i], &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } VkRenderPassBeginInfo renderPassInfo = {}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; @@ -1102,9 +1105,9 @@ class HelloTriangleApplication { vkCmdBindIndexBuffer(commandBuffers[i], indexBuffer, 0, VK_INDEX_TYPE_UINT16); - vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr); + vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets[i], 0, nullptr); - vkCmdDrawIndexed(commandBuffers[i], indices.size(), 1, 0, 0, 0); + vkCmdDrawIndexed(commandBuffers[i], static_cast(indices.size()), 1, 0, 0, 0); vkCmdEndRenderPass(commandBuffers[i]); @@ -1114,40 +1117,50 @@ class HelloTriangleApplication { } } - void createSemaphores() { + void createSyncObjects() { + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + inFlightFences.resize(MAX_FRAMES_IN_FLIGHT); + VkSemaphoreCreateInfo semaphoreInfo = {}; semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; - if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, imageAvailableSemaphore.replace()) != VK_SUCCESS || - vkCreateSemaphore(device, &semaphoreInfo, nullptr, renderFinishedSemaphore.replace()) != VK_SUCCESS) { + VkFenceCreateInfo fenceInfo = {}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; - throw std::runtime_error("failed to create semaphores!"); + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS || + vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create synchronization objects for a frame!"); + } } } - void updateUniformBuffer() { + 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_cast(currentTime - startTime).count() / 1000.0f; + float time = std::chrono::duration(currentTime - startTime).count(); UniformBufferObject ubo = {}; - ubo.model = glm::rotate(glm::mat4(), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + 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; - vkMapMemory(device, uniformStagingBufferMemory, 0, sizeof(ubo), 0, &data); + vkMapMemory(device, uniformBuffersMemory[currentImage], 0, sizeof(ubo), 0, &data); memcpy(data, &ubo, sizeof(ubo)); - vkUnmapMemory(device, uniformStagingBufferMemory); - - copyBuffer(uniformStagingBuffer, uniformBuffer, sizeof(ubo)); + vkUnmapMemory(device, uniformBuffersMemory[currentImage]); } void drawFrame() { + vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, std::numeric_limits::max()); + uint32_t imageIndex; - VkResult result = vkAcquireNextImageKHR(device, swapChain, std::numeric_limits::max(), imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex); + VkResult result = vkAcquireNextImageKHR(device, swapChain, std::numeric_limits::max(), imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); if (result == VK_ERROR_OUT_OF_DATE_KHR) { recreateSwapChain(); @@ -1156,10 +1169,12 @@ class HelloTriangleApplication { throw std::runtime_error("failed to acquire swap chain image!"); } + updateUniformBuffer(imageIndex); + VkSubmitInfo submitInfo = {}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; - VkSemaphore waitSemaphores[] = {imageAvailableSemaphore}; + VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[currentFrame]}; VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; submitInfo.waitSemaphoreCount = 1; submitInfo.pWaitSemaphores = waitSemaphores; @@ -1168,11 +1183,13 @@ class HelloTriangleApplication { submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &commandBuffers[imageIndex]; - VkSemaphore signalSemaphores[] = {renderFinishedSemaphore}; + VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[currentFrame]}; submitInfo.signalSemaphoreCount = 1; submitInfo.pSignalSemaphores = signalSemaphores; - if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) { throw std::runtime_error("failed to submit draw command buffer!"); } @@ -1190,26 +1207,28 @@ class HelloTriangleApplication { result = vkQueuePresentKHR(presentQueue, &presentInfo); - if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { + framebufferResized = false; recreateSwapChain(); } else if (result != VK_SUCCESS) { throw std::runtime_error("failed to present swap chain image!"); } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; } - void createShaderModule(const std::vector& code, VDeleter& shaderModule) { + VkShaderModule createShaderModule(const std::vector& code) { VkShaderModuleCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); - std::vector codeAligned(code.size() / 4 + 1); - memcpy(codeAligned.data(), code.data(), code.size()); - - createInfo.pCode = codeAligned.data(); - - if (vkCreateShaderModule(device, &createInfo, nullptr, shaderModule.replace()) != VK_SUCCESS) { + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { throw std::runtime_error("failed to create shader module!"); } + + return shaderModule; } VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { @@ -1245,9 +1264,12 @@ class HelloTriangleApplication { return capabilities.currentExtent; } else { int width, height; - glfwGetWindowSize(window, &width, &height); + glfwGetFramebufferSize(window, &width, &height); - VkExtent2D actualExtent = {width, height}; + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; actualExtent.width = std::max(capabilities.minImageExtent.width, std::min(capabilities.maxImageExtent.width, actualExtent.width)); actualExtent.height = std::max(capabilities.minImageExtent.height, std::min(capabilities.maxImageExtent.height, actualExtent.height)); @@ -1291,7 +1313,10 @@ class HelloTriangleApplication { swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); } - return indices.isComplete() && extensionsSupported && swapChainAdequate; + VkPhysicalDeviceFeatures supportedFeatures; + vkGetPhysicalDeviceFeatures(device, &supportedFeatures); + + return indices.isComplete() && extensionsSupported && swapChainAdequate && supportedFeatures.samplerAnisotropy; } bool checkDeviceExtensionSupport(VkPhysicalDevice device) { @@ -1343,15 +1368,11 @@ class HelloTriangleApplication { } std::vector getRequiredExtensions() { - std::vector extensions; - - unsigned int glfwExtensionCount = 0; + uint32_t glfwExtensionCount = 0; const char** glfwExtensions; glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - for (unsigned int i = 0; i < glfwExtensionCount; i++) { - extensions.push_back(glfwExtensions[i]); - } + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); if (enableValidationLayers) { extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); @@ -1415,10 +1436,10 @@ int main() { try { app.run(); - } catch (const std::runtime_error& e) { + } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; -} \ No newline at end of file +} diff --git a/code/shader_depth.frag b/code/25_shader_textures.frag similarity index 100% rename from code/shader_depth.frag rename to code/25_shader_textures.frag diff --git a/code/shader_textures.vert b/code/25_shader_textures.vert similarity index 100% rename from code/shader_textures.vert rename to code/25_shader_textures.vert diff --git a/code/texture_mapping.cpp b/code/25_texture_mapping.cpp similarity index 74% rename from code/texture_mapping.cpp rename to code/25_texture_mapping.cpp index 89ca8d41..f59388ec 100644 --- a/code/texture_mapping.cpp +++ b/code/25_texture_mapping.cpp @@ -9,19 +9,21 @@ #include #include -#include -#include -#include #include +#include #include +#include #include #include +#include #include #include const int WIDTH = 800; const int HEIGHT = 600; +const int MAX_FRAMES_IN_FLIGHT = 2; + const std::vector validationLayers = { "VK_LAYER_LUNARG_standard_validation" }; @@ -52,64 +54,6 @@ void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT } } -template -class VDeleter { -public: - VDeleter() : VDeleter([](T, VkAllocationCallbacks*) {}) {} - - VDeleter(std::function deletef) { - this->deleter = [=](T obj) { deletef(obj, nullptr); }; - } - - VDeleter(const VDeleter& instance, std::function deletef) { - this->deleter = [&instance, deletef](T obj) { deletef(instance, obj, nullptr); }; - } - - VDeleter(const VDeleter& device, std::function deletef) { - this->deleter = [&device, deletef](T obj) { deletef(device, obj, nullptr); }; - } - - ~VDeleter() { - cleanup(); - } - - const T* operator &() const { - return &object; - } - - T* replace() { - cleanup(); - return &object; - } - - operator T() const { - return object; - } - - void operator=(T rhs) { - if (rhs != object) { - cleanup(); - object = rhs; - } - } - - template - bool operator==(V rhs) { - return object == T(rhs); - } - -private: - T object{VK_NULL_HANDLE}; - std::function deleter; - - void cleanup() { - if (object != VK_NULL_HANDLE) { - deleter(object); - } - object = VK_NULL_HANDLE; - } -}; - struct QueueFamilyIndices { int graphicsFamily = -1; int presentFamily = -1; @@ -168,10 +112,10 @@ struct UniformBufferObject { }; const std::vector vertices = { - {{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}, - {{0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}}, - {{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, - {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}} + {{-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}} }; const std::vector indices = { @@ -184,57 +128,60 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugReportCallbackEXT callback; + VkSurfaceKHR surface; VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; - VDeleter device{vkDestroyDevice}; + VkDevice device; VkQueue graphicsQueue; VkQueue presentQueue; - - VDeleter swapChain{device, vkDestroySwapchainKHR}; + + VkSwapchainKHR swapChain; std::vector swapChainImages; VkFormat swapChainImageFormat; VkExtent2D swapChainExtent; - std::vector> swapChainImageViews; - std::vector> swapChainFramebuffers; - - VDeleter renderPass{device, vkDestroyRenderPass}; - VDeleter descriptorSetLayout{device, vkDestroyDescriptorSetLayout}; - VDeleter pipelineLayout{device, vkDestroyPipelineLayout}; - VDeleter graphicsPipeline{device, vkDestroyPipeline}; - - VDeleter commandPool{device, vkDestroyCommandPool}; - - VDeleter textureImage{device, vkDestroyImage}; - VDeleter textureImageMemory{device, vkFreeMemory}; - VDeleter textureImageView{device, vkDestroyImageView}; - VDeleter textureSampler{device, vkDestroySampler}; - - VDeleter vertexBuffer{device, vkDestroyBuffer}; - VDeleter vertexBufferMemory{device, vkFreeMemory}; - VDeleter indexBuffer{device, vkDestroyBuffer}; - VDeleter indexBufferMemory{device, vkFreeMemory}; - - VDeleter uniformStagingBuffer{device, vkDestroyBuffer}; - VDeleter uniformStagingBufferMemory{device, vkFreeMemory}; - VDeleter uniformBuffer{device, vkDestroyBuffer}; - VDeleter uniformBufferMemory{device, vkFreeMemory}; - - VDeleter descriptorPool{device, vkDestroyDescriptorPool}; - VkDescriptorSet descriptorSet; + std::vector swapChainImageViews; + std::vector swapChainFramebuffers; + + VkRenderPass renderPass; + VkDescriptorSetLayout descriptorSetLayout; + VkPipelineLayout pipelineLayout; + VkPipeline graphicsPipeline; + + VkCommandPool commandPool; + + VkImage textureImage; + VkDeviceMemory textureImageMemory; + VkImageView textureImageView; + VkSampler textureSampler; + + VkBuffer vertexBuffer; + VkDeviceMemory vertexBufferMemory; + VkBuffer indexBuffer; + VkDeviceMemory indexBufferMemory; + + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + + VkDescriptorPool descriptorPool; + std::vector descriptorSets; std::vector commandBuffers; - VDeleter imageAvailableSemaphore{device, vkDestroySemaphore}; - VDeleter renderFinishedSemaphore{device, vkDestroySemaphore}; + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + std::vector inFlightFences; + size_t currentFrame = 0; + + bool framebufferResized = false; void initWindow() { glfwInit(); @@ -242,9 +189,13 @@ class HelloTriangleApplication { glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetWindowSizeCallback(window, HelloTriangleApplication::onWindowResized); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; } void initVulkan() { @@ -265,38 +216,97 @@ class HelloTriangleApplication { createTextureSampler(); createVertexBuffer(); createIndexBuffer(); - createUniformBuffer(); + createUniformBuffers(); createDescriptorPool(); - createDescriptorSet(); + createDescriptorSets(); createCommandBuffers(); - createSemaphores(); + createSyncObjects(); } void mainLoop() { while (!glfwWindowShouldClose(window)) { glfwPollEvents(); - - updateUniformBuffer(); drawFrame(); } vkDeviceWaitIdle(device); + } - glfwDestroyWindow(window); + void cleanupSwapChain() { + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } - glfwTerminate(); + vkFreeCommandBuffers(device, commandPool, static_cast(commandBuffers.size()), commandBuffers.data()); + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); } - static void onWindowResized(GLFWwindow* window, int width, int height) { - if (width == 0 || height == 0) return; - - HelloTriangleApplication* app = reinterpret_cast(glfwGetWindowUserPointer(window)); - app->recreateSwapChain(); + void cleanup() { + cleanupSwapChain(); + + vkDestroySampler(device, textureSampler, nullptr); + vkDestroyImageView(device, textureImageView, nullptr); + + vkDestroyImage(device, textureImage, nullptr); + vkFreeMemory(device, textureImageMemory, nullptr); + + vkDestroyDescriptorPool(device, descriptorPool, nullptr); + + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + vkDestroyBuffer(device, uniformBuffers[i], nullptr); + vkFreeMemory(device, uniformBuffersMemory[i], nullptr); + } + + vkDestroyBuffer(device, indexBuffer, nullptr); + vkFreeMemory(device, indexBufferMemory, nullptr); + + vkDestroyBuffer(device, vertexBuffer, nullptr); + vkFreeMemory(device, vertexBufferMemory, nullptr); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + vkDestroyFence(device, inFlightFences[i], nullptr); + } + + vkDestroyCommandPool(device, commandPool, nullptr); + + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugReportCallbackEXT(instance, callback, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); } void recreateSwapChain() { + int width = 0, height = 0; + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + vkDeviceWaitIdle(device); + cleanupSwapChain(); + createSwapChain(); createImageViews(); createRenderPass(); @@ -323,17 +333,17 @@ class HelloTriangleApplication { createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } @@ -346,13 +356,13 @@ class HelloTriangleApplication { createInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT; createInfo.pfnCallback = debugCallback; - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { + if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, &callback) != VK_SUCCESS) { throw std::runtime_error("failed to set up debug callback!"); } } void createSurface() { - if (glfwCreateWindowSurface(instance, window, nullptr, surface.replace()) != VK_SUCCESS) { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { throw std::runtime_error("failed to create window surface!"); } } @@ -397,26 +407,27 @@ class HelloTriangleApplication { } VkPhysicalDeviceFeatures deviceFeatures = {}; + deviceFeatures.samplerAnisotropy = VK_TRUE; VkDeviceCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); createInfo.pQueueCreateInfos = queueCreateInfos.data(); - createInfo.queueCreateInfoCount = (uint32_t) queueCreateInfos.size(); createInfo.pEnabledFeatures = &deviceFeatures; - createInfo.enabledExtensionCount = deviceExtensions.size(); + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); createInfo.ppEnabledExtensionNames = deviceExtensions.data(); if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateDevice(physicalDevice, &createInfo, nullptr, device.replace()) != VK_SUCCESS) { + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { throw std::runtime_error("failed to create logical device!"); } @@ -463,16 +474,10 @@ class HelloTriangleApplication { createInfo.presentMode = presentMode; createInfo.clipped = VK_TRUE; - VkSwapchainKHR oldSwapChain = swapChain; - createInfo.oldSwapchain = oldSwapChain; - - VkSwapchainKHR newSwapChain; - if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &newSwapChain) != VK_SUCCESS) { + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { throw std::runtime_error("failed to create swap chain!"); } - swapChain = newSwapChain; - vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); swapChainImages.resize(imageCount); vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); @@ -482,10 +487,10 @@ class HelloTriangleApplication { } void createImageViews() { - swapChainImageViews.resize(swapChainImages.size(), VDeleter{device, vkDestroyImageView}); + swapChainImageViews.resize(swapChainImages.size()); - for (uint32_t i = 0; i < swapChainImages.size(); i++) { - createImageView(swapChainImages[i], swapChainImageFormat, swapChainImageViews[i]); + for (size_t i = 0; i < swapChainImages.size(); i++) { + swapChainImageViews[i] = createImageView(swapChainImages[i], swapChainImageFormat); } } @@ -526,7 +531,7 @@ class HelloTriangleApplication { renderPassInfo.dependencyCount = 1; renderPassInfo.pDependencies = &dependency; - if (vkCreateRenderPass(device, &renderPassInfo, nullptr, renderPass.replace()) != VK_SUCCESS) { + if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { throw std::runtime_error("failed to create render pass!"); } } @@ -545,14 +550,14 @@ class HelloTriangleApplication { samplerLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; samplerLayoutBinding.pImmutableSamplers = nullptr; samplerLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; - + std::array bindings = {uboLayoutBinding, samplerLayoutBinding}; VkDescriptorSetLayoutCreateInfo layoutInfo = {}; layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; - layoutInfo.bindingCount = bindings.size(); + layoutInfo.bindingCount = static_cast(bindings.size()); layoutInfo.pBindings = bindings.data(); - if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, descriptorSetLayout.replace()) != VK_SUCCESS) { + if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &descriptorSetLayout) != VK_SUCCESS) { throw std::runtime_error("failed to create descriptor set layout!"); } } @@ -561,10 +566,8 @@ class HelloTriangleApplication { auto vertShaderCode = readFile("shaders/vert.spv"); auto fragShaderCode = readFile("shaders/frag.spv"); - VDeleter vertShaderModule{device, vkDestroyShaderModule}; - VDeleter fragShaderModule{device, vkDestroyShaderModule}; - createShaderModule(vertShaderCode, vertShaderModule); - createShaderModule(fragShaderCode, fragShaderModule); + VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); VkPipelineShaderStageCreateInfo vertShaderStageInfo = {}; vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; @@ -587,7 +590,7 @@ class HelloTriangleApplication { auto attributeDescriptions = Vertex::getAttributeDescriptions(); vertexInputInfo.vertexBindingDescriptionCount = 1; - vertexInputInfo.vertexAttributeDescriptionCount = attributeDescriptions.size(); + vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()); vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); @@ -645,13 +648,12 @@ class HelloTriangleApplication { colorBlending.blendConstants[2] = 0.0f; colorBlending.blendConstants[3] = 0.0f; - VkDescriptorSetLayout setLayouts[] = {descriptorSetLayout}; VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; pipelineLayoutInfo.setLayoutCount = 1; - pipelineLayoutInfo.pSetLayouts = setLayouts; + pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout; - if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, pipelineLayout.replace()) != VK_SUCCESS) { + if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { throw std::runtime_error("failed to create pipeline layout!"); } @@ -670,13 +672,16 @@ class HelloTriangleApplication { pipelineInfo.subpass = 0; pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; - if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, graphicsPipeline.replace()) != VK_SUCCESS) { + if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { throw std::runtime_error("failed to create graphics pipeline!"); } + + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); } void createFramebuffers() { - swapChainFramebuffers.resize(swapChainImageViews.size(), VDeleter{device, vkDestroyFramebuffer}); + swapChainFramebuffers.resize(swapChainImageViews.size()); for (size_t i = 0; i < swapChainImageViews.size(); i++) { VkImageView attachments[] = { @@ -692,7 +697,7 @@ class HelloTriangleApplication { framebufferInfo.height = swapChainExtent.height; framebufferInfo.layers = 1; - if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, swapChainFramebuffers[i].replace()) != VK_SUCCESS) { + if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { throw std::runtime_error("failed to create framebuffer!"); } } @@ -705,7 +710,7 @@ class HelloTriangleApplication { poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily; - if (vkCreateCommandPool(device, &poolInfo, nullptr, commandPool.replace()) != VK_SUCCESS) { + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { throw std::runtime_error("failed to create graphics command pool!"); } } @@ -719,46 +724,29 @@ class HelloTriangleApplication { throw std::runtime_error("failed to load texture image!"); } - VDeleter stagingImage{device, vkDestroyImage}; - VDeleter stagingImageMemory{device, vkFreeMemory}; - createImage(texWidth, texHeight, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_LINEAR, VK_IMAGE_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingImage, stagingImageMemory); - - VkImageSubresource subresource = {}; - subresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - subresource.mipLevel = 0; - subresource.arrayLayer = 0; - - VkSubresourceLayout stagingImageLayout; - vkGetImageSubresourceLayout(device, stagingImage, &subresource, &stagingImageLayout); + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); void* data; - vkMapMemory(device, stagingImageMemory, 0, imageSize, 0, &data); - - if (stagingImageLayout.rowPitch == texWidth * 4) { - memcpy(data, pixels, (size_t) imageSize); - } else { - uint8_t* dataBytes = reinterpret_cast(data); - - for (int y = 0; y < texHeight; y++) { - memcpy(&dataBytes[y * stagingImageLayout.rowPitch], &pixels[y * texWidth * 4], texWidth * 4); - } - } + vkMapMemory(device, stagingBufferMemory, 0, imageSize, 0, &data); + memcpy(data, pixels, static_cast(imageSize)); + vkUnmapMemory(device, stagingBufferMemory); - vkUnmapMemory(device, stagingImageMemory); - stbi_image_free(pixels); createImage(texWidth, texHeight, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, textureImage, textureImageMemory); - transitionImageLayout(stagingImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_PREINITIALIZED, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); - transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_PREINITIALIZED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); - copyImage(stagingImage, textureImage, texWidth, texHeight); - + transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); } void createTextureImageView() { - createImageView(textureImage, VK_FORMAT_R8G8B8A8_UNORM, textureImageView); + textureImageView = createImageView(textureImage, VK_FORMAT_R8G8B8A8_UNORM); } void createTextureSampler() { @@ -777,12 +765,12 @@ class HelloTriangleApplication { samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS; samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; - if (vkCreateSampler(device, &samplerInfo, nullptr, textureSampler.replace()) != VK_SUCCESS) { + if (vkCreateSampler(device, &samplerInfo, nullptr, &textureSampler) != VK_SUCCESS) { throw std::runtime_error("failed to create texture sampler!"); } } - void createImageView(VkImage image, VkFormat format, VDeleter& imageView) { + VkImageView createImageView(VkImage image, VkFormat format) { VkImageViewCreateInfo viewInfo = {}; viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; viewInfo.image = image; @@ -794,12 +782,15 @@ class HelloTriangleApplication { viewInfo.subresourceRange.baseArrayLayer = 0; viewInfo.subresourceRange.layerCount = 1; - if (vkCreateImageView(device, &viewInfo, nullptr, imageView.replace()) != VK_SUCCESS) { + VkImageView imageView; + if (vkCreateImageView(device, &viewInfo, nullptr, &imageView) != VK_SUCCESS) { throw std::runtime_error("failed to create texture image view!"); } + + return imageView; } - void createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VDeleter& image, VDeleter& imageMemory) { + void createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage& image, VkDeviceMemory& imageMemory) { VkImageCreateInfo imageInfo = {}; imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; imageInfo.imageType = VK_IMAGE_TYPE_2D; @@ -810,12 +801,12 @@ class HelloTriangleApplication { imageInfo.arrayLayers = 1; imageInfo.format = format; imageInfo.tiling = tiling; - imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED; + imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; imageInfo.usage = usage; imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; - imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - if (vkCreateImage(device, &imageInfo, nullptr, image.replace()) != VK_SUCCESS) { + if (vkCreateImage(device, &imageInfo, nullptr, &image) != VK_SUCCESS) { throw std::runtime_error("failed to create image!"); } @@ -827,7 +818,7 @@ class HelloTriangleApplication { allocInfo.allocationSize = memRequirements.size; allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); - if (vkAllocateMemory(device, &allocInfo, nullptr, imageMemory.replace()) != VK_SUCCESS) { + if (vkAllocateMemory(device, &allocInfo, nullptr, &imageMemory) != VK_SUCCESS) { throw std::runtime_error("failed to allocate image memory!"); } @@ -850,22 +841,28 @@ class HelloTriangleApplication { barrier.subresourceRange.baseArrayLayer = 0; barrier.subresourceRange.layerCount = 1; - if (oldLayout == VK_IMAGE_LAYOUT_PREINITIALIZED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL) { - barrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT; - barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; - } else if (oldLayout == VK_IMAGE_LAYOUT_PREINITIALIZED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { - barrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT; + VkPipelineStageFlags sourceStage; + VkPipelineStageFlags destinationStage; + + if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { + barrier.srcAccessMask = 0; barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + + sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT; } else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + + sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; } else { throw std::invalid_argument("unsupported layout transition!"); } - + vkCmdPipelineBarrier( commandBuffer, - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + sourceStage, destinationStage, 0, 0, nullptr, 0, nullptr, @@ -875,30 +872,25 @@ class HelloTriangleApplication { endSingleTimeCommands(commandBuffer); } - void copyImage(VkImage srcImage, VkImage dstImage, uint32_t width, uint32_t height) { + void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) { VkCommandBuffer commandBuffer = beginSingleTimeCommands(); - VkImageSubresourceLayers subResource = {}; - subResource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - subResource.baseArrayLayer = 0; - subResource.mipLevel = 0; - subResource.layerCount = 1; - - VkImageCopy region = {}; - region.srcSubresource = subResource; - region.dstSubresource = subResource; - region.srcOffset = {0, 0, 0}; - region.dstOffset = {0, 0, 0}; - region.extent.width = width; - region.extent.height = height; - region.extent.depth = 1; - - vkCmdCopyImage( - commandBuffer, - srcImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - dstImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - 1, ®ion - ); + VkBufferImageCopy region = {}; + region.bufferOffset = 0; + region.bufferRowLength = 0; + region.bufferImageHeight = 0; + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.mipLevel = 0; + region.imageSubresource.baseArrayLayer = 0; + region.imageSubresource.layerCount = 1; + region.imageOffset = {0, 0, 0}; + region.imageExtent = { + width, + height, + 1 + }; + + vkCmdCopyBufferToImage(commandBuffer, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); endSingleTimeCommands(commandBuffer); } @@ -906,8 +898,8 @@ class HelloTriangleApplication { void createVertexBuffer() { VkDeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); - VDeleter stagingBuffer{device, vkDestroyBuffer}; - VDeleter stagingBufferMemory{device, vkFreeMemory}; + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); void* data; @@ -918,13 +910,16 @@ class HelloTriangleApplication { createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, vertexBuffer, vertexBufferMemory); copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); } void createIndexBuffer() { VkDeviceSize bufferSize = sizeof(indices[0]) * indices.size(); - VDeleter stagingBuffer{device, vkDestroyBuffer}; - VDeleter stagingBufferMemory{device, vkFreeMemory}; + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); void* data; @@ -935,84 +930,94 @@ class HelloTriangleApplication { createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, indexBuffer, indexBufferMemory); copyBuffer(stagingBuffer, indexBuffer, bufferSize); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); } - void createUniformBuffer() { + void createUniformBuffers() { VkDeviceSize bufferSize = sizeof(UniformBufferObject); - createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformStagingBuffer, uniformStagingBufferMemory); - createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, uniformBuffer, uniformBufferMemory); + uniformBuffers.resize(swapChainImages.size()); + uniformBuffersMemory.resize(swapChainImages.size()); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + createBuffer(bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformBuffers[i], uniformBuffersMemory[i]); + } } void createDescriptorPool() { std::array poolSizes = {}; poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - poolSizes[0].descriptorCount = 1; + poolSizes[0].descriptorCount = static_cast(swapChainImages.size()); poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - poolSizes[1].descriptorCount = 1; + poolSizes[1].descriptorCount = static_cast(swapChainImages.size()); VkDescriptorPoolCreateInfo poolInfo = {}; poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; - poolInfo.poolSizeCount = poolSizes.size(); + poolInfo.poolSizeCount = static_cast(poolSizes.size()); poolInfo.pPoolSizes = poolSizes.data(); - poolInfo.maxSets = 1; + poolInfo.maxSets = static_cast(swapChainImages.size()); - if (vkCreateDescriptorPool(device, &poolInfo, nullptr, descriptorPool.replace()) != VK_SUCCESS) { + if (vkCreateDescriptorPool(device, &poolInfo, nullptr, &descriptorPool) != VK_SUCCESS) { throw std::runtime_error("failed to create descriptor pool!"); } } - void createDescriptorSet() { - VkDescriptorSetLayout layouts[] = {descriptorSetLayout}; + void createDescriptorSets() { + std::vector layouts(swapChainImages.size(), descriptorSetLayout); VkDescriptorSetAllocateInfo allocInfo = {}; allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; allocInfo.descriptorPool = descriptorPool; - allocInfo.descriptorSetCount = 1; - allocInfo.pSetLayouts = layouts; + allocInfo.descriptorSetCount = static_cast(swapChainImages.size()); + allocInfo.pSetLayouts = layouts.data(); - if (vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet) != VK_SUCCESS) { - throw std::runtime_error("failed to allocate descriptor set!"); + descriptorSets.resize(swapChainImages.size()); + if (vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets[0]) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate descriptor sets!"); } - VkDescriptorBufferInfo bufferInfo = {}; - bufferInfo.buffer = uniformBuffer; - bufferInfo.offset = 0; - bufferInfo.range = sizeof(UniformBufferObject); - - VkDescriptorImageInfo imageInfo = {}; - imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - imageInfo.imageView = textureImageView; - imageInfo.sampler = textureSampler; - - std::array descriptorWrites = {}; - - descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; - descriptorWrites[0].dstSet = descriptorSet; - descriptorWrites[0].dstBinding = 0; - descriptorWrites[0].dstArrayElement = 0; - descriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - descriptorWrites[0].descriptorCount = 1; - descriptorWrites[0].pBufferInfo = &bufferInfo; - - descriptorWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; - descriptorWrites[1].dstSet = descriptorSet; - descriptorWrites[1].dstBinding = 1; - descriptorWrites[1].dstArrayElement = 0; - descriptorWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - descriptorWrites[1].descriptorCount = 1; - descriptorWrites[1].pImageInfo = &imageInfo; - - vkUpdateDescriptorSets(device, descriptorWrites.size(), descriptorWrites.data(), 0, nullptr); + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkDescriptorBufferInfo bufferInfo = {}; + bufferInfo.buffer = uniformBuffers[i]; + bufferInfo.offset = 0; + bufferInfo.range = sizeof(UniformBufferObject); + + VkDescriptorImageInfo imageInfo = {}; + imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + imageInfo.imageView = textureImageView; + imageInfo.sampler = textureSampler; + + std::array descriptorWrites = {}; + + descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[0].dstSet = descriptorSets[i]; + descriptorWrites[0].dstBinding = 0; + descriptorWrites[0].dstArrayElement = 0; + descriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptorWrites[0].descriptorCount = 1; + descriptorWrites[0].pBufferInfo = &bufferInfo; + + descriptorWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[1].dstSet = descriptorSets[i]; + descriptorWrites[1].dstBinding = 1; + descriptorWrites[1].dstArrayElement = 0; + descriptorWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + descriptorWrites[1].descriptorCount = 1; + descriptorWrites[1].pImageInfo = &imageInfo; + + vkUpdateDescriptorSets(device, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); + } } - void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VDeleter& buffer, VDeleter& bufferMemory) { + void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) { VkBufferCreateInfo bufferInfo = {}; bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; bufferInfo.size = size; bufferInfo.usage = usage; - bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - if (vkCreateBuffer(device, &bufferInfo, nullptr, buffer.replace()) != VK_SUCCESS) { + if (vkCreateBuffer(device, &bufferInfo, nullptr, &buffer) != VK_SUCCESS) { throw std::runtime_error("failed to create buffer!"); } @@ -1024,7 +1029,7 @@ class HelloTriangleApplication { allocInfo.allocationSize = memRequirements.size; allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); - if (vkAllocateMemory(device, &allocInfo, nullptr, bufferMemory.replace()) != VK_SUCCESS) { + if (vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) { throw std::runtime_error("failed to allocate buffer memory!"); } @@ -1088,10 +1093,6 @@ class HelloTriangleApplication { } void createCommandBuffers() { - if (commandBuffers.size() > 0) { - vkFreeCommandBuffers(device, commandPool, commandBuffers.size(), commandBuffers.data()); - } - commandBuffers.resize(swapChainFramebuffers.size()); VkCommandBufferAllocateInfo allocInfo = {}; @@ -1109,7 +1110,9 @@ class HelloTriangleApplication { beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; beginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT; - vkBeginCommandBuffer(commandBuffers[i], &beginInfo); + if (vkBeginCommandBuffer(commandBuffers[i], &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } VkRenderPassBeginInfo renderPassInfo = {}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; @@ -1132,9 +1135,9 @@ class HelloTriangleApplication { vkCmdBindIndexBuffer(commandBuffers[i], indexBuffer, 0, VK_INDEX_TYPE_UINT16); - vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr); + vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets[i], 0, nullptr); - vkCmdDrawIndexed(commandBuffers[i], indices.size(), 1, 0, 0, 0); + vkCmdDrawIndexed(commandBuffers[i], static_cast(indices.size()), 1, 0, 0, 0); vkCmdEndRenderPass(commandBuffers[i]); @@ -1144,40 +1147,50 @@ class HelloTriangleApplication { } } - void createSemaphores() { + void createSyncObjects() { + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + inFlightFences.resize(MAX_FRAMES_IN_FLIGHT); + VkSemaphoreCreateInfo semaphoreInfo = {}; semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; - if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, imageAvailableSemaphore.replace()) != VK_SUCCESS || - vkCreateSemaphore(device, &semaphoreInfo, nullptr, renderFinishedSemaphore.replace()) != VK_SUCCESS) { + VkFenceCreateInfo fenceInfo = {}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; - throw std::runtime_error("failed to create semaphores!"); + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS || + vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create synchronization objects for a frame!"); + } } } - void updateUniformBuffer() { + 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_cast(currentTime - startTime).count() / 1000.0f; + float time = std::chrono::duration(currentTime - startTime).count(); UniformBufferObject ubo = {}; - ubo.model = glm::rotate(glm::mat4(), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + 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; - vkMapMemory(device, uniformStagingBufferMemory, 0, sizeof(ubo), 0, &data); + vkMapMemory(device, uniformBuffersMemory[currentImage], 0, sizeof(ubo), 0, &data); memcpy(data, &ubo, sizeof(ubo)); - vkUnmapMemory(device, uniformStagingBufferMemory); - - copyBuffer(uniformStagingBuffer, uniformBuffer, sizeof(ubo)); + vkUnmapMemory(device, uniformBuffersMemory[currentImage]); } void drawFrame() { + vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, std::numeric_limits::max()); + uint32_t imageIndex; - VkResult result = vkAcquireNextImageKHR(device, swapChain, std::numeric_limits::max(), imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex); + VkResult result = vkAcquireNextImageKHR(device, swapChain, std::numeric_limits::max(), imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); if (result == VK_ERROR_OUT_OF_DATE_KHR) { recreateSwapChain(); @@ -1186,10 +1199,12 @@ class HelloTriangleApplication { throw std::runtime_error("failed to acquire swap chain image!"); } + updateUniformBuffer(imageIndex); + VkSubmitInfo submitInfo = {}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; - VkSemaphore waitSemaphores[] = {imageAvailableSemaphore}; + VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[currentFrame]}; VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; submitInfo.waitSemaphoreCount = 1; submitInfo.pWaitSemaphores = waitSemaphores; @@ -1198,11 +1213,13 @@ class HelloTriangleApplication { submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &commandBuffers[imageIndex]; - VkSemaphore signalSemaphores[] = {renderFinishedSemaphore}; + VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[currentFrame]}; submitInfo.signalSemaphoreCount = 1; submitInfo.pSignalSemaphores = signalSemaphores; - if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) { throw std::runtime_error("failed to submit draw command buffer!"); } @@ -1220,26 +1237,28 @@ class HelloTriangleApplication { result = vkQueuePresentKHR(presentQueue, &presentInfo); - if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { + framebufferResized = false; recreateSwapChain(); } else if (result != VK_SUCCESS) { throw std::runtime_error("failed to present swap chain image!"); } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; } - void createShaderModule(const std::vector& code, VDeleter& shaderModule) { + VkShaderModule createShaderModule(const std::vector& code) { VkShaderModuleCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); - std::vector codeAligned(code.size() / 4 + 1); - memcpy(codeAligned.data(), code.data(), code.size()); - - createInfo.pCode = codeAligned.data(); - - if (vkCreateShaderModule(device, &createInfo, nullptr, shaderModule.replace()) != VK_SUCCESS) { + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { throw std::runtime_error("failed to create shader module!"); } + + return shaderModule; } VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { @@ -1275,9 +1294,12 @@ class HelloTriangleApplication { return capabilities.currentExtent; } else { int width, height; - glfwGetWindowSize(window, &width, &height); + glfwGetFramebufferSize(window, &width, &height); - VkExtent2D actualExtent = {width, height}; + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; actualExtent.width = std::max(capabilities.minImageExtent.width, std::min(capabilities.maxImageExtent.width, actualExtent.width)); actualExtent.height = std::max(capabilities.minImageExtent.height, std::min(capabilities.maxImageExtent.height, actualExtent.height)); @@ -1321,7 +1343,10 @@ class HelloTriangleApplication { swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); } - return indices.isComplete() && extensionsSupported && swapChainAdequate; + VkPhysicalDeviceFeatures supportedFeatures; + vkGetPhysicalDeviceFeatures(device, &supportedFeatures); + + return indices.isComplete() && extensionsSupported && swapChainAdequate && supportedFeatures.samplerAnisotropy; } bool checkDeviceExtensionSupport(VkPhysicalDevice device) { @@ -1373,15 +1398,11 @@ class HelloTriangleApplication { } std::vector getRequiredExtensions() { - std::vector extensions; - - unsigned int glfwExtensionCount = 0; + uint32_t glfwExtensionCount = 0; const char** glfwExtensions; glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - for (unsigned int i = 0; i < glfwExtensionCount; i++) { - extensions.push_back(glfwExtensions[i]); - } + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); if (enableValidationLayers) { extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); @@ -1445,10 +1466,10 @@ int main() { try { app.run(); - } catch (const std::runtime_error& e) { + } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; -} \ No newline at end of file +} diff --git a/code/depth_buffering.cpp b/code/26_depth_buffering.cpp similarity index 74% rename from code/depth_buffering.cpp rename to code/26_depth_buffering.cpp index 5aa24560..96c0bac3 100644 --- a/code/depth_buffering.cpp +++ b/code/26_depth_buffering.cpp @@ -10,19 +10,21 @@ #include #include -#include -#include -#include #include +#include #include +#include #include #include +#include #include #include const int WIDTH = 800; const int HEIGHT = 600; +const int MAX_FRAMES_IN_FLIGHT = 2; + const std::vector validationLayers = { "VK_LAYER_LUNARG_standard_validation" }; @@ -53,64 +55,6 @@ void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT } } -template -class VDeleter { -public: - VDeleter() : VDeleter([](T, VkAllocationCallbacks*) {}) {} - - VDeleter(std::function deletef) { - this->deleter = [=](T obj) { deletef(obj, nullptr); }; - } - - VDeleter(const VDeleter& instance, std::function deletef) { - this->deleter = [&instance, deletef](T obj) { deletef(instance, obj, nullptr); }; - } - - VDeleter(const VDeleter& device, std::function deletef) { - this->deleter = [&device, deletef](T obj) { deletef(device, obj, nullptr); }; - } - - ~VDeleter() { - cleanup(); - } - - const T* operator &() const { - return &object; - } - - T* replace() { - cleanup(); - return &object; - } - - operator T() const { - return object; - } - - void operator=(T rhs) { - if (rhs != object) { - cleanup(); - object = rhs; - } - } - - template - bool operator==(V rhs) { - return object == T(rhs); - } - -private: - T object{VK_NULL_HANDLE}; - std::function deleter; - - void cleanup() { - if (object != VK_NULL_HANDLE) { - deleter(object); - } - object = VK_NULL_HANDLE; - } -}; - struct QueueFamilyIndices { int graphicsFamily = -1; int presentFamily = -1; @@ -169,15 +113,15 @@ struct UniformBufferObject { }; const std::vector vertices = { - {{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}, - {{0.5f, -0.5f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}}, - {{0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, - {{-0.5f, 0.5f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}}, - - {{-0.5f, -0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}, - {{0.5f, -0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}}, - {{0.5f, 0.5f, -0.5f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, - {{-0.5f, 0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}} + {{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {1.0f, 0.0f}}, + {{0.5f, -0.5f, 0.0f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f}}, + {{0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}, {0.0f, 1.0f}}, + {{-0.5f, 0.5f, 0.0f}, {1.0f, 1.0f, 1.0f}, {1.0f, 1.0f}}, + + {{-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}} }; const std::vector indices = { @@ -191,61 +135,64 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugReportCallbackEXT callback; + VkSurfaceKHR surface; VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; - VDeleter device{vkDestroyDevice}; + VkDevice device; VkQueue graphicsQueue; VkQueue presentQueue; - VDeleter swapChain{device, vkDestroySwapchainKHR}; + VkSwapchainKHR swapChain; std::vector swapChainImages; VkFormat swapChainImageFormat; VkExtent2D swapChainExtent; - std::vector> swapChainImageViews; - std::vector> swapChainFramebuffers; + std::vector swapChainImageViews; + std::vector swapChainFramebuffers; - VDeleter renderPass{device, vkDestroyRenderPass}; - VDeleter descriptorSetLayout{device, vkDestroyDescriptorSetLayout}; - VDeleter pipelineLayout{device, vkDestroyPipelineLayout}; - VDeleter graphicsPipeline{device, vkDestroyPipeline}; + VkRenderPass renderPass; + VkDescriptorSetLayout descriptorSetLayout; + VkPipelineLayout pipelineLayout; + VkPipeline graphicsPipeline; - VDeleter commandPool{device, vkDestroyCommandPool}; + VkCommandPool commandPool; - VDeleter depthImage{device, vkDestroyImage}; - VDeleter depthImageMemory{device, vkFreeMemory}; - VDeleter depthImageView{device, vkDestroyImageView}; + VkImage depthImage; + VkDeviceMemory depthImageMemory; + VkImageView depthImageView; - VDeleter textureImage{device, vkDestroyImage}; - VDeleter textureImageMemory{device, vkFreeMemory}; - VDeleter textureImageView{device, vkDestroyImageView}; - VDeleter textureSampler{device, vkDestroySampler}; + VkImage textureImage; + VkDeviceMemory textureImageMemory; + VkImageView textureImageView; + VkSampler textureSampler; - VDeleter vertexBuffer{device, vkDestroyBuffer}; - VDeleter vertexBufferMemory{device, vkFreeMemory}; - VDeleter indexBuffer{device, vkDestroyBuffer}; - VDeleter indexBufferMemory{device, vkFreeMemory}; + VkBuffer vertexBuffer; + VkDeviceMemory vertexBufferMemory; + VkBuffer indexBuffer; + VkDeviceMemory indexBufferMemory; - VDeleter uniformStagingBuffer{device, vkDestroyBuffer}; - VDeleter uniformStagingBufferMemory{device, vkFreeMemory}; - VDeleter uniformBuffer{device, vkDestroyBuffer}; - VDeleter uniformBufferMemory{device, vkFreeMemory}; + std::vector uniformBuffers; + std::vector uniformBuffersMemory; - VDeleter descriptorPool{device, vkDestroyDescriptorPool}; - VkDescriptorSet descriptorSet; + VkDescriptorPool descriptorPool; + std::vector descriptorSets; std::vector commandBuffers; - VDeleter imageAvailableSemaphore{device, vkDestroySemaphore}; - VDeleter renderFinishedSemaphore{device, vkDestroySemaphore}; + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + std::vector inFlightFences; + size_t currentFrame = 0; + + bool framebufferResized = false; void initWindow() { glfwInit(); @@ -253,9 +200,13 @@ class HelloTriangleApplication { glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetWindowSizeCallback(window, HelloTriangleApplication::onWindowResized); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; } void initVulkan() { @@ -277,38 +228,101 @@ class HelloTriangleApplication { createTextureSampler(); createVertexBuffer(); createIndexBuffer(); - createUniformBuffer(); + createUniformBuffers(); createDescriptorPool(); - createDescriptorSet(); + createDescriptorSets(); createCommandBuffers(); - createSemaphores(); + createSyncObjects(); } void mainLoop() { while (!glfwWindowShouldClose(window)) { glfwPollEvents(); - - updateUniformBuffer(); drawFrame(); } vkDeviceWaitIdle(device); + } - glfwDestroyWindow(window); + void cleanupSwapChain() { + vkDestroyImageView(device, depthImageView, nullptr); + vkDestroyImage(device, depthImage, nullptr); + vkFreeMemory(device, depthImageMemory, nullptr); - glfwTerminate(); + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } + + vkFreeCommandBuffers(device, commandPool, static_cast(commandBuffers.size()), commandBuffers.data()); + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); } - static void onWindowResized(GLFWwindow* window, int width, int height) { - if (width == 0 || height == 0) return; + void cleanup() { + cleanupSwapChain(); + + vkDestroySampler(device, textureSampler, nullptr); + vkDestroyImageView(device, textureImageView, nullptr); + + vkDestroyImage(device, textureImage, nullptr); + vkFreeMemory(device, textureImageMemory, nullptr); + + vkDestroyDescriptorPool(device, descriptorPool, nullptr); + + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + vkDestroyBuffer(device, uniformBuffers[i], nullptr); + vkFreeMemory(device, uniformBuffersMemory[i], nullptr); + } + + vkDestroyBuffer(device, indexBuffer, nullptr); + vkFreeMemory(device, indexBufferMemory, nullptr); + + vkDestroyBuffer(device, vertexBuffer, nullptr); + vkFreeMemory(device, vertexBufferMemory, nullptr); - HelloTriangleApplication* app = reinterpret_cast(glfwGetWindowUserPointer(window)); - app->recreateSwapChain(); + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + vkDestroyFence(device, inFlightFences[i], nullptr); + } + + vkDestroyCommandPool(device, commandPool, nullptr); + + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugReportCallbackEXT(instance, callback, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); } void recreateSwapChain() { + int width = 0, height = 0; + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + vkDeviceWaitIdle(device); + cleanupSwapChain(); + createSwapChain(); createImageViews(); createRenderPass(); @@ -336,17 +350,17 @@ class HelloTriangleApplication { createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } @@ -359,13 +373,13 @@ class HelloTriangleApplication { createInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT; createInfo.pfnCallback = debugCallback; - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { + if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, &callback) != VK_SUCCESS) { throw std::runtime_error("failed to set up debug callback!"); } } void createSurface() { - if (glfwCreateWindowSurface(instance, window, nullptr, surface.replace()) != VK_SUCCESS) { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { throw std::runtime_error("failed to create window surface!"); } } @@ -410,26 +424,27 @@ class HelloTriangleApplication { } VkPhysicalDeviceFeatures deviceFeatures = {}; + deviceFeatures.samplerAnisotropy = VK_TRUE; VkDeviceCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); createInfo.pQueueCreateInfos = queueCreateInfos.data(); - createInfo.queueCreateInfoCount = (uint32_t) queueCreateInfos.size(); createInfo.pEnabledFeatures = &deviceFeatures; - createInfo.enabledExtensionCount = deviceExtensions.size(); + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); createInfo.ppEnabledExtensionNames = deviceExtensions.data(); if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateDevice(physicalDevice, &createInfo, nullptr, device.replace()) != VK_SUCCESS) { + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { throw std::runtime_error("failed to create logical device!"); } @@ -476,16 +491,10 @@ class HelloTriangleApplication { createInfo.presentMode = presentMode; createInfo.clipped = VK_TRUE; - VkSwapchainKHR oldSwapChain = swapChain; - createInfo.oldSwapchain = oldSwapChain; - - VkSwapchainKHR newSwapChain; - if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &newSwapChain) != VK_SUCCESS) { + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { throw std::runtime_error("failed to create swap chain!"); } - swapChain = newSwapChain; - vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); swapChainImages.resize(imageCount); vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); @@ -495,10 +504,10 @@ class HelloTriangleApplication { } void createImageViews() { - swapChainImageViews.resize(swapChainImages.size(), VDeleter{device, vkDestroyImageView}); + swapChainImageViews.resize(swapChainImages.size()); for (uint32_t i = 0; i < swapChainImages.size(); i++) { - createImageView(swapChainImages[i], swapChainImageFormat, VK_IMAGE_ASPECT_COLOR_BIT, swapChainImageViews[i]); + swapChainImageViews[i] = createImageView(swapChainImages[i], swapChainImageFormat, VK_IMAGE_ASPECT_COLOR_BIT); } } @@ -548,14 +557,14 @@ class HelloTriangleApplication { std::array attachments = {colorAttachment, depthAttachment}; VkRenderPassCreateInfo renderPassInfo = {}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; - renderPassInfo.attachmentCount = attachments.size(); + renderPassInfo.attachmentCount = static_cast(attachments.size()); renderPassInfo.pAttachments = attachments.data(); renderPassInfo.subpassCount = 1; renderPassInfo.pSubpasses = &subpass; renderPassInfo.dependencyCount = 1; renderPassInfo.pDependencies = &dependency; - if (vkCreateRenderPass(device, &renderPassInfo, nullptr, renderPass.replace()) != VK_SUCCESS) { + if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { throw std::runtime_error("failed to create render pass!"); } } @@ -578,10 +587,10 @@ class HelloTriangleApplication { std::array bindings = {uboLayoutBinding, samplerLayoutBinding}; VkDescriptorSetLayoutCreateInfo layoutInfo = {}; layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; - layoutInfo.bindingCount = bindings.size(); + layoutInfo.bindingCount = static_cast(bindings.size()); layoutInfo.pBindings = bindings.data(); - if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, descriptorSetLayout.replace()) != VK_SUCCESS) { + if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &descriptorSetLayout) != VK_SUCCESS) { throw std::runtime_error("failed to create descriptor set layout!"); } } @@ -590,10 +599,8 @@ class HelloTriangleApplication { auto vertShaderCode = readFile("shaders/vert.spv"); auto fragShaderCode = readFile("shaders/frag.spv"); - VDeleter vertShaderModule{device, vkDestroyShaderModule}; - VDeleter fragShaderModule{device, vkDestroyShaderModule}; - createShaderModule(vertShaderCode, vertShaderModule); - createShaderModule(fragShaderCode, fragShaderModule); + VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); VkPipelineShaderStageCreateInfo vertShaderStageInfo = {}; vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; @@ -616,7 +623,7 @@ class HelloTriangleApplication { auto attributeDescriptions = Vertex::getAttributeDescriptions(); vertexInputInfo.vertexBindingDescriptionCount = 1; - vertexInputInfo.vertexAttributeDescriptionCount = attributeDescriptions.size(); + vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()); vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); @@ -682,13 +689,12 @@ class HelloTriangleApplication { colorBlending.blendConstants[2] = 0.0f; colorBlending.blendConstants[3] = 0.0f; - VkDescriptorSetLayout setLayouts[] = {descriptorSetLayout}; VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; pipelineLayoutInfo.setLayoutCount = 1; - pipelineLayoutInfo.pSetLayouts = setLayouts; + pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout; - if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, pipelineLayout.replace()) != VK_SUCCESS) { + if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { throw std::runtime_error("failed to create pipeline layout!"); } @@ -708,13 +714,16 @@ class HelloTriangleApplication { pipelineInfo.subpass = 0; pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; - if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, graphicsPipeline.replace()) != VK_SUCCESS) { + if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { throw std::runtime_error("failed to create graphics pipeline!"); } + + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); } void createFramebuffers() { - swapChainFramebuffers.resize(swapChainImageViews.size(), VDeleter{device, vkDestroyFramebuffer}); + swapChainFramebuffers.resize(swapChainImageViews.size()); for (size_t i = 0; i < swapChainImageViews.size(); i++) { std::array attachments = { @@ -725,13 +734,13 @@ class HelloTriangleApplication { VkFramebufferCreateInfo framebufferInfo = {}; framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; framebufferInfo.renderPass = renderPass; - framebufferInfo.attachmentCount = attachments.size(); + framebufferInfo.attachmentCount = static_cast(attachments.size()); framebufferInfo.pAttachments = attachments.data(); framebufferInfo.width = swapChainExtent.width; framebufferInfo.height = swapChainExtent.height; framebufferInfo.layers = 1; - if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, swapChainFramebuffers[i].replace()) != VK_SUCCESS) { + if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { throw std::runtime_error("failed to create framebuffer!"); } } @@ -744,7 +753,7 @@ class HelloTriangleApplication { poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily; - if (vkCreateCommandPool(device, &poolInfo, nullptr, commandPool.replace()) != VK_SUCCESS) { + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { throw std::runtime_error("failed to create graphics command pool!"); } } @@ -753,7 +762,7 @@ class HelloTriangleApplication { VkFormat depthFormat = findDepthFormat(); createImage(swapChainExtent.width, swapChainExtent.height, depthFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, depthImage, depthImageMemory); - createImageView(depthImage, depthFormat, VK_IMAGE_ASPECT_DEPTH_BIT, depthImageView); + depthImageView = createImageView(depthImage, depthFormat, VK_IMAGE_ASPECT_DEPTH_BIT); transitionImageLayout(depthImage, depthFormat, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); } @@ -794,46 +803,29 @@ class HelloTriangleApplication { throw std::runtime_error("failed to load texture image!"); } - VDeleter stagingImage{device, vkDestroyImage}; - VDeleter stagingImageMemory{device, vkFreeMemory}; - createImage(texWidth, texHeight, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_LINEAR, VK_IMAGE_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingImage, stagingImageMemory); - - VkImageSubresource subresource = {}; - subresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - subresource.mipLevel = 0; - subresource.arrayLayer = 0; - - VkSubresourceLayout stagingImageLayout; - vkGetImageSubresourceLayout(device, stagingImage, &subresource, &stagingImageLayout); + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); void* data; - vkMapMemory(device, stagingImageMemory, 0, imageSize, 0, &data); - - if (stagingImageLayout.rowPitch == texWidth * 4) { - memcpy(data, pixels, (size_t) imageSize); - } else { - uint8_t* dataBytes = reinterpret_cast(data); - - for (int y = 0; y < texHeight; y++) { - memcpy(&dataBytes[y * stagingImageLayout.rowPitch], &pixels[y * texWidth * 4], texWidth * 4); - } - } - - vkUnmapMemory(device, stagingImageMemory); + vkMapMemory(device, stagingBufferMemory, 0, imageSize, 0, &data); + memcpy(data, pixels, static_cast(imageSize)); + vkUnmapMemory(device, stagingBufferMemory); stbi_image_free(pixels); createImage(texWidth, texHeight, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, textureImage, textureImageMemory); - transitionImageLayout(stagingImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_PREINITIALIZED, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); - transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_PREINITIALIZED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); - copyImage(stagingImage, textureImage, texWidth, texHeight); - + transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); } void createTextureImageView() { - createImageView(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT, textureImageView); + textureImageView = createImageView(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT); } void createTextureSampler() { @@ -852,12 +844,12 @@ class HelloTriangleApplication { samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS; samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; - if (vkCreateSampler(device, &samplerInfo, nullptr, textureSampler.replace()) != VK_SUCCESS) { + if (vkCreateSampler(device, &samplerInfo, nullptr, &textureSampler) != VK_SUCCESS) { throw std::runtime_error("failed to create texture sampler!"); } } - void createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags, VDeleter& imageView) { + VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags) { VkImageViewCreateInfo viewInfo = {}; viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; viewInfo.image = image; @@ -869,12 +861,15 @@ class HelloTriangleApplication { viewInfo.subresourceRange.baseArrayLayer = 0; viewInfo.subresourceRange.layerCount = 1; - if (vkCreateImageView(device, &viewInfo, nullptr, imageView.replace()) != VK_SUCCESS) { + VkImageView imageView; + if (vkCreateImageView(device, &viewInfo, nullptr, &imageView) != VK_SUCCESS) { throw std::runtime_error("failed to create texture image view!"); } + + return imageView; } - void createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VDeleter& image, VDeleter& imageMemory) { + void createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage& image, VkDeviceMemory& imageMemory) { VkImageCreateInfo imageInfo = {}; imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; imageInfo.imageType = VK_IMAGE_TYPE_2D; @@ -885,12 +880,12 @@ class HelloTriangleApplication { imageInfo.arrayLayers = 1; imageInfo.format = format; imageInfo.tiling = tiling; - imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED; + imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; imageInfo.usage = usage; imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - if (vkCreateImage(device, &imageInfo, nullptr, image.replace()) != VK_SUCCESS) { + if (vkCreateImage(device, &imageInfo, nullptr, &image) != VK_SUCCESS) { throw std::runtime_error("failed to create image!"); } @@ -902,7 +897,7 @@ class HelloTriangleApplication { allocInfo.allocationSize = memRequirements.size; allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); - if (vkAllocateMemory(device, &allocInfo, nullptr, imageMemory.replace()) != VK_SUCCESS) { + if (vkAllocateMemory(device, &allocInfo, nullptr, &imageMemory) != VK_SUCCESS) { throw std::runtime_error("failed to allocate image memory!"); } @@ -935,25 +930,34 @@ class HelloTriangleApplication { barrier.subresourceRange.baseArrayLayer = 0; barrier.subresourceRange.layerCount = 1; - if (oldLayout == VK_IMAGE_LAYOUT_PREINITIALIZED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL) { - barrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT; - barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; - } else if (oldLayout == VK_IMAGE_LAYOUT_PREINITIALIZED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { - barrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT; + VkPipelineStageFlags sourceStage; + VkPipelineStageFlags destinationStage; + + if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { + barrier.srcAccessMask = 0; barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + + sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT; } else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + + sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; } else if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) { barrier.srcAccessMask = 0; barrier.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + + sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + destinationStage = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; } else { throw std::invalid_argument("unsupported layout transition!"); } vkCmdPipelineBarrier( commandBuffer, - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + sourceStage, destinationStage, 0, 0, nullptr, 0, nullptr, @@ -963,30 +967,25 @@ class HelloTriangleApplication { endSingleTimeCommands(commandBuffer); } - void copyImage(VkImage srcImage, VkImage dstImage, uint32_t width, uint32_t height) { + void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) { VkCommandBuffer commandBuffer = beginSingleTimeCommands(); - VkImageSubresourceLayers subResource = {}; - subResource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - subResource.baseArrayLayer = 0; - subResource.mipLevel = 0; - subResource.layerCount = 1; - - VkImageCopy region = {}; - region.srcSubresource = subResource; - region.dstSubresource = subResource; - region.srcOffset = {0, 0, 0}; - region.dstOffset = {0, 0, 0}; - region.extent.width = width; - region.extent.height = height; - region.extent.depth = 1; - - vkCmdCopyImage( - commandBuffer, - srcImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - dstImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - 1, ®ion - ); + VkBufferImageCopy region = {}; + region.bufferOffset = 0; + region.bufferRowLength = 0; + region.bufferImageHeight = 0; + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.mipLevel = 0; + region.imageSubresource.baseArrayLayer = 0; + region.imageSubresource.layerCount = 1; + region.imageOffset = {0, 0, 0}; + region.imageExtent = { + width, + height, + 1 + }; + + vkCmdCopyBufferToImage(commandBuffer, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); endSingleTimeCommands(commandBuffer); } @@ -994,8 +993,8 @@ class HelloTriangleApplication { void createVertexBuffer() { VkDeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); - VDeleter stagingBuffer{device, vkDestroyBuffer}; - VDeleter stagingBufferMemory{device, vkFreeMemory}; + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); void* data; @@ -1006,13 +1005,16 @@ class HelloTriangleApplication { createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, vertexBuffer, vertexBufferMemory); copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); } void createIndexBuffer() { VkDeviceSize bufferSize = sizeof(indices[0]) * indices.size(); - VDeleter stagingBuffer{device, vkDestroyBuffer}; - VDeleter stagingBufferMemory{device, vkFreeMemory}; + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); void* data; @@ -1023,84 +1025,94 @@ class HelloTriangleApplication { createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, indexBuffer, indexBufferMemory); copyBuffer(stagingBuffer, indexBuffer, bufferSize); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); } - void createUniformBuffer() { + void createUniformBuffers() { VkDeviceSize bufferSize = sizeof(UniformBufferObject); - createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformStagingBuffer, uniformStagingBufferMemory); - createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, uniformBuffer, uniformBufferMemory); + uniformBuffers.resize(swapChainImages.size()); + uniformBuffersMemory.resize(swapChainImages.size()); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + createBuffer(bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformBuffers[i], uniformBuffersMemory[i]); + } } void createDescriptorPool() { std::array poolSizes = {}; poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - poolSizes[0].descriptorCount = 1; + poolSizes[0].descriptorCount = static_cast(swapChainImages.size()); poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - poolSizes[1].descriptorCount = 1; + poolSizes[1].descriptorCount = static_cast(swapChainImages.size()); VkDescriptorPoolCreateInfo poolInfo = {}; poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; - poolInfo.poolSizeCount = poolSizes.size(); + poolInfo.poolSizeCount = static_cast(poolSizes.size()); poolInfo.pPoolSizes = poolSizes.data(); - poolInfo.maxSets = 1; + poolInfo.maxSets = static_cast(swapChainImages.size()); - if (vkCreateDescriptorPool(device, &poolInfo, nullptr, descriptorPool.replace()) != VK_SUCCESS) { + if (vkCreateDescriptorPool(device, &poolInfo, nullptr, &descriptorPool) != VK_SUCCESS) { throw std::runtime_error("failed to create descriptor pool!"); } } - void createDescriptorSet() { - VkDescriptorSetLayout layouts[] = {descriptorSetLayout}; + void createDescriptorSets() { + std::vector layouts(swapChainImages.size(), descriptorSetLayout); VkDescriptorSetAllocateInfo allocInfo = {}; allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; allocInfo.descriptorPool = descriptorPool; - allocInfo.descriptorSetCount = 1; - allocInfo.pSetLayouts = layouts; + allocInfo.descriptorSetCount = static_cast(swapChainImages.size()); + allocInfo.pSetLayouts = layouts.data(); - if (vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet) != VK_SUCCESS) { - throw std::runtime_error("failed to allocate descriptor set!"); + descriptorSets.resize(swapChainImages.size()); + if (vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets[0]) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate descriptor sets!"); } - VkDescriptorBufferInfo bufferInfo = {}; - bufferInfo.buffer = uniformBuffer; - bufferInfo.offset = 0; - bufferInfo.range = sizeof(UniformBufferObject); - - VkDescriptorImageInfo imageInfo = {}; - imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - imageInfo.imageView = textureImageView; - imageInfo.sampler = textureSampler; - - std::array descriptorWrites = {}; - - descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; - descriptorWrites[0].dstSet = descriptorSet; - descriptorWrites[0].dstBinding = 0; - descriptorWrites[0].dstArrayElement = 0; - descriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - descriptorWrites[0].descriptorCount = 1; - descriptorWrites[0].pBufferInfo = &bufferInfo; - - descriptorWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; - descriptorWrites[1].dstSet = descriptorSet; - descriptorWrites[1].dstBinding = 1; - descriptorWrites[1].dstArrayElement = 0; - descriptorWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - descriptorWrites[1].descriptorCount = 1; - descriptorWrites[1].pImageInfo = &imageInfo; - - vkUpdateDescriptorSets(device, descriptorWrites.size(), descriptorWrites.data(), 0, nullptr); + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkDescriptorBufferInfo bufferInfo = {}; + bufferInfo.buffer = uniformBuffers[i]; + bufferInfo.offset = 0; + bufferInfo.range = sizeof(UniformBufferObject); + + VkDescriptorImageInfo imageInfo = {}; + imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + imageInfo.imageView = textureImageView; + imageInfo.sampler = textureSampler; + + std::array descriptorWrites = {}; + + descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[0].dstSet = descriptorSets[i]; + descriptorWrites[0].dstBinding = 0; + descriptorWrites[0].dstArrayElement = 0; + descriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptorWrites[0].descriptorCount = 1; + descriptorWrites[0].pBufferInfo = &bufferInfo; + + descriptorWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[1].dstSet = descriptorSets[i]; + descriptorWrites[1].dstBinding = 1; + descriptorWrites[1].dstArrayElement = 0; + descriptorWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + descriptorWrites[1].descriptorCount = 1; + descriptorWrites[1].pImageInfo = &imageInfo; + + vkUpdateDescriptorSets(device, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); + } } - void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VDeleter& buffer, VDeleter& bufferMemory) { + void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) { VkBufferCreateInfo bufferInfo = {}; bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; bufferInfo.size = size; bufferInfo.usage = usage; - bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - if (vkCreateBuffer(device, &bufferInfo, nullptr, buffer.replace()) != VK_SUCCESS) { + if (vkCreateBuffer(device, &bufferInfo, nullptr, &buffer) != VK_SUCCESS) { throw std::runtime_error("failed to create buffer!"); } @@ -1112,7 +1124,7 @@ class HelloTriangleApplication { allocInfo.allocationSize = memRequirements.size; allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); - if (vkAllocateMemory(device, &allocInfo, nullptr, bufferMemory.replace()) != VK_SUCCESS) { + if (vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) { throw std::runtime_error("failed to allocate buffer memory!"); } @@ -1176,10 +1188,6 @@ class HelloTriangleApplication { } void createCommandBuffers() { - if (commandBuffers.size() > 0) { - vkFreeCommandBuffers(device, commandPool, commandBuffers.size(), commandBuffers.data()); - } - commandBuffers.resize(swapChainFramebuffers.size()); VkCommandBufferAllocateInfo allocInfo = {}; @@ -1197,7 +1205,9 @@ class HelloTriangleApplication { beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; beginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT; - vkBeginCommandBuffer(commandBuffers[i], &beginInfo); + if (vkBeginCommandBuffer(commandBuffers[i], &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } VkRenderPassBeginInfo renderPassInfo = {}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; @@ -1210,7 +1220,7 @@ class HelloTriangleApplication { clearValues[0].color = {0.0f, 0.0f, 0.0f, 1.0f}; clearValues[1].depthStencil = {1.0f, 0}; - renderPassInfo.clearValueCount = clearValues.size(); + renderPassInfo.clearValueCount = static_cast(clearValues.size()); renderPassInfo.pClearValues = clearValues.data(); vkCmdBeginRenderPass(commandBuffers[i], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); @@ -1223,9 +1233,9 @@ class HelloTriangleApplication { vkCmdBindIndexBuffer(commandBuffers[i], indexBuffer, 0, VK_INDEX_TYPE_UINT16); - vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr); + vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets[i], 0, nullptr); - vkCmdDrawIndexed(commandBuffers[i], indices.size(), 1, 0, 0, 0); + vkCmdDrawIndexed(commandBuffers[i], static_cast(indices.size()), 1, 0, 0, 0); vkCmdEndRenderPass(commandBuffers[i]); @@ -1235,40 +1245,50 @@ class HelloTriangleApplication { } } - void createSemaphores() { + void createSyncObjects() { + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + inFlightFences.resize(MAX_FRAMES_IN_FLIGHT); + VkSemaphoreCreateInfo semaphoreInfo = {}; semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; - if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, imageAvailableSemaphore.replace()) != VK_SUCCESS || - vkCreateSemaphore(device, &semaphoreInfo, nullptr, renderFinishedSemaphore.replace()) != VK_SUCCESS) { + VkFenceCreateInfo fenceInfo = {}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; - throw std::runtime_error("failed to create semaphores!"); + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS || + vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create synchronization objects for a frame!"); + } } } - void updateUniformBuffer() { + 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_cast(currentTime - startTime).count() / 1000.0f; + float time = std::chrono::duration(currentTime - startTime).count(); UniformBufferObject ubo = {}; - ubo.model = glm::rotate(glm::mat4(), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + 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; - vkMapMemory(device, uniformStagingBufferMemory, 0, sizeof(ubo), 0, &data); + vkMapMemory(device, uniformBuffersMemory[currentImage], 0, sizeof(ubo), 0, &data); memcpy(data, &ubo, sizeof(ubo)); - vkUnmapMemory(device, uniformStagingBufferMemory); - - copyBuffer(uniformStagingBuffer, uniformBuffer, sizeof(ubo)); + vkUnmapMemory(device, uniformBuffersMemory[currentImage]); } void drawFrame() { + vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, std::numeric_limits::max()); + uint32_t imageIndex; - VkResult result = vkAcquireNextImageKHR(device, swapChain, std::numeric_limits::max(), imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex); + VkResult result = vkAcquireNextImageKHR(device, swapChain, std::numeric_limits::max(), imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); if (result == VK_ERROR_OUT_OF_DATE_KHR) { recreateSwapChain(); @@ -1277,10 +1297,12 @@ class HelloTriangleApplication { throw std::runtime_error("failed to acquire swap chain image!"); } + updateUniformBuffer(imageIndex); + VkSubmitInfo submitInfo = {}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; - VkSemaphore waitSemaphores[] = {imageAvailableSemaphore}; + VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[currentFrame]}; VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; submitInfo.waitSemaphoreCount = 1; submitInfo.pWaitSemaphores = waitSemaphores; @@ -1289,11 +1311,13 @@ class HelloTriangleApplication { submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &commandBuffers[imageIndex]; - VkSemaphore signalSemaphores[] = {renderFinishedSemaphore}; + VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[currentFrame]}; submitInfo.signalSemaphoreCount = 1; submitInfo.pSignalSemaphores = signalSemaphores; - if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) { throw std::runtime_error("failed to submit draw command buffer!"); } @@ -1311,26 +1335,28 @@ class HelloTriangleApplication { result = vkQueuePresentKHR(presentQueue, &presentInfo); - if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { + framebufferResized = false; recreateSwapChain(); } else if (result != VK_SUCCESS) { throw std::runtime_error("failed to present swap chain image!"); } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; } - void createShaderModule(const std::vector& code, VDeleter& shaderModule) { + VkShaderModule createShaderModule(const std::vector& code) { VkShaderModuleCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); - std::vector codeAligned(code.size() / 4 + 1); - memcpy(codeAligned.data(), code.data(), code.size()); - - createInfo.pCode = codeAligned.data(); - - if (vkCreateShaderModule(device, &createInfo, nullptr, shaderModule.replace()) != VK_SUCCESS) { + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { throw std::runtime_error("failed to create shader module!"); } + + return shaderModule; } VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { @@ -1366,9 +1392,12 @@ class HelloTriangleApplication { return capabilities.currentExtent; } else { int width, height; - glfwGetWindowSize(window, &width, &height); + glfwGetFramebufferSize(window, &width, &height); - VkExtent2D actualExtent = {width, height}; + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; actualExtent.width = std::max(capabilities.minImageExtent.width, std::min(capabilities.maxImageExtent.width, actualExtent.width)); actualExtent.height = std::max(capabilities.minImageExtent.height, std::min(capabilities.maxImageExtent.height, actualExtent.height)); @@ -1412,7 +1441,10 @@ class HelloTriangleApplication { swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); } - return indices.isComplete() && extensionsSupported && swapChainAdequate; + VkPhysicalDeviceFeatures supportedFeatures; + vkGetPhysicalDeviceFeatures(device, &supportedFeatures); + + return indices.isComplete() && extensionsSupported && swapChainAdequate && supportedFeatures.samplerAnisotropy; } bool checkDeviceExtensionSupport(VkPhysicalDevice device) { @@ -1464,15 +1496,11 @@ class HelloTriangleApplication { } std::vector getRequiredExtensions() { - std::vector extensions; - - unsigned int glfwExtensionCount = 0; + uint32_t glfwExtensionCount = 0; const char** glfwExtensions; glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - for (unsigned int i = 0; i < glfwExtensionCount; i++) { - extensions.push_back(glfwExtensions[i]); - } + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); if (enableValidationLayers) { extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); @@ -1536,7 +1564,7 @@ int main() { try { app.run(); - } catch (const std::runtime_error& e) { + } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; } diff --git a/code/shader_textures.frag b/code/26_shader_depth.frag similarity index 100% rename from code/shader_textures.frag rename to code/26_shader_depth.frag diff --git a/code/shader_depth.vert b/code/26_shader_depth.vert similarity index 100% rename from code/shader_depth.vert rename to code/26_shader_depth.vert diff --git a/code/model_loading.cpp b/code/27_model_loading.cpp similarity index 75% rename from code/model_loading.cpp rename to code/27_model_loading.cpp index 67d625e5..ea08ef00 100644 --- a/code/model_loading.cpp +++ b/code/27_model_loading.cpp @@ -3,6 +3,7 @@ #define GLM_FORCE_RADIANS #define GLM_FORCE_DEPTH_ZERO_TO_ONE +#define GLM_ENABLE_EXPERIMENTAL #include #include #include @@ -14,13 +15,13 @@ #include #include -#include -#include -#include #include +#include #include +#include #include #include +#include #include #include #include @@ -31,6 +32,8 @@ const int HEIGHT = 600; const std::string MODEL_PATH = "models/chalet.obj"; const std::string TEXTURE_PATH = "textures/chalet.jpg"; +const int MAX_FRAMES_IN_FLIGHT = 2; + const std::vector validationLayers = { "VK_LAYER_LUNARG_standard_validation" }; @@ -61,64 +64,6 @@ void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT } } -template -class VDeleter { -public: - VDeleter() : VDeleter([](T, VkAllocationCallbacks*) {}) {} - - VDeleter(std::function deletef) { - this->deleter = [=](T obj) { deletef(obj, nullptr); }; - } - - VDeleter(const VDeleter& instance, std::function deletef) { - this->deleter = [&instance, deletef](T obj) { deletef(instance, obj, nullptr); }; - } - - VDeleter(const VDeleter& device, std::function deletef) { - this->deleter = [&device, deletef](T obj) { deletef(device, obj, nullptr); }; - } - - ~VDeleter() { - cleanup(); - } - - const T* operator &() const { - return &object; - } - - T* replace() { - cleanup(); - return &object; - } - - operator T() const { - return object; - } - - void operator=(T rhs) { - if (rhs != object) { - cleanup(); - object = rhs; - } - } - - template - bool operator==(V rhs) { - return object == T(rhs); - } - -private: - T object{VK_NULL_HANDLE}; - std::function deleter; - - void cleanup() { - if (object != VK_NULL_HANDLE) { - deleter(object); - } - object = VK_NULL_HANDLE; - } -}; - struct QueueFamilyIndices { int graphicsFamily = -1; int presentFamily = -1; @@ -194,63 +139,66 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugReportCallbackEXT callback; + VkSurfaceKHR surface; VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; - VDeleter device{vkDestroyDevice}; + VkDevice device; VkQueue graphicsQueue; VkQueue presentQueue; - VDeleter swapChain{device, vkDestroySwapchainKHR}; + VkSwapchainKHR swapChain; std::vector swapChainImages; VkFormat swapChainImageFormat; VkExtent2D swapChainExtent; - std::vector> swapChainImageViews; - std::vector> swapChainFramebuffers; + std::vector swapChainImageViews; + std::vector swapChainFramebuffers; - VDeleter renderPass{device, vkDestroyRenderPass}; - VDeleter descriptorSetLayout{device, vkDestroyDescriptorSetLayout}; - VDeleter pipelineLayout{device, vkDestroyPipelineLayout}; - VDeleter graphicsPipeline{device, vkDestroyPipeline}; + VkRenderPass renderPass; + VkDescriptorSetLayout descriptorSetLayout; + VkPipelineLayout pipelineLayout; + VkPipeline graphicsPipeline; - VDeleter commandPool{device, vkDestroyCommandPool}; + VkCommandPool commandPool; - VDeleter depthImage{device, vkDestroyImage}; - VDeleter depthImageMemory{device, vkFreeMemory}; - VDeleter depthImageView{device, vkDestroyImageView}; + VkImage depthImage; + VkDeviceMemory depthImageMemory; + VkImageView depthImageView; - VDeleter textureImage{device, vkDestroyImage}; - VDeleter textureImageMemory{device, vkFreeMemory}; - VDeleter textureImageView{device, vkDestroyImageView}; - VDeleter textureSampler{device, vkDestroySampler}; + VkImage textureImage; + VkDeviceMemory textureImageMemory; + VkImageView textureImageView; + VkSampler textureSampler; std::vector vertices; std::vector indices; - VDeleter vertexBuffer{device, vkDestroyBuffer}; - VDeleter vertexBufferMemory{device, vkFreeMemory}; - VDeleter indexBuffer{device, vkDestroyBuffer}; - VDeleter indexBufferMemory{device, vkFreeMemory}; + VkBuffer vertexBuffer; + VkDeviceMemory vertexBufferMemory; + VkBuffer indexBuffer; + VkDeviceMemory indexBufferMemory; - VDeleter uniformStagingBuffer{device, vkDestroyBuffer}; - VDeleter uniformStagingBufferMemory{device, vkFreeMemory}; - VDeleter uniformBuffer{device, vkDestroyBuffer}; - VDeleter uniformBufferMemory{device, vkFreeMemory}; + std::vector uniformBuffers; + std::vector uniformBuffersMemory; - VDeleter descriptorPool{device, vkDestroyDescriptorPool}; - VkDescriptorSet descriptorSet; + VkDescriptorPool descriptorPool; + std::vector descriptorSets; std::vector commandBuffers; - VDeleter imageAvailableSemaphore{device, vkDestroySemaphore}; - VDeleter renderFinishedSemaphore{device, vkDestroySemaphore}; + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + std::vector inFlightFences; + size_t currentFrame = 0; + + bool framebufferResized = false; void initWindow() { glfwInit(); @@ -258,9 +206,13 @@ class HelloTriangleApplication { glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - glfwSetWindowUserPointer(window, this); - glfwSetWindowSizeCallback(window, HelloTriangleApplication::onWindowResized); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; } void initVulkan() { @@ -283,38 +235,101 @@ class HelloTriangleApplication { loadModel(); createVertexBuffer(); createIndexBuffer(); - createUniformBuffer(); + createUniformBuffers(); createDescriptorPool(); - createDescriptorSet(); + createDescriptorSets(); createCommandBuffers(); - createSemaphores(); + createSyncObjects(); } void mainLoop() { while (!glfwWindowShouldClose(window)) { glfwPollEvents(); - - updateUniformBuffer(); drawFrame(); } vkDeviceWaitIdle(device); + } - glfwDestroyWindow(window); + void cleanupSwapChain() { + vkDestroyImageView(device, depthImageView, nullptr); + vkDestroyImage(device, depthImage, nullptr); + vkFreeMemory(device, depthImageMemory, nullptr); - glfwTerminate(); + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } + + vkFreeCommandBuffers(device, commandPool, static_cast(commandBuffers.size()), commandBuffers.data()); + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); } - static void onWindowResized(GLFWwindow* window, int width, int height) { - if (width == 0 || height == 0) return; + void cleanup() { + cleanupSwapChain(); + + vkDestroySampler(device, textureSampler, nullptr); + vkDestroyImageView(device, textureImageView, nullptr); + + vkDestroyImage(device, textureImage, nullptr); + vkFreeMemory(device, textureImageMemory, nullptr); + + vkDestroyDescriptorPool(device, descriptorPool, nullptr); + + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + vkDestroyBuffer(device, uniformBuffers[i], nullptr); + vkFreeMemory(device, uniformBuffersMemory[i], nullptr); + } + + vkDestroyBuffer(device, indexBuffer, nullptr); + vkFreeMemory(device, indexBufferMemory, nullptr); + + vkDestroyBuffer(device, vertexBuffer, nullptr); + vkFreeMemory(device, vertexBufferMemory, nullptr); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + vkDestroyFence(device, inFlightFences[i], nullptr); + } + + vkDestroyCommandPool(device, commandPool, nullptr); + + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugReportCallbackEXT(instance, callback, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); - HelloTriangleApplication* app = reinterpret_cast(glfwGetWindowUserPointer(window)); - app->recreateSwapChain(); + glfwTerminate(); } void recreateSwapChain() { + int width = 0, height = 0; + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + vkDeviceWaitIdle(device); + cleanupSwapChain(); + createSwapChain(); createImageViews(); createRenderPass(); @@ -342,17 +357,17 @@ class HelloTriangleApplication { createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } @@ -365,13 +380,13 @@ class HelloTriangleApplication { createInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT; createInfo.pfnCallback = debugCallback; - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { + if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, &callback) != VK_SUCCESS) { throw std::runtime_error("failed to set up debug callback!"); } } void createSurface() { - if (glfwCreateWindowSurface(instance, window, nullptr, surface.replace()) != VK_SUCCESS) { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { throw std::runtime_error("failed to create window surface!"); } } @@ -416,26 +431,27 @@ class HelloTriangleApplication { } VkPhysicalDeviceFeatures deviceFeatures = {}; + deviceFeatures.samplerAnisotropy = VK_TRUE; VkDeviceCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); createInfo.pQueueCreateInfos = queueCreateInfos.data(); - createInfo.queueCreateInfoCount = (uint32_t) queueCreateInfos.size(); createInfo.pEnabledFeatures = &deviceFeatures; - createInfo.enabledExtensionCount = deviceExtensions.size(); + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); createInfo.ppEnabledExtensionNames = deviceExtensions.data(); if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } - if (vkCreateDevice(physicalDevice, &createInfo, nullptr, device.replace()) != VK_SUCCESS) { + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { throw std::runtime_error("failed to create logical device!"); } @@ -482,16 +498,10 @@ class HelloTriangleApplication { createInfo.presentMode = presentMode; createInfo.clipped = VK_TRUE; - VkSwapchainKHR oldSwapChain = swapChain; - createInfo.oldSwapchain = oldSwapChain; - - VkSwapchainKHR newSwapChain; - if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &newSwapChain) != VK_SUCCESS) { + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { throw std::runtime_error("failed to create swap chain!"); } - swapChain = newSwapChain; - vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); swapChainImages.resize(imageCount); vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); @@ -501,10 +511,10 @@ class HelloTriangleApplication { } void createImageViews() { - swapChainImageViews.resize(swapChainImages.size(), VDeleter{device, vkDestroyImageView}); + swapChainImageViews.resize(swapChainImages.size()); for (uint32_t i = 0; i < swapChainImages.size(); i++) { - createImageView(swapChainImages[i], swapChainImageFormat, VK_IMAGE_ASPECT_COLOR_BIT, swapChainImageViews[i]); + swapChainImageViews[i] = createImageView(swapChainImages[i], swapChainImageFormat, VK_IMAGE_ASPECT_COLOR_BIT); } } @@ -554,14 +564,14 @@ class HelloTriangleApplication { std::array attachments = {colorAttachment, depthAttachment}; VkRenderPassCreateInfo renderPassInfo = {}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; - renderPassInfo.attachmentCount = attachments.size(); + renderPassInfo.attachmentCount = static_cast(attachments.size()); renderPassInfo.pAttachments = attachments.data(); renderPassInfo.subpassCount = 1; renderPassInfo.pSubpasses = &subpass; renderPassInfo.dependencyCount = 1; renderPassInfo.pDependencies = &dependency; - if (vkCreateRenderPass(device, &renderPassInfo, nullptr, renderPass.replace()) != VK_SUCCESS) { + if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { throw std::runtime_error("failed to create render pass!"); } } @@ -584,10 +594,10 @@ class HelloTriangleApplication { std::array bindings = {uboLayoutBinding, samplerLayoutBinding}; VkDescriptorSetLayoutCreateInfo layoutInfo = {}; layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; - layoutInfo.bindingCount = bindings.size(); + layoutInfo.bindingCount = static_cast(bindings.size()); layoutInfo.pBindings = bindings.data(); - if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, descriptorSetLayout.replace()) != VK_SUCCESS) { + if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &descriptorSetLayout) != VK_SUCCESS) { throw std::runtime_error("failed to create descriptor set layout!"); } } @@ -596,10 +606,8 @@ class HelloTriangleApplication { auto vertShaderCode = readFile("shaders/vert.spv"); auto fragShaderCode = readFile("shaders/frag.spv"); - VDeleter vertShaderModule{device, vkDestroyShaderModule}; - VDeleter fragShaderModule{device, vkDestroyShaderModule}; - createShaderModule(vertShaderCode, vertShaderModule); - createShaderModule(fragShaderCode, fragShaderModule); + VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); VkPipelineShaderStageCreateInfo vertShaderStageInfo = {}; vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; @@ -622,7 +630,7 @@ class HelloTriangleApplication { auto attributeDescriptions = Vertex::getAttributeDescriptions(); vertexInputInfo.vertexBindingDescriptionCount = 1; - vertexInputInfo.vertexAttributeDescriptionCount = attributeDescriptions.size(); + vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()); vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); @@ -688,13 +696,12 @@ class HelloTriangleApplication { colorBlending.blendConstants[2] = 0.0f; colorBlending.blendConstants[3] = 0.0f; - VkDescriptorSetLayout setLayouts[] = {descriptorSetLayout}; VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; pipelineLayoutInfo.setLayoutCount = 1; - pipelineLayoutInfo.pSetLayouts = setLayouts; + pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout; - if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, pipelineLayout.replace()) != VK_SUCCESS) { + if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { throw std::runtime_error("failed to create pipeline layout!"); } @@ -714,13 +721,16 @@ class HelloTriangleApplication { pipelineInfo.subpass = 0; pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; - if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, graphicsPipeline.replace()) != VK_SUCCESS) { + if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { throw std::runtime_error("failed to create graphics pipeline!"); } + + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); } void createFramebuffers() { - swapChainFramebuffers.resize(swapChainImageViews.size(), VDeleter{device, vkDestroyFramebuffer}); + swapChainFramebuffers.resize(swapChainImageViews.size()); for (size_t i = 0; i < swapChainImageViews.size(); i++) { std::array attachments = { @@ -731,13 +741,13 @@ class HelloTriangleApplication { VkFramebufferCreateInfo framebufferInfo = {}; framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; framebufferInfo.renderPass = renderPass; - framebufferInfo.attachmentCount = attachments.size(); + framebufferInfo.attachmentCount = static_cast(attachments.size()); framebufferInfo.pAttachments = attachments.data(); framebufferInfo.width = swapChainExtent.width; framebufferInfo.height = swapChainExtent.height; framebufferInfo.layers = 1; - if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, swapChainFramebuffers[i].replace()) != VK_SUCCESS) { + if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { throw std::runtime_error("failed to create framebuffer!"); } } @@ -750,7 +760,7 @@ class HelloTriangleApplication { poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily; - if (vkCreateCommandPool(device, &poolInfo, nullptr, commandPool.replace()) != VK_SUCCESS) { + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { throw std::runtime_error("failed to create graphics command pool!"); } } @@ -759,7 +769,7 @@ class HelloTriangleApplication { VkFormat depthFormat = findDepthFormat(); createImage(swapChainExtent.width, swapChainExtent.height, depthFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, depthImage, depthImageMemory); - createImageView(depthImage, depthFormat, VK_IMAGE_ASPECT_DEPTH_BIT, depthImageView); + depthImageView = createImageView(depthImage, depthFormat, VK_IMAGE_ASPECT_DEPTH_BIT); transitionImageLayout(depthImage, depthFormat, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); } @@ -781,7 +791,7 @@ class HelloTriangleApplication { VkFormat findDepthFormat() { return findSupportedFormat( - {VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT}, + {VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT}, VK_IMAGE_TILING_OPTIMAL, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT ); @@ -800,46 +810,29 @@ class HelloTriangleApplication { throw std::runtime_error("failed to load texture image!"); } - VDeleter stagingImage{device, vkDestroyImage}; - VDeleter stagingImageMemory{device, vkFreeMemory}; - createImage(texWidth, texHeight, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_LINEAR, VK_IMAGE_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingImage, stagingImageMemory); - - VkImageSubresource subresource = {}; - subresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - subresource.mipLevel = 0; - subresource.arrayLayer = 0; - - VkSubresourceLayout stagingImageLayout; - vkGetImageSubresourceLayout(device, stagingImage, &subresource, &stagingImageLayout); + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); void* data; - vkMapMemory(device, stagingImageMemory, 0, imageSize, 0, &data); - - if (stagingImageLayout.rowPitch == texWidth * 4) { - memcpy(data, pixels, (size_t) imageSize); - } else { - uint8_t* dataBytes = reinterpret_cast(data); - - for (int y = 0; y < texHeight; y++) { - memcpy(&dataBytes[y * stagingImageLayout.rowPitch], &pixels[y * texWidth * 4], texWidth * 4); - } - } - - vkUnmapMemory(device, stagingImageMemory); + vkMapMemory(device, stagingBufferMemory, 0, imageSize, 0, &data); + memcpy(data, pixels, static_cast(imageSize)); + vkUnmapMemory(device, stagingBufferMemory); stbi_image_free(pixels); createImage(texWidth, texHeight, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, textureImage, textureImageMemory); - transitionImageLayout(stagingImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_PREINITIALIZED, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); - transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_PREINITIALIZED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); - copyImage(stagingImage, textureImage, texWidth, texHeight); - + transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); } void createTextureImageView() { - createImageView(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT, textureImageView); + textureImageView = createImageView(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT); } void createTextureSampler() { @@ -858,12 +851,12 @@ class HelloTriangleApplication { samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS; samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; - if (vkCreateSampler(device, &samplerInfo, nullptr, textureSampler.replace()) != VK_SUCCESS) { + if (vkCreateSampler(device, &samplerInfo, nullptr, &textureSampler) != VK_SUCCESS) { throw std::runtime_error("failed to create texture sampler!"); } } - void createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags, VDeleter& imageView) { + VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags) { VkImageViewCreateInfo viewInfo = {}; viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; viewInfo.image = image; @@ -875,12 +868,15 @@ class HelloTriangleApplication { viewInfo.subresourceRange.baseArrayLayer = 0; viewInfo.subresourceRange.layerCount = 1; - if (vkCreateImageView(device, &viewInfo, nullptr, imageView.replace()) != VK_SUCCESS) { + VkImageView imageView; + if (vkCreateImageView(device, &viewInfo, nullptr, &imageView) != VK_SUCCESS) { throw std::runtime_error("failed to create texture image view!"); } + + return imageView; } - void createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VDeleter& image, VDeleter& imageMemory) { + void createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage& image, VkDeviceMemory& imageMemory) { VkImageCreateInfo imageInfo = {}; imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; imageInfo.imageType = VK_IMAGE_TYPE_2D; @@ -891,12 +887,12 @@ class HelloTriangleApplication { imageInfo.arrayLayers = 1; imageInfo.format = format; imageInfo.tiling = tiling; - imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED; + imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; imageInfo.usage = usage; imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; - imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - if (vkCreateImage(device, &imageInfo, nullptr, image.replace()) != VK_SUCCESS) { + if (vkCreateImage(device, &imageInfo, nullptr, &image) != VK_SUCCESS) { throw std::runtime_error("failed to create image!"); } @@ -908,7 +904,7 @@ class HelloTriangleApplication { allocInfo.allocationSize = memRequirements.size; allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); - if (vkAllocateMemory(device, &allocInfo, nullptr, imageMemory.replace()) != VK_SUCCESS) { + if (vkAllocateMemory(device, &allocInfo, nullptr, &imageMemory) != VK_SUCCESS) { throw std::runtime_error("failed to allocate image memory!"); } @@ -941,25 +937,34 @@ class HelloTriangleApplication { barrier.subresourceRange.baseArrayLayer = 0; barrier.subresourceRange.layerCount = 1; - if (oldLayout == VK_IMAGE_LAYOUT_PREINITIALIZED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL) { - barrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT; - barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; - } else if (oldLayout == VK_IMAGE_LAYOUT_PREINITIALIZED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { - barrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT; + VkPipelineStageFlags sourceStage; + VkPipelineStageFlags destinationStage; + + if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { + barrier.srcAccessMask = 0; barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + + sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT; } else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + + sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; } else if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) { barrier.srcAccessMask = 0; barrier.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + + sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + destinationStage = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; } else { throw std::invalid_argument("unsupported layout transition!"); } vkCmdPipelineBarrier( commandBuffer, - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + sourceStage, destinationStage, 0, 0, nullptr, 0, nullptr, @@ -969,30 +974,25 @@ class HelloTriangleApplication { endSingleTimeCommands(commandBuffer); } - void copyImage(VkImage srcImage, VkImage dstImage, uint32_t width, uint32_t height) { + void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) { VkCommandBuffer commandBuffer = beginSingleTimeCommands(); - VkImageSubresourceLayers subResource = {}; - subResource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - subResource.baseArrayLayer = 0; - subResource.mipLevel = 0; - subResource.layerCount = 1; - - VkImageCopy region = {}; - region.srcSubresource = subResource; - region.dstSubresource = subResource; - region.srcOffset = {0, 0, 0}; - region.dstOffset = {0, 0, 0}; - region.extent.width = width; - region.extent.height = height; - region.extent.depth = 1; - - vkCmdCopyImage( - commandBuffer, - srcImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - dstImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - 1, ®ion - ); + VkBufferImageCopy region = {}; + region.bufferOffset = 0; + region.bufferRowLength = 0; + region.bufferImageHeight = 0; + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.mipLevel = 0; + region.imageSubresource.baseArrayLayer = 0; + region.imageSubresource.layerCount = 1; + region.imageOffset = {0, 0, 0}; + region.imageExtent = { + width, + height, + 1 + }; + + vkCmdCopyBufferToImage(commandBuffer, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); endSingleTimeCommands(commandBuffer); } @@ -1007,7 +1007,7 @@ class HelloTriangleApplication { throw std::runtime_error(err); } - std::unordered_map uniqueVertices = {}; + std::unordered_map uniqueVertices = {}; for (const auto& shape : shapes) { for (const auto& index : shape.mesh.indices) { @@ -1027,7 +1027,7 @@ class HelloTriangleApplication { vertex.color = {1.0f, 1.0f, 1.0f}; if (uniqueVertices.count(vertex) == 0) { - uniqueVertices[vertex] = vertices.size(); + uniqueVertices[vertex] = static_cast(vertices.size()); vertices.push_back(vertex); } @@ -1039,8 +1039,8 @@ class HelloTriangleApplication { void createVertexBuffer() { VkDeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); - VDeleter stagingBuffer{device, vkDestroyBuffer}; - VDeleter stagingBufferMemory{device, vkFreeMemory}; + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); void* data; @@ -1051,13 +1051,16 @@ class HelloTriangleApplication { createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, vertexBuffer, vertexBufferMemory); copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); } void createIndexBuffer() { VkDeviceSize bufferSize = sizeof(indices[0]) * indices.size(); - VDeleter stagingBuffer{device, vkDestroyBuffer}; - VDeleter stagingBufferMemory{device, vkFreeMemory}; + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); void* data; @@ -1068,84 +1071,94 @@ class HelloTriangleApplication { createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, indexBuffer, indexBufferMemory); copyBuffer(stagingBuffer, indexBuffer, bufferSize); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); } - void createUniformBuffer() { + void createUniformBuffers() { VkDeviceSize bufferSize = sizeof(UniformBufferObject); - createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformStagingBuffer, uniformStagingBufferMemory); - createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, uniformBuffer, uniformBufferMemory); + uniformBuffers.resize(swapChainImages.size()); + uniformBuffersMemory.resize(swapChainImages.size()); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + createBuffer(bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformBuffers[i], uniformBuffersMemory[i]); + } } void createDescriptorPool() { std::array poolSizes = {}; poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - poolSizes[0].descriptorCount = 1; + poolSizes[0].descriptorCount = static_cast(swapChainImages.size()); poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - poolSizes[1].descriptorCount = 1; + poolSizes[1].descriptorCount = static_cast(swapChainImages.size()); VkDescriptorPoolCreateInfo poolInfo = {}; poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; - poolInfo.poolSizeCount = poolSizes.size(); + poolInfo.poolSizeCount = static_cast(poolSizes.size()); poolInfo.pPoolSizes = poolSizes.data(); - poolInfo.maxSets = 1; + poolInfo.maxSets = static_cast(swapChainImages.size()); - if (vkCreateDescriptorPool(device, &poolInfo, nullptr, descriptorPool.replace()) != VK_SUCCESS) { + if (vkCreateDescriptorPool(device, &poolInfo, nullptr, &descriptorPool) != VK_SUCCESS) { throw std::runtime_error("failed to create descriptor pool!"); } } - void createDescriptorSet() { - VkDescriptorSetLayout layouts[] = {descriptorSetLayout}; + void createDescriptorSets() { + std::vector layouts(swapChainImages.size(), descriptorSetLayout); VkDescriptorSetAllocateInfo allocInfo = {}; allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; allocInfo.descriptorPool = descriptorPool; - allocInfo.descriptorSetCount = 1; - allocInfo.pSetLayouts = layouts; + allocInfo.descriptorSetCount = static_cast(swapChainImages.size()); + allocInfo.pSetLayouts = layouts.data(); - if (vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet) != VK_SUCCESS) { - throw std::runtime_error("failed to allocate descriptor set!"); + descriptorSets.resize(swapChainImages.size()); + if (vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets[0]) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate descriptor sets!"); } - VkDescriptorBufferInfo bufferInfo = {}; - bufferInfo.buffer = uniformBuffer; - bufferInfo.offset = 0; - bufferInfo.range = sizeof(UniformBufferObject); - - VkDescriptorImageInfo imageInfo = {}; - imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - imageInfo.imageView = textureImageView; - imageInfo.sampler = textureSampler; - - std::array descriptorWrites = {}; - - descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; - descriptorWrites[0].dstSet = descriptorSet; - descriptorWrites[0].dstBinding = 0; - descriptorWrites[0].dstArrayElement = 0; - descriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - descriptorWrites[0].descriptorCount = 1; - descriptorWrites[0].pBufferInfo = &bufferInfo; - - descriptorWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; - descriptorWrites[1].dstSet = descriptorSet; - descriptorWrites[1].dstBinding = 1; - descriptorWrites[1].dstArrayElement = 0; - descriptorWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - descriptorWrites[1].descriptorCount = 1; - descriptorWrites[1].pImageInfo = &imageInfo; - - vkUpdateDescriptorSets(device, descriptorWrites.size(), descriptorWrites.data(), 0, nullptr); + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkDescriptorBufferInfo bufferInfo = {}; + bufferInfo.buffer = uniformBuffers[i]; + bufferInfo.offset = 0; + bufferInfo.range = sizeof(UniformBufferObject); + + VkDescriptorImageInfo imageInfo = {}; + imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + imageInfo.imageView = textureImageView; + imageInfo.sampler = textureSampler; + + std::array descriptorWrites = {}; + + descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[0].dstSet = descriptorSets[i]; + descriptorWrites[0].dstBinding = 0; + descriptorWrites[0].dstArrayElement = 0; + descriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptorWrites[0].descriptorCount = 1; + descriptorWrites[0].pBufferInfo = &bufferInfo; + + descriptorWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[1].dstSet = descriptorSets[i]; + descriptorWrites[1].dstBinding = 1; + descriptorWrites[1].dstArrayElement = 0; + descriptorWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + descriptorWrites[1].descriptorCount = 1; + descriptorWrites[1].pImageInfo = &imageInfo; + + vkUpdateDescriptorSets(device, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); + } } - void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VDeleter& buffer, VDeleter& bufferMemory) { + void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) { VkBufferCreateInfo bufferInfo = {}; bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; bufferInfo.size = size; bufferInfo.usage = usage; - bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - if (vkCreateBuffer(device, &bufferInfo, nullptr, buffer.replace()) != VK_SUCCESS) { + if (vkCreateBuffer(device, &bufferInfo, nullptr, &buffer) != VK_SUCCESS) { throw std::runtime_error("failed to create buffer!"); } @@ -1157,7 +1170,7 @@ class HelloTriangleApplication { allocInfo.allocationSize = memRequirements.size; allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); - if (vkAllocateMemory(device, &allocInfo, nullptr, bufferMemory.replace()) != VK_SUCCESS) { + if (vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) { throw std::runtime_error("failed to allocate buffer memory!"); } @@ -1221,10 +1234,6 @@ class HelloTriangleApplication { } void createCommandBuffers() { - if (commandBuffers.size() > 0) { - vkFreeCommandBuffers(device, commandPool, commandBuffers.size(), commandBuffers.data()); - } - commandBuffers.resize(swapChainFramebuffers.size()); VkCommandBufferAllocateInfo allocInfo = {}; @@ -1242,7 +1251,9 @@ class HelloTriangleApplication { beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; beginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT; - vkBeginCommandBuffer(commandBuffers[i], &beginInfo); + if (vkBeginCommandBuffer(commandBuffers[i], &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } VkRenderPassBeginInfo renderPassInfo = {}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; @@ -1255,7 +1266,7 @@ class HelloTriangleApplication { clearValues[0].color = {0.0f, 0.0f, 0.0f, 1.0f}; clearValues[1].depthStencil = {1.0f, 0}; - renderPassInfo.clearValueCount = clearValues.size(); + renderPassInfo.clearValueCount = static_cast(clearValues.size()); renderPassInfo.pClearValues = clearValues.data(); vkCmdBeginRenderPass(commandBuffers[i], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); @@ -1268,9 +1279,9 @@ class HelloTriangleApplication { vkCmdBindIndexBuffer(commandBuffers[i], indexBuffer, 0, VK_INDEX_TYPE_UINT32); - vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr); + vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets[i], 0, nullptr); - vkCmdDrawIndexed(commandBuffers[i], indices.size(), 1, 0, 0, 0); + vkCmdDrawIndexed(commandBuffers[i], static_cast(indices.size()), 1, 0, 0, 0); vkCmdEndRenderPass(commandBuffers[i]); @@ -1280,40 +1291,50 @@ class HelloTriangleApplication { } } - void createSemaphores() { + void createSyncObjects() { + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + inFlightFences.resize(MAX_FRAMES_IN_FLIGHT); + VkSemaphoreCreateInfo semaphoreInfo = {}; semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; - if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, imageAvailableSemaphore.replace()) != VK_SUCCESS || - vkCreateSemaphore(device, &semaphoreInfo, nullptr, renderFinishedSemaphore.replace()) != VK_SUCCESS) { + VkFenceCreateInfo fenceInfo = {}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; - throw std::runtime_error("failed to create semaphores!"); + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS || + vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create synchronization objects for a frame!"); + } } } - void updateUniformBuffer() { + 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_cast(currentTime - startTime).count() / 1000.0f; + float time = std::chrono::duration(currentTime - startTime).count(); UniformBufferObject ubo = {}; - ubo.model = glm::rotate(glm::mat4(), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + 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; - vkMapMemory(device, uniformStagingBufferMemory, 0, sizeof(ubo), 0, &data); + vkMapMemory(device, uniformBuffersMemory[currentImage], 0, sizeof(ubo), 0, &data); memcpy(data, &ubo, sizeof(ubo)); - vkUnmapMemory(device, uniformStagingBufferMemory); - - copyBuffer(uniformStagingBuffer, uniformBuffer, sizeof(ubo)); + vkUnmapMemory(device, uniformBuffersMemory[currentImage]); } void drawFrame() { + vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, std::numeric_limits::max()); + uint32_t imageIndex; - VkResult result = vkAcquireNextImageKHR(device, swapChain, std::numeric_limits::max(), imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex); + VkResult result = vkAcquireNextImageKHR(device, swapChain, std::numeric_limits::max(), imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); if (result == VK_ERROR_OUT_OF_DATE_KHR) { recreateSwapChain(); @@ -1322,10 +1343,12 @@ class HelloTriangleApplication { throw std::runtime_error("failed to acquire swap chain image!"); } + updateUniformBuffer(imageIndex); + VkSubmitInfo submitInfo = {}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; - VkSemaphore waitSemaphores[] = {imageAvailableSemaphore}; + VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[currentFrame]}; VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; submitInfo.waitSemaphoreCount = 1; submitInfo.pWaitSemaphores = waitSemaphores; @@ -1334,11 +1357,13 @@ class HelloTriangleApplication { submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &commandBuffers[imageIndex]; - VkSemaphore signalSemaphores[] = {renderFinishedSemaphore}; + VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[currentFrame]}; submitInfo.signalSemaphoreCount = 1; submitInfo.pSignalSemaphores = signalSemaphores; - if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) { throw std::runtime_error("failed to submit draw command buffer!"); } @@ -1356,26 +1381,28 @@ class HelloTriangleApplication { result = vkQueuePresentKHR(presentQueue, &presentInfo); - if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { + framebufferResized = false; recreateSwapChain(); } else if (result != VK_SUCCESS) { throw std::runtime_error("failed to present swap chain image!"); } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; } - void createShaderModule(const std::vector& code, VDeleter& shaderModule) { + VkShaderModule createShaderModule(const std::vector& code) { VkShaderModuleCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); - std::vector codeAligned(code.size() / 4 + 1); - memcpy(codeAligned.data(), code.data(), code.size()); - - createInfo.pCode = codeAligned.data(); - - if (vkCreateShaderModule(device, &createInfo, nullptr, shaderModule.replace()) != VK_SUCCESS) { + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { throw std::runtime_error("failed to create shader module!"); } + + return shaderModule; } VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { @@ -1411,9 +1438,12 @@ class HelloTriangleApplication { return capabilities.currentExtent; } else { int width, height; - glfwGetWindowSize(window, &width, &height); + glfwGetFramebufferSize(window, &width, &height); - VkExtent2D actualExtent = {width, height}; + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; actualExtent.width = std::max(capabilities.minImageExtent.width, std::min(capabilities.maxImageExtent.width, actualExtent.width)); actualExtent.height = std::max(capabilities.minImageExtent.height, std::min(capabilities.maxImageExtent.height, actualExtent.height)); @@ -1457,7 +1487,10 @@ class HelloTriangleApplication { swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); } - return indices.isComplete() && extensionsSupported && swapChainAdequate; + VkPhysicalDeviceFeatures supportedFeatures; + vkGetPhysicalDeviceFeatures(device, &supportedFeatures); + + return indices.isComplete() && extensionsSupported && swapChainAdequate && supportedFeatures.samplerAnisotropy; } bool checkDeviceExtensionSupport(VkPhysicalDevice device) { @@ -1509,15 +1542,11 @@ class HelloTriangleApplication { } std::vector getRequiredExtensions() { - std::vector extensions; - - unsigned int glfwExtensionCount = 0; + uint32_t glfwExtensionCount = 0; const char** glfwExtensions; glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - for (unsigned int i = 0; i < glfwExtensionCount; i++) { - extensions.push_back(glfwExtensions[i]); - } + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); if (enableValidationLayers) { extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); @@ -1581,7 +1610,7 @@ int main() { try { app.run(); - } catch (const std::runtime_error& e) { + } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; } diff --git a/code/28_mipmapping.cpp b/code/28_mipmapping.cpp new file mode 100644 index 00000000..94e602c4 --- /dev/null +++ b/code/28_mipmapping.cpp @@ -0,0 +1,1713 @@ +#define GLFW_INCLUDE_VULKAN +#include + +#define GLM_FORCE_RADIANS +#define GLM_FORCE_DEPTH_ZERO_TO_ONE +#define GLM_ENABLE_EXPERIMENTAL +#include +#include +#include + +#define STB_IMAGE_IMPLEMENTATION +#include + +#define TINYOBJLOADER_IMPLEMENTATION +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const int WIDTH = 800; +const int HEIGHT = 600; + +const std::string MODEL_PATH = "models/chalet.obj"; +const std::string TEXTURE_PATH = "textures/chalet.jpg"; + +const int MAX_FRAMES_IN_FLIGHT = 2; + +const std::vector validationLayers = { + "VK_LAYER_LUNARG_standard_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugReportCallbackEXT(VkInstance instance, const VkDebugReportCallbackCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugReportCallbackEXT* pCallback) { + auto func = (PFN_vkCreateDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pCallback); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT callback, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT"); + if (func != nullptr) { + func(instance, callback, pAllocator); + } +} + +struct QueueFamilyIndices { + int graphicsFamily = -1; + int presentFamily = -1; + + bool isComplete() { + return graphicsFamily >= 0 && presentFamily >= 0; + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; + +struct Vertex { + glm::vec3 pos; + glm::vec3 color; + glm::vec2 texCoord; + + static VkVertexInputBindingDescription getBindingDescription() { + VkVertexInputBindingDescription bindingDescription = {}; + bindingDescription.binding = 0; + bindingDescription.stride = sizeof(Vertex); + bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + + return bindingDescription; + } + + static std::array getAttributeDescriptions() { + std::array attributeDescriptions = {}; + + attributeDescriptions[0].binding = 0; + attributeDescriptions[0].location = 0; + attributeDescriptions[0].format = VK_FORMAT_R32G32B32_SFLOAT; + attributeDescriptions[0].offset = offsetof(Vertex, pos); + + attributeDescriptions[1].binding = 0; + attributeDescriptions[1].location = 1; + attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; + attributeDescriptions[1].offset = offsetof(Vertex, color); + + attributeDescriptions[2].binding = 0; + attributeDescriptions[2].location = 2; + attributeDescriptions[2].format = VK_FORMAT_R32G32_SFLOAT; + attributeDescriptions[2].offset = offsetof(Vertex, texCoord); + + return attributeDescriptions; + } + + bool operator==(const Vertex& other) const { + return pos == other.pos && color == other.color && texCoord == other.texCoord; + } +}; + +namespace std { + template<> struct hash { + size_t operator()(Vertex const& vertex) const { + return ((hash()(vertex.pos) ^ (hash()(vertex.color) << 1)) >> 1) ^ (hash()(vertex.texCoord) << 1); + } + }; +} + +struct UniformBufferObject { + glm::mat4 model; + glm::mat4 view; + glm::mat4 proj; +}; + +class HelloTriangleApplication { +public: + void run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugReportCallbackEXT callback; + VkSurfaceKHR surface; + + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device; + + VkQueue graphicsQueue; + VkQueue presentQueue; + + VkSwapchainKHR swapChain; + std::vector swapChainImages; + VkFormat swapChainImageFormat; + VkExtent2D swapChainExtent; + std::vector swapChainImageViews; + std::vector swapChainFramebuffers; + + VkRenderPass renderPass; + VkDescriptorSetLayout descriptorSetLayout; + VkPipelineLayout pipelineLayout; + VkPipeline graphicsPipeline; + + VkCommandPool commandPool; + + VkImage depthImage; + VkDeviceMemory depthImageMemory; + VkImageView depthImageView; + + uint32_t mipLevels; + VkImage textureImage; + VkDeviceMemory textureImageMemory; + VkImageView textureImageView; + VkSampler textureSampler; + + std::vector vertices; + std::vector indices; + VkBuffer vertexBuffer; + VkDeviceMemory vertexBufferMemory; + VkBuffer indexBuffer; + VkDeviceMemory indexBufferMemory; + + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + + VkDescriptorPool descriptorPool; + std::vector descriptorSets; + + std::vector commandBuffers; + + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + std::vector inFlightFences; + size_t currentFrame = 0; + + bool framebufferResized = false; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() { + createInstance(); + setupDebugCallback(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createDescriptorSetLayout(); + createGraphicsPipeline(); + createCommandPool(); + createDepthResources(); + createFramebuffers(); + createTextureImage(); + createTextureImageView(); + createTextureSampler(); + loadModel(); + createVertexBuffer(); + createIndexBuffer(); + createUniformBuffers(); + createDescriptorPool(); + createDescriptorSets(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + drawFrame(); + } + + vkDeviceWaitIdle(device); + } + + void cleanupSwapChain() { + vkDestroyImageView(device, depthImageView, nullptr); + vkDestroyImage(device, depthImage, nullptr); + vkFreeMemory(device, depthImageMemory, nullptr); + + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } + + vkFreeCommandBuffers(device, commandPool, static_cast(commandBuffers.size()), commandBuffers.data()); + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + } + + void cleanup() { + cleanupSwapChain(); + + vkDestroySampler(device, textureSampler, nullptr); + vkDestroyImageView(device, textureImageView, nullptr); + + vkDestroyImage(device, textureImage, nullptr); + vkFreeMemory(device, textureImageMemory, nullptr); + + vkDestroyDescriptorPool(device, descriptorPool, nullptr); + + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + vkDestroyBuffer(device, uniformBuffers[i], nullptr); + vkFreeMemory(device, uniformBuffersMemory[i], nullptr); + } + + vkDestroyBuffer(device, indexBuffer, nullptr); + vkFreeMemory(device, indexBufferMemory, nullptr); + + vkDestroyBuffer(device, vertexBuffer, nullptr); + vkFreeMemory(device, vertexBufferMemory, nullptr); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + vkDestroyFence(device, inFlightFences[i], nullptr); + } + + vkDestroyCommandPool(device, commandPool, nullptr); + + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugReportCallbackEXT(instance, callback, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() { + int width = 0, height = 0; + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + vkDeviceWaitIdle(device); + + cleanupSwapChain(); + + createSwapChain(); + createImageViews(); + createRenderPass(); + createGraphicsPipeline(); + createDepthResources(); + createFramebuffers(); + createCommandBuffers(); + } + + void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + VkApplicationInfo appInfo = {}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_0; + + VkInstanceCreateInfo createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + } + + void setupDebugCallback() { + if (!enableValidationLayers) return; + + VkDebugReportCallbackCreateInfoEXT createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT; + createInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT; + createInfo.pfnCallback = debugCallback; + + if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, &callback) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug callback!"); + } + } + + void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); + } + } + + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + break; + } + } + + if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = {indices.graphicsFamily, indices.presentFamily}; + + float queuePriority = 1.0f; + for (int queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo = {}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceFeatures deviceFeatures = {}; + deviceFeatures.samplerAnisotropy = VK_TRUE; + + VkDeviceCreateInfo createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pEnabledFeatures = &deviceFeatures; + + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + vkGetDeviceQueue(device, indices.graphicsFamily, 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily, 0, &presentQueue); + } + + void createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + uint32_t queueFamilyIndices[] = {(uint32_t) indices.graphicsFamily, (uint32_t) indices.presentFamily}; + + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw std::runtime_error("failed to create swap chain!"); + } + + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + swapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + + for (uint32_t i = 0; i < swapChainImages.size(); i++) { + swapChainImageViews[i] = createImageView(swapChainImages[i], swapChainImageFormat, VK_IMAGE_ASPECT_COLOR_BIT, 1); + } + } + + void createRenderPass() { + VkAttachmentDescription colorAttachment = {}; + colorAttachment.format = swapChainImageFormat; + colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + VkAttachmentDescription depthAttachment = {}; + depthAttachment.format = findDepthFormat(); + depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + VkAttachmentReference colorAttachmentRef = {}; + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkAttachmentReference depthAttachmentRef = {}; + depthAttachmentRef.attachment = 1; + depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpass = {}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; + subpass.pDepthStencilAttachment = &depthAttachmentRef; + + VkSubpassDependency dependency = {}; + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.srcAccessMask = 0; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + + std::array attachments = {colorAttachment, depthAttachment}; + VkRenderPassCreateInfo renderPassInfo = {}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassInfo.attachmentCount = static_cast(attachments.size()); + renderPassInfo.pAttachments = attachments.data(); + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + renderPassInfo.dependencyCount = 1; + renderPassInfo.pDependencies = &dependency; + + if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { + throw std::runtime_error("failed to create render pass!"); + } + } + + void createDescriptorSetLayout() { + VkDescriptorSetLayoutBinding uboLayoutBinding = {}; + uboLayoutBinding.binding = 0; + uboLayoutBinding.descriptorCount = 1; + uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + uboLayoutBinding.pImmutableSamplers = nullptr; + uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + + VkDescriptorSetLayoutBinding samplerLayoutBinding = {}; + samplerLayoutBinding.binding = 1; + samplerLayoutBinding.descriptorCount = 1; + samplerLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + samplerLayoutBinding.pImmutableSamplers = nullptr; + samplerLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + + std::array bindings = {uboLayoutBinding, samplerLayoutBinding}; + VkDescriptorSetLayoutCreateInfo layoutInfo = {}; + layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + layoutInfo.bindingCount = static_cast(bindings.size()); + layoutInfo.pBindings = bindings.data(); + + if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &descriptorSetLayout) != VK_SUCCESS) { + throw std::runtime_error("failed to create descriptor set layout!"); + } + } + + void createGraphicsPipeline() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); + + VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); + + VkPipelineShaderStageCreateInfo vertShaderStageInfo = {}; + vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; + vertShaderStageInfo.module = vertShaderModule; + vertShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo fragShaderStageInfo = {}; + fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + fragShaderStageInfo.module = fragShaderModule; + fragShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; + + VkPipelineVertexInputStateCreateInfo vertexInputInfo = {}; + vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + + vertexInputInfo.vertexBindingDescriptionCount = 1; + vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()); + vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; + vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); + + VkPipelineInputAssemblyStateCreateInfo inputAssembly = {}; + inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + inputAssembly.primitiveRestartEnable = VK_FALSE; + + VkViewport viewport = {}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float) swapChainExtent.width; + viewport.height = (float) swapChainExtent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + + VkRect2D scissor = {}; + scissor.offset = {0, 0}; + scissor.extent = swapChainExtent; + + VkPipelineViewportStateCreateInfo viewportState = {}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.pViewports = &viewport; + viewportState.scissorCount = 1; + viewportState.pScissors = &scissor; + + VkPipelineRasterizationStateCreateInfo rasterizer = {}; + rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.lineWidth = 1.0f; + rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; + rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + + VkPipelineMultisampleStateCreateInfo multisampling = {}; + multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + VkPipelineDepthStencilStateCreateInfo depthStencil = {}; + depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; + depthStencil.depthTestEnable = VK_TRUE; + depthStencil.depthWriteEnable = VK_TRUE; + depthStencil.depthCompareOp = VK_COMPARE_OP_LESS; + depthStencil.depthBoundsTestEnable = VK_FALSE; + depthStencil.stencilTestEnable = VK_FALSE; + + VkPipelineColorBlendAttachmentState colorBlendAttachment = {}; + colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + colorBlendAttachment.blendEnable = VK_FALSE; + + VkPipelineColorBlendStateCreateInfo colorBlending = {}; + colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlending.logicOpEnable = VK_FALSE; + colorBlending.logicOp = VK_LOGIC_OP_COPY; + colorBlending.attachmentCount = 1; + colorBlending.pAttachments = &colorBlendAttachment; + colorBlending.blendConstants[0] = 0.0f; + colorBlending.blendConstants[1] = 0.0f; + colorBlending.blendConstants[2] = 0.0f; + colorBlending.blendConstants[3] = 0.0f; + + VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.setLayoutCount = 1; + pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout; + + if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { + throw std::runtime_error("failed to create pipeline layout!"); + } + + VkGraphicsPipelineCreateInfo pipelineInfo = {}; + pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipelineInfo.stageCount = 2; + pipelineInfo.pStages = shaderStages; + pipelineInfo.pVertexInputState = &vertexInputInfo; + pipelineInfo.pInputAssemblyState = &inputAssembly; + pipelineInfo.pViewportState = &viewportState; + pipelineInfo.pRasterizationState = &rasterizer; + pipelineInfo.pMultisampleState = &multisampling; + pipelineInfo.pDepthStencilState = &depthStencil; + pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.layout = pipelineLayout; + pipelineInfo.renderPass = renderPass; + pipelineInfo.subpass = 0; + pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; + + if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { + throw std::runtime_error("failed to create graphics pipeline!"); + } + + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); + } + + void createFramebuffers() { + swapChainFramebuffers.resize(swapChainImageViews.size()); + + for (size_t i = 0; i < swapChainImageViews.size(); i++) { + std::array attachments = { + swapChainImageViews[i], + depthImageView + }; + + VkFramebufferCreateInfo framebufferInfo = {}; + framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebufferInfo.renderPass = renderPass; + framebufferInfo.attachmentCount = static_cast(attachments.size()); + framebufferInfo.pAttachments = attachments.data(); + framebufferInfo.width = swapChainExtent.width; + framebufferInfo.height = swapChainExtent.height; + framebufferInfo.layers = 1; + + if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create framebuffer!"); + } + } + } + + void createCommandPool() { + QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); + + VkCommandPoolCreateInfo poolInfo = {}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily; + + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create graphics command pool!"); + } + } + + void createDepthResources() { + VkFormat depthFormat = findDepthFormat(); + + createImage(swapChainExtent.width, swapChainExtent.height, 1, depthFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, depthImage, depthImageMemory); + depthImageView = createImageView(depthImage, depthFormat, VK_IMAGE_ASPECT_DEPTH_BIT, 1); + + transitionImageLayout(depthImage, depthFormat, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, 1); + } + + VkFormat findSupportedFormat(const std::vector& candidates, VkImageTiling tiling, VkFormatFeatureFlags features) { + for (VkFormat format : candidates) { + VkFormatProperties props; + vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &props); + + if (tiling == VK_IMAGE_TILING_LINEAR && (props.linearTilingFeatures & features) == features) { + return format; + } else if (tiling == VK_IMAGE_TILING_OPTIMAL && (props.optimalTilingFeatures & features) == features) { + return format; + } + } + + throw std::runtime_error("failed to find supported format!"); + } + + VkFormat findDepthFormat() { + return findSupportedFormat( + {VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT}, + VK_IMAGE_TILING_OPTIMAL, + VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT + ); + } + + bool hasStencilComponent(VkFormat format) { + return format == VK_FORMAT_D32_SFLOAT_S8_UINT || format == VK_FORMAT_D24_UNORM_S8_UINT; + } + + void createTextureImage() { + int texWidth, texHeight, texChannels; + stbi_uc* pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + VkDeviceSize 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!"); + } + + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); + + void* data; + vkMapMemory(device, stagingBufferMemory, 0, imageSize, 0, &data); + memcpy(data, pixels, static_cast(imageSize)); + vkUnmapMemory(device, stagingBufferMemory); + + stbi_image_free(pixels); + + createImage(texWidth, texHeight, mipLevels, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, textureImage, textureImageMemory); + + transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, mipLevels); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); + //transitioned to VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL while generating mipmaps + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); + + generateMipmaps(textureImage, VK_FORMAT_R8G8B8A8_UNORM, texWidth, texHeight, mipLevels); + } + + void generateMipmaps(VkImage image, VkFormat imageFormat, int32_t texWidth, int32_t texHeight, uint32_t mipLevels) { + // Check if image format supports linear blitting + VkFormatProperties formatProperties; + vkGetPhysicalDeviceFormatProperties(physicalDevice, imageFormat, &formatProperties); + + if (!(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT)) { + throw std::runtime_error("texture image format does not support linear blitting!"); + } + + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkImageMemoryBarrier barrier = {}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.image = image; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + 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_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + + vkCmdPipelineBarrier(commandBuffer, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, + 0, nullptr, + 0, nullptr, + 1, &barrier); + + VkImageBlit blit = {}; + blit.srcOffsets[0] = {0, 0, 0}; + blit.srcOffsets[1] = {mipWidth, mipHeight, 1}; + blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + blit.srcSubresource.mipLevel = i - 1; + blit.srcSubresource.baseArrayLayer = 0; + blit.srcSubresource.layerCount = 1; + blit.dstOffsets[0] = {0, 0, 0}; + blit.dstOffsets[1] = { mipWidth > 1 ? mipWidth / 2 : 1, mipHeight > 1 ? mipHeight / 2 : 1, 1 }; + blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + blit.dstSubresource.mipLevel = i; + blit.dstSubresource.baseArrayLayer = 0; + blit.dstSubresource.layerCount = 1; + + vkCmdBlitImage(commandBuffer, + image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, &blit, + VK_FILTER_LINEAR); + + barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + + vkCmdPipelineBarrier(commandBuffer, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, + 0, nullptr, + 0, nullptr, + 1, &barrier); + + if (mipWidth > 1) mipWidth /= 2; + if (mipHeight > 1) mipHeight /= 2; + } + + barrier.subresourceRange.baseMipLevel = mipLevels - 1; + barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + + vkCmdPipelineBarrier(commandBuffer, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, + 0, nullptr, + 0, nullptr, + 1, &barrier); + + endSingleTimeCommands(commandBuffer); + } + + void createTextureImageView() { + textureImageView = createImageView(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT, mipLevels); + } + + void createTextureSampler() { + VkSamplerCreateInfo samplerInfo = {}; + samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + samplerInfo.magFilter = VK_FILTER_LINEAR; + samplerInfo.minFilter = VK_FILTER_LINEAR; + samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.anisotropyEnable = VK_TRUE; + samplerInfo.maxAnisotropy = 16; + samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; + samplerInfo.unnormalizedCoordinates = VK_FALSE; + samplerInfo.compareEnable = VK_FALSE; + samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS; + samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + samplerInfo.minLod = 0; + samplerInfo.maxLod = static_cast(mipLevels); + samplerInfo.mipLodBias = 0; + + if (vkCreateSampler(device, &samplerInfo, nullptr, &textureSampler) != VK_SUCCESS) { + throw std::runtime_error("failed to create texture sampler!"); + } + } + + VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags, uint32_t mipLevels) { + VkImageViewCreateInfo viewInfo = {}; + viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewInfo.image = image; + viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewInfo.format = format; + viewInfo.subresourceRange.aspectMask = aspectFlags; + viewInfo.subresourceRange.baseMipLevel = 0; + viewInfo.subresourceRange.levelCount = mipLevels; + viewInfo.subresourceRange.baseArrayLayer = 0; + viewInfo.subresourceRange.layerCount = 1; + + VkImageView imageView; + if (vkCreateImageView(device, &viewInfo, nullptr, &imageView) != VK_SUCCESS) { + throw std::runtime_error("failed to create texture image view!"); + } + + return imageView; + } + + void createImage(uint32_t width, uint32_t height, uint32_t mipLevels, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage& image, VkDeviceMemory& imageMemory) { + VkImageCreateInfo imageInfo = {}; + imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.extent.width = width; + imageInfo.extent.height = height; + imageInfo.extent.depth = 1; + imageInfo.mipLevels = mipLevels; + imageInfo.arrayLayers = 1; + imageInfo.format = format; + imageInfo.tiling = tiling; + imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imageInfo.usage = usage; + imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + if (vkCreateImage(device, &imageInfo, nullptr, &image) != VK_SUCCESS) { + throw std::runtime_error("failed to create image!"); + } + + VkMemoryRequirements memRequirements; + vkGetImageMemoryRequirements(device, image, &memRequirements); + + VkMemoryAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = memRequirements.size; + allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); + + if (vkAllocateMemory(device, &allocInfo, nullptr, &imageMemory) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate image memory!"); + } + + vkBindImageMemory(device, image, imageMemory, 0); + } + + void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout, uint32_t mipLevels) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkImageMemoryBarrier barrier = {}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.oldLayout = oldLayout; + barrier.newLayout = newLayout; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image; + + if (newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) { + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + + if (hasStencilComponent(format)) { + barrier.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT; + } + } else { + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + } + + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = mipLevels; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + + VkPipelineStageFlags sourceStage; + VkPipelineStageFlags destinationStage; + + if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + + sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + } else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + + sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + } else if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) { + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + + sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + destinationStage = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + } else { + throw std::invalid_argument("unsupported layout transition!"); + } + + vkCmdPipelineBarrier( + commandBuffer, + sourceStage, destinationStage, + 0, + 0, nullptr, + 0, nullptr, + 1, &barrier + ); + + endSingleTimeCommands(commandBuffer); + } + + void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkBufferImageCopy region = {}; + region.bufferOffset = 0; + region.bufferRowLength = 0; + region.bufferImageHeight = 0; + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.mipLevel = 0; + region.imageSubresource.baseArrayLayer = 0; + region.imageSubresource.layerCount = 1; + region.imageOffset = {0, 0, 0}; + region.imageExtent = { + width, + height, + 1 + }; + + vkCmdCopyBufferToImage(commandBuffer, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); + + endSingleTimeCommands(commandBuffer); + } + + void loadModel() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string err; + + if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &err, MODEL_PATH.c_str())) { + throw std::runtime_error(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 createVertexBuffer() { + VkDeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); + + void* data; + vkMapMemory(device, stagingBufferMemory, 0, bufferSize, 0, &data); + memcpy(data, vertices.data(), (size_t) bufferSize); + vkUnmapMemory(device, stagingBufferMemory); + + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, vertexBuffer, vertexBufferMemory); + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); + } + + void createIndexBuffer() { + VkDeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); + + void* data; + vkMapMemory(device, stagingBufferMemory, 0, bufferSize, 0, &data); + memcpy(data, indices.data(), (size_t) bufferSize); + vkUnmapMemory(device, stagingBufferMemory); + + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, indexBuffer, indexBufferMemory); + + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); + } + + void createUniformBuffers() { + VkDeviceSize bufferSize = sizeof(UniformBufferObject); + + uniformBuffers.resize(swapChainImages.size()); + uniformBuffersMemory.resize(swapChainImages.size()); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + createBuffer(bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformBuffers[i], uniformBuffersMemory[i]); + } + } + + void createDescriptorPool() { + std::array poolSizes = {}; + poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + poolSizes[0].descriptorCount = static_cast(swapChainImages.size()); + poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + poolSizes[1].descriptorCount = static_cast(swapChainImages.size()); + + VkDescriptorPoolCreateInfo poolInfo = {}; + poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + poolInfo.poolSizeCount = static_cast(poolSizes.size()); + poolInfo.pPoolSizes = poolSizes.data(); + poolInfo.maxSets = static_cast(swapChainImages.size()); + + if (vkCreateDescriptorPool(device, &poolInfo, nullptr, &descriptorPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create descriptor pool!"); + } + } + + void createDescriptorSets() { + std::vector layouts(swapChainImages.size(), descriptorSetLayout); + VkDescriptorSetAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = descriptorPool; + allocInfo.descriptorSetCount = static_cast(swapChainImages.size()); + allocInfo.pSetLayouts = layouts.data(); + + descriptorSets.resize(swapChainImages.size()); + if (vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets[0]) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate descriptor sets!"); + } + + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkDescriptorBufferInfo bufferInfo = {}; + bufferInfo.buffer = uniformBuffers[i]; + bufferInfo.offset = 0; + bufferInfo.range = sizeof(UniformBufferObject); + + VkDescriptorImageInfo imageInfo = {}; + imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + imageInfo.imageView = textureImageView; + imageInfo.sampler = textureSampler; + + std::array descriptorWrites = {}; + + descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[0].dstSet = descriptorSets[i]; + descriptorWrites[0].dstBinding = 0; + descriptorWrites[0].dstArrayElement = 0; + descriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptorWrites[0].descriptorCount = 1; + descriptorWrites[0].pBufferInfo = &bufferInfo; + + descriptorWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[1].dstSet = descriptorSets[i]; + descriptorWrites[1].dstBinding = 1; + descriptorWrites[1].dstArrayElement = 0; + descriptorWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + descriptorWrites[1].descriptorCount = 1; + descriptorWrites[1].pImageInfo = &imageInfo; + + vkUpdateDescriptorSets(device, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); + } + } + + void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) { + VkBufferCreateInfo bufferInfo = {}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = size; + bufferInfo.usage = usage; + bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + if (vkCreateBuffer(device, &bufferInfo, nullptr, &buffer) != VK_SUCCESS) { + throw std::runtime_error("failed to create buffer!"); + } + + VkMemoryRequirements memRequirements; + vkGetBufferMemoryRequirements(device, buffer, &memRequirements); + + VkMemoryAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = memRequirements.size; + allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); + + if (vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate buffer memory!"); + } + + vkBindBufferMemory(device, buffer, bufferMemory, 0); + } + + VkCommandBuffer beginSingleTimeCommands() { + VkCommandBufferAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandPool = commandPool; + allocInfo.commandBufferCount = 1; + + VkCommandBuffer commandBuffer; + vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer); + + VkCommandBufferBeginInfo beginInfo = {}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + vkBeginCommandBuffer(commandBuffer, &beginInfo); + + return commandBuffer; + } + + void endSingleTimeCommands(VkCommandBuffer commandBuffer) { + vkEndCommandBuffer(commandBuffer); + + VkSubmitInfo submitInfo = {}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE); + vkQueueWaitIdle(graphicsQueue); + + vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); + } + + void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkBufferCopy copyRegion = {}; + copyRegion.size = size; + vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); + + endSingleTimeCommands(commandBuffer); + } + + uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) { + VkPhysicalDeviceMemoryProperties memProperties; + vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties); + + 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.resize(swapChainFramebuffers.size()); + + VkCommandBufferAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = (uint32_t) commandBuffers.size(); + + if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate command buffers!"); + } + + for (size_t i = 0; i < commandBuffers.size(); i++) { + VkCommandBufferBeginInfo beginInfo = {}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT; + + if (vkBeginCommandBuffer(commandBuffers[i], &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } + + VkRenderPassBeginInfo renderPassInfo = {}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = renderPass; + renderPassInfo.framebuffer = swapChainFramebuffers[i]; + renderPassInfo.renderArea.offset = {0, 0}; + renderPassInfo.renderArea.extent = swapChainExtent; + + std::array clearValues = {}; + clearValues[0].color = {0.0f, 0.0f, 0.0f, 1.0f}; + clearValues[1].depthStencil = {1.0f, 0}; + + renderPassInfo.clearValueCount = static_cast(clearValues.size()); + renderPassInfo.pClearValues = clearValues.data(); + + vkCmdBeginRenderPass(commandBuffers[i], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + + vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); + + VkBuffer vertexBuffers[] = {vertexBuffer}; + VkDeviceSize offsets[] = {0}; + vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets); + + vkCmdBindIndexBuffer(commandBuffers[i], indexBuffer, 0, VK_INDEX_TYPE_UINT32); + + vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets[i], 0, nullptr); + + vkCmdDrawIndexed(commandBuffers[i], static_cast(indices.size()), 1, 0, 0, 0); + + vkCmdEndRenderPass(commandBuffers[i]); + + if (vkEndCommandBuffer(commandBuffers[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); + } + } + } + + void createSyncObjects() { + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + inFlightFences.resize(MAX_FRAMES_IN_FLIGHT); + + VkSemaphoreCreateInfo semaphoreInfo = {}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + VkFenceCreateInfo fenceInfo = {}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS || + vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create synchronization objects for a frame!"); + } + } + } + + 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; + vkMapMemory(device, uniformBuffersMemory[currentImage], 0, sizeof(ubo), 0, &data); + memcpy(data, &ubo, sizeof(ubo)); + vkUnmapMemory(device, uniformBuffersMemory[currentImage]); + } + + void drawFrame() { + vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, std::numeric_limits::max()); + + uint32_t imageIndex; + VkResult result = vkAcquireNextImageKHR(device, swapChain, std::numeric_limits::max(), imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + + if (result == VK_ERROR_OUT_OF_DATE_KHR) { + recreateSwapChain(); + return; + } else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { + throw std::runtime_error("failed to acquire swap chain image!"); + } + + updateUniformBuffer(imageIndex); + + VkSubmitInfo submitInfo = {}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + + VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[currentFrame]}; + VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; + submitInfo.waitSemaphoreCount = 1; + submitInfo.pWaitSemaphores = waitSemaphores; + submitInfo.pWaitDstStageMask = waitStages; + + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffers[imageIndex]; + + VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[currentFrame]}; + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = signalSemaphores; + + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) { + throw std::runtime_error("failed to submit draw command buffer!"); + } + + VkPresentInfoKHR presentInfo = {}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = signalSemaphores; + + VkSwapchainKHR swapChains[] = {swapChain}; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapChains; + + presentInfo.pImageIndices = &imageIndex; + + result = vkQueuePresentKHR(presentQueue, &presentInfo); + + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { + framebufferResized = false; + recreateSwapChain(); + } else if (result != VK_SUCCESS) { + throw std::runtime_error("failed to present swap chain image!"); + } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + VkShaderModule createShaderModule(const std::vector& code) { + VkShaderModuleCreateInfo createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); + + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { + throw std::runtime_error("failed to create shader module!"); + } + + return shaderModule; + } + + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + if (availableFormats.size() == 1 && availableFormats[0].format == VK_FORMAT_UNDEFINED) { + return{VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}; + } + + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_UNORM && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR chooseSwapPresentMode(const std::vector availablePresentModes) { + VkPresentModeKHR bestMode = VK_PRESENT_MODE_FIFO_KHR; + + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return availablePresentMode; + } else if (availablePresentMode == VK_PRESENT_MODE_IMMEDIATE_KHR) { + bestMode = availablePresentMode; + } + } + + return bestMode; + } + + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::max(capabilities.minImageExtent.width, std::min(capabilities.maxImageExtent.width, actualExtent.width)); + actualExtent.height = std::max(capabilities.minImageExtent.height, std::min(capabilities.maxImageExtent.height, actualExtent.height)); + + return actualExtent; + } + } + + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + } + + return details; + } + + bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + bool swapChainAdequate = false; + if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); + } + + VkPhysicalDeviceFeatures supportedFeatures; + vkGetPhysicalDeviceFeatures(device, &supportedFeatures); + + return indices.isComplete() && extensionsSupported && swapChainAdequate && supportedFeatures.samplerAnisotropy; + } + + bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (queueFamily.queueCount > 0 && presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); + } + + return extensions; + } + + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + 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!"); + } + + size_t fileSize = (size_t) file.tellg(); + std::vector buffer(fileSize); + + file.seekg(0); + file.read(buffer.data(), fileSize); + + file.close(); + + return buffer; + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objType, uint64_t obj, size_t location, int32_t code, const char* layerPrefix, const char* msg, void* userData) { + std::cerr << "validation layer: " << msg << std::endl; + + return VK_FALSE; + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/code/29_multisampling.cpp b/code/29_multisampling.cpp new file mode 100644 index 00000000..2b941640 --- /dev/null +++ b/code/29_multisampling.cpp @@ -0,0 +1,1785 @@ +#define GLFW_INCLUDE_VULKAN +#include + +#define GLM_FORCE_RADIANS +#define GLM_FORCE_DEPTH_ZERO_TO_ONE +#define GLM_ENABLE_EXPERIMENTAL +#include +#include +#include + +#define STB_IMAGE_IMPLEMENTATION +#include + +#define TINYOBJLOADER_IMPLEMENTATION +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const int WIDTH = 800; +const int HEIGHT = 600; + +const std::string MODEL_PATH = "models/chalet.obj"; +const std::string TEXTURE_PATH = "textures/chalet.jpg"; + +const int MAX_FRAMES_IN_FLIGHT = 2; + +const std::vector validationLayers = { + "VK_LAYER_LUNARG_standard_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugReportCallbackEXT(VkInstance instance, const VkDebugReportCallbackCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugReportCallbackEXT* pCallback) { + auto func = (PFN_vkCreateDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pCallback); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT callback, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT"); + if (func != nullptr) { + func(instance, callback, pAllocator); + } +} + +struct QueueFamilyIndices { + int graphicsFamily = -1; + int presentFamily = -1; + + bool isComplete() { + return graphicsFamily >= 0 && presentFamily >= 0; + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; + +struct Vertex { + glm::vec3 pos; + glm::vec3 color; + glm::vec2 texCoord; + + static VkVertexInputBindingDescription getBindingDescription() { + VkVertexInputBindingDescription bindingDescription = {}; + bindingDescription.binding = 0; + bindingDescription.stride = sizeof(Vertex); + bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + + return bindingDescription; + } + + static std::array getAttributeDescriptions() { + std::array attributeDescriptions = {}; + + attributeDescriptions[0].binding = 0; + attributeDescriptions[0].location = 0; + attributeDescriptions[0].format = VK_FORMAT_R32G32B32_SFLOAT; + attributeDescriptions[0].offset = offsetof(Vertex, pos); + + attributeDescriptions[1].binding = 0; + attributeDescriptions[1].location = 1; + attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; + attributeDescriptions[1].offset = offsetof(Vertex, color); + + attributeDescriptions[2].binding = 0; + attributeDescriptions[2].location = 2; + attributeDescriptions[2].format = VK_FORMAT_R32G32_SFLOAT; + attributeDescriptions[2].offset = offsetof(Vertex, texCoord); + + return attributeDescriptions; + } + + bool operator==(const Vertex& other) const { + return pos == other.pos && color == other.color && texCoord == other.texCoord; + } +}; + +namespace std { + template<> struct hash { + size_t operator()(Vertex const& vertex) const { + return ((hash()(vertex.pos) ^ (hash()(vertex.color) << 1)) >> 1) ^ (hash()(vertex.texCoord) << 1); + } + }; +} + +struct UniformBufferObject { + glm::mat4 model; + glm::mat4 view; + glm::mat4 proj; +}; + +class HelloTriangleApplication { +public: + void run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugReportCallbackEXT callback; + VkSurfaceKHR surface; + + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkSampleCountFlagBits msaaSamples = VK_SAMPLE_COUNT_1_BIT; + VkDevice device; + + VkQueue graphicsQueue; + VkQueue presentQueue; + + VkSwapchainKHR swapChain; + std::vector swapChainImages; + VkFormat swapChainImageFormat; + VkExtent2D swapChainExtent; + std::vector swapChainImageViews; + std::vector swapChainFramebuffers; + + VkRenderPass renderPass; + VkDescriptorSetLayout descriptorSetLayout; + VkPipelineLayout pipelineLayout; + VkPipeline graphicsPipeline; + + VkCommandPool commandPool; + + std::vector colorImages; + std::vector colorImagesMemory; + std::vector colorImageViews; + + std::vector depthImages; + std::vector depthImagesMemory; + std::vector depthImagesView; + + uint32_t mipLevels; + VkImage textureImage; + VkDeviceMemory textureImageMemory; + VkImageView textureImageView; + VkSampler textureSampler; + + std::vector vertices; + std::vector indices; + VkBuffer vertexBuffer; + VkDeviceMemory vertexBufferMemory; + VkBuffer indexBuffer; + VkDeviceMemory indexBufferMemory; + + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + + VkDescriptorPool descriptorPool; + std::vector descriptorSets; + + std::vector commandBuffers; + + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + std::vector inFlightFences; + size_t currentFrame = 0; + + bool framebufferResized = false; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + + static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + void initVulkan() { + createInstance(); + setupDebugCallback(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createDescriptorSetLayout(); + createGraphicsPipeline(); + createCommandPool(); + createColorResources(); + createDepthResources(); + createFramebuffers(); + createTextureImage(); + createTextureImageView(); + createTextureSampler(); + loadModel(); + createVertexBuffer(); + createIndexBuffer(); + createUniformBuffers(); + createDescriptorPool(); + createDescriptorSets(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + drawFrame(); + } + + vkDeviceWaitIdle(device); + } + + void cleanupSwapChain() { + for (size_t i = 0; i < swapChainImages.size(); i++) { + vkDestroyImageView(device, colorImageViews[i], nullptr); + vkDestroyImage(device, colorImages[i], nullptr); + vkFreeMemory(device, colorImagesMemory[i], nullptr); + vkDestroyImageView(device, depthImagesView[i], nullptr); + vkDestroyImage(device, depthImages[i], nullptr); + vkFreeMemory(device, depthImagesMemory[i], nullptr); + } + + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } + + vkFreeCommandBuffers(device, commandPool, static_cast(commandBuffers.size()), commandBuffers.data()); + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + } + + void cleanup() { + cleanupSwapChain(); + + vkDestroySampler(device, textureSampler, nullptr); + vkDestroyImageView(device, textureImageView, nullptr); + + vkDestroyImage(device, textureImage, nullptr); + vkFreeMemory(device, textureImageMemory, nullptr); + + vkDestroyDescriptorPool(device, descriptorPool, nullptr); + + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + vkDestroyBuffer(device, uniformBuffers[i], nullptr); + vkFreeMemory(device, uniformBuffersMemory[i], nullptr); + } + + vkDestroyBuffer(device, indexBuffer, nullptr); + vkFreeMemory(device, indexBufferMemory, nullptr); + + vkDestroyBuffer(device, vertexBuffer, nullptr); + vkFreeMemory(device, vertexBufferMemory, nullptr); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + vkDestroyFence(device, inFlightFences[i], nullptr); + } + + vkDestroyCommandPool(device, commandPool, nullptr); + + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugReportCallbackEXT(instance, callback, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() { + int width = 0, height = 0; + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + vkDeviceWaitIdle(device); + + cleanupSwapChain(); + + createSwapChain(); + createImageViews(); + createRenderPass(); + createGraphicsPipeline(); + createColorResources(); + createDepthResources(); + createFramebuffers(); + createCommandBuffers(); + } + + void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + VkApplicationInfo appInfo = {}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_0; + + VkInstanceCreateInfo createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + } + + void setupDebugCallback() { + if (!enableValidationLayers) return; + + VkDebugReportCallbackCreateInfoEXT createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT; + createInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT; + createInfo.pfnCallback = debugCallback; + + if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, &callback) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug callback!"); + } + } + + void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface!"); + } + } + + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + msaaSamples = getMaxUsableSampleCount(); + break; + } + } + + if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("failed to find a suitable GPU!"); + } + } + + void createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = {indices.graphicsFamily, indices.presentFamily}; + + float queuePriority = 1.0f; + for (int queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo = {}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceFeatures deviceFeatures = {}; + deviceFeatures.samplerAnisotropy = VK_TRUE; + + VkDeviceCreateInfo createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pEnabledFeatures = &deviceFeatures; + + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + vkGetDeviceQueue(device, indices.graphicsFamily, 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily, 0, &presentQueue); + } + + void createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + uint32_t queueFamilyIndices[] = {(uint32_t) indices.graphicsFamily, (uint32_t) indices.presentFamily}; + + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw std::runtime_error("failed to create swap chain!"); + } + + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + swapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + + for (uint32_t i = 0; i < swapChainImages.size(); i++) { + swapChainImageViews[i] = createImageView(swapChainImages[i], swapChainImageFormat, VK_IMAGE_ASPECT_COLOR_BIT, 1); + } + } + + void createRenderPass() { + VkAttachmentDescription colorAttachment = {}; + colorAttachment.format = swapChainImageFormat; + colorAttachment.samples = msaaSamples; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + colorAttachment.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkAttachmentDescription depthAttachment = {}; + depthAttachment.format = findDepthFormat(); + depthAttachment.samples = msaaSamples; + depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + VkAttachmentDescription colorAttachmentResolve = {}; + colorAttachmentResolve.format = swapChainImageFormat; + colorAttachmentResolve.samples = VK_SAMPLE_COUNT_1_BIT; + colorAttachmentResolve.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachmentResolve.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachmentResolve.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachmentResolve.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachmentResolve.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + colorAttachmentResolve.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + VkAttachmentReference colorAttachmentRef = {}; + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkAttachmentReference depthAttachmentRef = {}; + depthAttachmentRef.attachment = 1; + depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + VkAttachmentReference colorAttachmentResolveRef = {}; + colorAttachmentResolveRef.attachment = 2; + colorAttachmentResolveRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpass = {}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; + subpass.pDepthStencilAttachment = &depthAttachmentRef; + subpass.pResolveAttachments = &colorAttachmentResolveRef; + + VkSubpassDependency dependency = {}; + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.srcAccessMask = 0; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + + std::array attachments = {colorAttachment, depthAttachment, colorAttachmentResolve }; + VkRenderPassCreateInfo renderPassInfo = {}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassInfo.attachmentCount = static_cast(attachments.size()); + renderPassInfo.pAttachments = attachments.data(); + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + renderPassInfo.dependencyCount = 1; + renderPassInfo.pDependencies = &dependency; + + if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { + throw std::runtime_error("failed to create render pass!"); + } + } + + void createDescriptorSetLayout() { + VkDescriptorSetLayoutBinding uboLayoutBinding = {}; + uboLayoutBinding.binding = 0; + uboLayoutBinding.descriptorCount = 1; + uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + uboLayoutBinding.pImmutableSamplers = nullptr; + uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + + VkDescriptorSetLayoutBinding samplerLayoutBinding = {}; + samplerLayoutBinding.binding = 1; + samplerLayoutBinding.descriptorCount = 1; + samplerLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + samplerLayoutBinding.pImmutableSamplers = nullptr; + samplerLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + + std::array bindings = {uboLayoutBinding, samplerLayoutBinding}; + VkDescriptorSetLayoutCreateInfo layoutInfo = {}; + layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + layoutInfo.bindingCount = static_cast(bindings.size()); + layoutInfo.pBindings = bindings.data(); + + if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &descriptorSetLayout) != VK_SUCCESS) { + throw std::runtime_error("failed to create descriptor set layout!"); + } + } + + void createGraphicsPipeline() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); + + VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); + + VkPipelineShaderStageCreateInfo vertShaderStageInfo = {}; + vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; + vertShaderStageInfo.module = vertShaderModule; + vertShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo fragShaderStageInfo = {}; + fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + fragShaderStageInfo.module = fragShaderModule; + fragShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; + + VkPipelineVertexInputStateCreateInfo vertexInputInfo = {}; + vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + + vertexInputInfo.vertexBindingDescriptionCount = 1; + vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()); + vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; + vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); + + VkPipelineInputAssemblyStateCreateInfo inputAssembly = {}; + inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + inputAssembly.primitiveRestartEnable = VK_FALSE; + + VkViewport viewport = {}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float) swapChainExtent.width; + viewport.height = (float) swapChainExtent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + + VkRect2D scissor = {}; + scissor.offset = {0, 0}; + scissor.extent = swapChainExtent; + + VkPipelineViewportStateCreateInfo viewportState = {}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.pViewports = &viewport; + viewportState.scissorCount = 1; + viewportState.pScissors = &scissor; + + VkPipelineRasterizationStateCreateInfo rasterizer = {}; + rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.lineWidth = 1.0f; + rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; + rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + + VkPipelineMultisampleStateCreateInfo multisampling = {}; + multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.rasterizationSamples = msaaSamples; + + VkPipelineDepthStencilStateCreateInfo depthStencil = {}; + depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; + depthStencil.depthTestEnable = VK_TRUE; + depthStencil.depthWriteEnable = VK_TRUE; + depthStencil.depthCompareOp = VK_COMPARE_OP_LESS; + depthStencil.depthBoundsTestEnable = VK_FALSE; + depthStencil.stencilTestEnable = VK_FALSE; + + VkPipelineColorBlendAttachmentState colorBlendAttachment = {}; + colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + colorBlendAttachment.blendEnable = VK_FALSE; + + VkPipelineColorBlendStateCreateInfo colorBlending = {}; + colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlending.logicOpEnable = VK_FALSE; + colorBlending.logicOp = VK_LOGIC_OP_COPY; + colorBlending.attachmentCount = 1; + colorBlending.pAttachments = &colorBlendAttachment; + colorBlending.blendConstants[0] = 0.0f; + colorBlending.blendConstants[1] = 0.0f; + colorBlending.blendConstants[2] = 0.0f; + colorBlending.blendConstants[3] = 0.0f; + + VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.setLayoutCount = 1; + pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout; + + if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { + throw std::runtime_error("failed to create pipeline layout!"); + } + + VkGraphicsPipelineCreateInfo pipelineInfo = {}; + pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipelineInfo.stageCount = 2; + pipelineInfo.pStages = shaderStages; + pipelineInfo.pVertexInputState = &vertexInputInfo; + pipelineInfo.pInputAssemblyState = &inputAssembly; + pipelineInfo.pViewportState = &viewportState; + pipelineInfo.pRasterizationState = &rasterizer; + pipelineInfo.pMultisampleState = &multisampling; + pipelineInfo.pDepthStencilState = &depthStencil; + pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.layout = pipelineLayout; + pipelineInfo.renderPass = renderPass; + pipelineInfo.subpass = 0; + pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; + + if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { + throw std::runtime_error("failed to create graphics pipeline!"); + } + + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); + } + + void createFramebuffers() { + swapChainFramebuffers.resize(swapChainImageViews.size()); + + for (size_t i = 0; i < swapChainImageViews.size(); i++) { + std::array attachments = { + colorImageViews[i], + depthImagesView[i], + swapChainImageViews[i] + }; + + VkFramebufferCreateInfo framebufferInfo = {}; + framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebufferInfo.renderPass = renderPass; + framebufferInfo.attachmentCount = static_cast(attachments.size()); + framebufferInfo.pAttachments = attachments.data(); + framebufferInfo.width = swapChainExtent.width; + framebufferInfo.height = swapChainExtent.height; + framebufferInfo.layers = 1; + + if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create framebuffer!"); + } + } + } + + void createCommandPool() { + QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); + + VkCommandPoolCreateInfo poolInfo = {}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily; + + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create graphics command pool!"); + } + } + + void createColorResources() { + VkFormat colorFormat = swapChainImageFormat; + + colorImages.resize(swapChainImages.size()); + colorImagesMemory.resize(swapChainImages.size()); + colorImageViews.resize(swapChainImages.size()); + + for (size_t i = 0; i < swapChainImageViews.size(); i++) { + createImage(swapChainExtent.width, swapChainExtent.height, 1, msaaSamples, colorFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, colorImages[i], colorImagesMemory[i]); + colorImageViews[i] = createImageView(colorImages[i], colorFormat, VK_IMAGE_ASPECT_COLOR_BIT, 1); + + transitionImageLayout(colorImages[i], colorFormat, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, 1); + } + } + + void createDepthResources() { + VkFormat depthFormat = findDepthFormat(); + + depthImages.resize(swapChainImages.size()); + depthImagesMemory.resize(swapChainImages.size()); + depthImagesView.resize(swapChainImages.size()); + + for (size_t i = 0; i < swapChainImageViews.size(); i++) { + createImage(swapChainExtent.width, swapChainExtent.height, 1, msaaSamples, depthFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, depthImages[i], depthImagesMemory[i]); + depthImagesView[i] = createImageView(depthImages[i], depthFormat, VK_IMAGE_ASPECT_DEPTH_BIT, 1); + + transitionImageLayout(depthImages[i], depthFormat, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, 1); + } + } + + VkFormat findSupportedFormat(const std::vector& candidates, VkImageTiling tiling, VkFormatFeatureFlags features) { + for (VkFormat format : candidates) { + VkFormatProperties props; + vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &props); + + if (tiling == VK_IMAGE_TILING_LINEAR && (props.linearTilingFeatures & features) == features) { + return format; + } else if (tiling == VK_IMAGE_TILING_OPTIMAL && (props.optimalTilingFeatures & features) == features) { + return format; + } + } + + throw std::runtime_error("failed to find supported format!"); + } + + VkFormat findDepthFormat() { + return findSupportedFormat( + {VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT}, + VK_IMAGE_TILING_OPTIMAL, + VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT + ); + } + + bool hasStencilComponent(VkFormat format) { + return format == VK_FORMAT_D32_SFLOAT_S8_UINT || format == VK_FORMAT_D24_UNORM_S8_UINT; + } + + void createTextureImage() { + int texWidth, texHeight, texChannels; + stbi_uc* pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + VkDeviceSize 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!"); + } + + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); + + void* data; + vkMapMemory(device, stagingBufferMemory, 0, imageSize, 0, &data); + memcpy(data, pixels, static_cast(imageSize)); + vkUnmapMemory(device, stagingBufferMemory); + + stbi_image_free(pixels); + + createImage(texWidth, texHeight, mipLevels, VK_SAMPLE_COUNT_1_BIT, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, textureImage, textureImageMemory); + + transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, mipLevels); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); + //transitioned to VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL while generating mipmaps + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); + + generateMipmaps(textureImage, VK_FORMAT_R8G8B8A8_UNORM, texWidth, texHeight, mipLevels); + } + + void generateMipmaps(VkImage image, VkFormat imageFormat, int32_t texWidth, int32_t texHeight, uint32_t mipLevels) { + // Check if image format supports linear blitting + VkFormatProperties formatProperties; + vkGetPhysicalDeviceFormatProperties(physicalDevice, imageFormat, &formatProperties); + + if (!(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT)) { + throw std::runtime_error("texture image format does not support linear blitting!"); + } + + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkImageMemoryBarrier barrier = {}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.image = image; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + 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_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + + vkCmdPipelineBarrier(commandBuffer, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, + 0, nullptr, + 0, nullptr, + 1, &barrier); + + VkImageBlit blit = {}; + blit.srcOffsets[0] = {0, 0, 0}; + blit.srcOffsets[1] = {mipWidth, mipHeight, 1}; + blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + blit.srcSubresource.mipLevel = i - 1; + blit.srcSubresource.baseArrayLayer = 0; + blit.srcSubresource.layerCount = 1; + blit.dstOffsets[0] = {0, 0, 0}; + blit.dstOffsets[1] = { mipWidth > 1 ? mipWidth / 2 : 1, mipHeight > 1 ? mipHeight / 2 : 1, 1 }; + blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + blit.dstSubresource.mipLevel = i; + blit.dstSubresource.baseArrayLayer = 0; + blit.dstSubresource.layerCount = 1; + + vkCmdBlitImage(commandBuffer, + image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, &blit, + VK_FILTER_LINEAR); + + barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + + vkCmdPipelineBarrier(commandBuffer, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, + 0, nullptr, + 0, nullptr, + 1, &barrier); + + if (mipWidth > 1) mipWidth /= 2; + if (mipHeight > 1) mipHeight /= 2; + } + + barrier.subresourceRange.baseMipLevel = mipLevels - 1; + barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + + vkCmdPipelineBarrier(commandBuffer, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, + 0, nullptr, + 0, nullptr, + 1, &barrier); + + endSingleTimeCommands(commandBuffer); + } + +VkSampleCountFlagBits getMaxUsableSampleCount() { + VkPhysicalDeviceProperties physicalDeviceProperties; + vkGetPhysicalDeviceProperties(physicalDevice, &physicalDeviceProperties); + + VkSampleCountFlags counts = std::min(physicalDeviceProperties.limits.framebufferColorSampleCounts, physicalDeviceProperties.limits.framebufferDepthSampleCounts); + if (counts & VK_SAMPLE_COUNT_64_BIT) { return VK_SAMPLE_COUNT_64_BIT; } + if (counts & VK_SAMPLE_COUNT_32_BIT) { return VK_SAMPLE_COUNT_32_BIT; } + if (counts & VK_SAMPLE_COUNT_16_BIT) { return VK_SAMPLE_COUNT_16_BIT; } + if (counts & VK_SAMPLE_COUNT_8_BIT) { return VK_SAMPLE_COUNT_8_BIT; } + if (counts & VK_SAMPLE_COUNT_4_BIT) { return VK_SAMPLE_COUNT_4_BIT; } + if (counts & VK_SAMPLE_COUNT_2_BIT) { return VK_SAMPLE_COUNT_2_BIT; } + + return VK_SAMPLE_COUNT_1_BIT; +} + + void createTextureImageView() { + textureImageView = createImageView(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT, mipLevels); + } + + void createTextureSampler() { + VkSamplerCreateInfo samplerInfo = {}; + samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + samplerInfo.magFilter = VK_FILTER_LINEAR; + samplerInfo.minFilter = VK_FILTER_LINEAR; + samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.anisotropyEnable = VK_TRUE; + samplerInfo.maxAnisotropy = 16; + samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; + samplerInfo.unnormalizedCoordinates = VK_FALSE; + samplerInfo.compareEnable = VK_FALSE; + samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS; + samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + samplerInfo.minLod = 0; + samplerInfo.maxLod = static_cast(mipLevels); + samplerInfo.mipLodBias = 0; + + if (vkCreateSampler(device, &samplerInfo, nullptr, &textureSampler) != VK_SUCCESS) { + throw std::runtime_error("failed to create texture sampler!"); + } + } + + VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags, uint32_t mipLevels) { + VkImageViewCreateInfo viewInfo = {}; + viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewInfo.image = image; + viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewInfo.format = format; + viewInfo.subresourceRange.aspectMask = aspectFlags; + viewInfo.subresourceRange.baseMipLevel = 0; + viewInfo.subresourceRange.levelCount = mipLevels; + viewInfo.subresourceRange.baseArrayLayer = 0; + viewInfo.subresourceRange.layerCount = 1; + + VkImageView imageView; + if (vkCreateImageView(device, &viewInfo, nullptr, &imageView) != VK_SUCCESS) { + throw std::runtime_error("failed to create texture image view!"); + } + + return imageView; + } + + void createImage(uint32_t width, uint32_t height, uint32_t mipLevels, VkSampleCountFlagBits numSamples, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage& image, VkDeviceMemory& imageMemory) { + VkImageCreateInfo imageInfo = {}; + imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.extent.width = width; + imageInfo.extent.height = height; + imageInfo.extent.depth = 1; + imageInfo.mipLevels = mipLevels; + imageInfo.arrayLayers = 1; + imageInfo.format = format; + imageInfo.tiling = tiling; + imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imageInfo.usage = usage; + imageInfo.samples = numSamples; + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + if (vkCreateImage(device, &imageInfo, nullptr, &image) != VK_SUCCESS) { + throw std::runtime_error("failed to create image!"); + } + + VkMemoryRequirements memRequirements; + vkGetImageMemoryRequirements(device, image, &memRequirements); + + VkMemoryAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = memRequirements.size; + allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); + + if (vkAllocateMemory(device, &allocInfo, nullptr, &imageMemory) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate image memory!"); + } + + vkBindImageMemory(device, image, imageMemory, 0); + } + + void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout, uint32_t mipLevels) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkImageMemoryBarrier barrier = {}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.oldLayout = oldLayout; + barrier.newLayout = newLayout; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image; + + if (newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) { + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + + if (hasStencilComponent(format)) { + barrier.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT; + } + } else { + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + } + + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = mipLevels; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + + VkPipelineStageFlags sourceStage; + VkPipelineStageFlags destinationStage; + + if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + + sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + } else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + + sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + } else if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) { + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + + sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + destinationStage = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + } + else if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) { + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + destinationStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + } + else { + throw std::invalid_argument("unsupported layout transition!"); + } + + vkCmdPipelineBarrier( + commandBuffer, + sourceStage, destinationStage, + 0, + 0, nullptr, + 0, nullptr, + 1, &barrier + ); + + endSingleTimeCommands(commandBuffer); + } + + void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkBufferImageCopy region = {}; + region.bufferOffset = 0; + region.bufferRowLength = 0; + region.bufferImageHeight = 0; + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.mipLevel = 0; + region.imageSubresource.baseArrayLayer = 0; + region.imageSubresource.layerCount = 1; + region.imageOffset = {0, 0, 0}; + region.imageExtent = { + width, + height, + 1 + }; + + vkCmdCopyBufferToImage(commandBuffer, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); + + endSingleTimeCommands(commandBuffer); + } + + void loadModel() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string err; + + if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &err, MODEL_PATH.c_str())) { + throw std::runtime_error(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 createVertexBuffer() { + VkDeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); + + void* data; + vkMapMemory(device, stagingBufferMemory, 0, bufferSize, 0, &data); + memcpy(data, vertices.data(), (size_t) bufferSize); + vkUnmapMemory(device, stagingBufferMemory); + + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, vertexBuffer, vertexBufferMemory); + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); + } + + void createIndexBuffer() { + VkDeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); + + void* data; + vkMapMemory(device, stagingBufferMemory, 0, bufferSize, 0, &data); + memcpy(data, indices.data(), (size_t) bufferSize); + vkUnmapMemory(device, stagingBufferMemory); + + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, indexBuffer, indexBufferMemory); + + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); + } + + void createUniformBuffers() { + VkDeviceSize bufferSize = sizeof(UniformBufferObject); + + uniformBuffers.resize(swapChainImages.size()); + uniformBuffersMemory.resize(swapChainImages.size()); + + for (size_t i = 0; i < swapChainImages.size(); i++) { + createBuffer(bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformBuffers[i], uniformBuffersMemory[i]); + } + } + + void createDescriptorPool() { + std::array poolSizes = {}; + poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + poolSizes[0].descriptorCount = static_cast(swapChainImages.size()); + poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + poolSizes[1].descriptorCount = static_cast(swapChainImages.size()); + + VkDescriptorPoolCreateInfo poolInfo = {}; + poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + poolInfo.poolSizeCount = static_cast(poolSizes.size()); + poolInfo.pPoolSizes = poolSizes.data(); + poolInfo.maxSets = static_cast(swapChainImages.size()); + + if (vkCreateDescriptorPool(device, &poolInfo, nullptr, &descriptorPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create descriptor pool!"); + } + } + + void createDescriptorSets() { + std::vector layouts(swapChainImages.size(), descriptorSetLayout); + VkDescriptorSetAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = descriptorPool; + allocInfo.descriptorSetCount = static_cast(swapChainImages.size()); + allocInfo.pSetLayouts = layouts.data(); + + descriptorSets.resize(swapChainImages.size()); + if (vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets[0]) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate descriptor sets!"); + } + + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkDescriptorBufferInfo bufferInfo = {}; + bufferInfo.buffer = uniformBuffers[i]; + bufferInfo.offset = 0; + bufferInfo.range = sizeof(UniformBufferObject); + + VkDescriptorImageInfo imageInfo = {}; + imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + imageInfo.imageView = textureImageView; + imageInfo.sampler = textureSampler; + + std::array descriptorWrites = {}; + + descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[0].dstSet = descriptorSets[i]; + descriptorWrites[0].dstBinding = 0; + descriptorWrites[0].dstArrayElement = 0; + descriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptorWrites[0].descriptorCount = 1; + descriptorWrites[0].pBufferInfo = &bufferInfo; + + descriptorWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[1].dstSet = descriptorSets[i]; + descriptorWrites[1].dstBinding = 1; + descriptorWrites[1].dstArrayElement = 0; + descriptorWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + descriptorWrites[1].descriptorCount = 1; + descriptorWrites[1].pImageInfo = &imageInfo; + + vkUpdateDescriptorSets(device, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); + } + } + + void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) { + VkBufferCreateInfo bufferInfo = {}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = size; + bufferInfo.usage = usage; + bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + if (vkCreateBuffer(device, &bufferInfo, nullptr, &buffer) != VK_SUCCESS) { + throw std::runtime_error("failed to create buffer!"); + } + + VkMemoryRequirements memRequirements; + vkGetBufferMemoryRequirements(device, buffer, &memRequirements); + + VkMemoryAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = memRequirements.size; + allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); + + if (vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate buffer memory!"); + } + + vkBindBufferMemory(device, buffer, bufferMemory, 0); + } + + VkCommandBuffer beginSingleTimeCommands() { + VkCommandBufferAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandPool = commandPool; + allocInfo.commandBufferCount = 1; + + VkCommandBuffer commandBuffer; + vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer); + + VkCommandBufferBeginInfo beginInfo = {}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + vkBeginCommandBuffer(commandBuffer, &beginInfo); + + return commandBuffer; + } + + void endSingleTimeCommands(VkCommandBuffer commandBuffer) { + vkEndCommandBuffer(commandBuffer); + + VkSubmitInfo submitInfo = {}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE); + vkQueueWaitIdle(graphicsQueue); + + vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); + } + + void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkBufferCopy copyRegion = {}; + copyRegion.size = size; + vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); + + endSingleTimeCommands(commandBuffer); + } + + uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) { + VkPhysicalDeviceMemoryProperties memProperties; + vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties); + + 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.resize(swapChainFramebuffers.size()); + + VkCommandBufferAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = (uint32_t) commandBuffers.size(); + + if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate command buffers!"); + } + + for (size_t i = 0; i < commandBuffers.size(); i++) { + VkCommandBufferBeginInfo beginInfo = {}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT; + + if (vkBeginCommandBuffer(commandBuffers[i], &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); + } + + VkRenderPassBeginInfo renderPassInfo = {}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = renderPass; + renderPassInfo.framebuffer = swapChainFramebuffers[i]; + renderPassInfo.renderArea.offset = {0, 0}; + renderPassInfo.renderArea.extent = swapChainExtent; + + std::array clearValues = {}; + clearValues[0].color = {0.0f, 0.0f, 0.0f, 1.0f}; + clearValues[1].depthStencil = {1.0f, 0}; + + renderPassInfo.clearValueCount = static_cast(clearValues.size()); + renderPassInfo.pClearValues = clearValues.data(); + + vkCmdBeginRenderPass(commandBuffers[i], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + + vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); + + VkBuffer vertexBuffers[] = {vertexBuffer}; + VkDeviceSize offsets[] = {0}; + vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets); + + vkCmdBindIndexBuffer(commandBuffers[i], indexBuffer, 0, VK_INDEX_TYPE_UINT32); + + vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets[i], 0, nullptr); + + vkCmdDrawIndexed(commandBuffers[i], static_cast(indices.size()), 1, 0, 0, 0); + + vkCmdEndRenderPass(commandBuffers[i]); + + if (vkEndCommandBuffer(commandBuffers[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); + } + } + } + + void createSyncObjects() { + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + inFlightFences.resize(MAX_FRAMES_IN_FLIGHT); + + VkSemaphoreCreateInfo semaphoreInfo = {}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + VkFenceCreateInfo fenceInfo = {}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS || + vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create synchronization objects for a frame!"); + } + } + } + + 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; + vkMapMemory(device, uniformBuffersMemory[currentImage], 0, sizeof(ubo), 0, &data); + memcpy(data, &ubo, sizeof(ubo)); + vkUnmapMemory(device, uniformBuffersMemory[currentImage]); + } + + void drawFrame() { + vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, std::numeric_limits::max()); + + uint32_t imageIndex; + VkResult result = vkAcquireNextImageKHR(device, swapChain, std::numeric_limits::max(), imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + + if (result == VK_ERROR_OUT_OF_DATE_KHR) { + recreateSwapChain(); + return; + } else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { + throw std::runtime_error("failed to acquire swap chain image!"); + } + + updateUniformBuffer(imageIndex); + + VkSubmitInfo submitInfo = {}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + + VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[currentFrame]}; + VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; + submitInfo.waitSemaphoreCount = 1; + submitInfo.pWaitSemaphores = waitSemaphores; + submitInfo.pWaitDstStageMask = waitStages; + + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffers[imageIndex]; + + VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[currentFrame]}; + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = signalSemaphores; + + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) { + throw std::runtime_error("failed to submit draw command buffer!"); + } + + VkPresentInfoKHR presentInfo = {}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = signalSemaphores; + + VkSwapchainKHR swapChains[] = {swapChain}; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapChains; + + presentInfo.pImageIndices = &imageIndex; + + result = vkQueuePresentKHR(presentQueue, &presentInfo); + + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { + framebufferResized = false; + recreateSwapChain(); + } else if (result != VK_SUCCESS) { + throw std::runtime_error("failed to present swap chain image!"); + } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + VkShaderModule createShaderModule(const std::vector& code) { + VkShaderModuleCreateInfo createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); + + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { + throw std::runtime_error("failed to create shader module!"); + } + + return shaderModule; + } + + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + if (availableFormats.size() == 1 && availableFormats[0].format == VK_FORMAT_UNDEFINED) { + return{VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}; + } + + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_UNORM && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR chooseSwapPresentMode(const std::vector availablePresentModes) { + VkPresentModeKHR bestMode = VK_PRESENT_MODE_FIFO_KHR; + + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return availablePresentMode; + } else if (availablePresentMode == VK_PRESENT_MODE_IMMEDIATE_KHR) { + bestMode = availablePresentMode; + } + } + + return bestMode; + } + + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::max(capabilities.minImageExtent.width, std::min(capabilities.maxImageExtent.width, actualExtent.width)); + actualExtent.height = std::max(capabilities.minImageExtent.height, std::min(capabilities.maxImageExtent.height, actualExtent.height)); + + return actualExtent; + } + } + + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + } + + return details; + } + + bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + bool swapChainAdequate = false; + if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); + } + + VkPhysicalDeviceFeatures supportedFeatures; + vkGetPhysicalDeviceFeatures(device, &supportedFeatures); + + return indices.isComplete() && extensionsSupported && swapChainAdequate && supportedFeatures.samplerAnisotropy; + } + + bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (queueFamily.queueCount > 0 && presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); + } + + return extensions; + } + + bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + 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!"); + } + + size_t fileSize = (size_t) file.tellg(); + std::vector buffer(fileSize); + + file.seekg(0); + file.read(buffer.data(), fileSize); + + file.close(); + + return buffer; + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objType, uint64_t obj, size_t location, int32_t code, const char* layerPrefix, const char* msg, void* userData) { + std::cerr << "validation layer: " << msg << std::endl; + + return VK_FALSE; + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/code/base_code.cpp b/code/base_code.cpp deleted file mode 100644 index 8b5fe910..00000000 --- a/code/base_code.cpp +++ /dev/null @@ -1,115 +0,0 @@ -#define GLFW_INCLUDE_VULKAN -#include - -#include -#include -#include - -const int WIDTH = 800; -const int HEIGHT = 600; - -template -class VDeleter { -public: - VDeleter() : VDeleter([](T, VkAllocationCallbacks*) {}) {} - - VDeleter(std::function deletef) { - this->deleter = [=](T obj) { deletef(obj, nullptr); }; - } - - VDeleter(const VDeleter& instance, std::function deletef) { - this->deleter = [&instance, deletef](T obj) { deletef(instance, obj, nullptr); }; - } - - VDeleter(const VDeleter& device, std::function deletef) { - this->deleter = [&device, deletef](T obj) { deletef(device, obj, nullptr); }; - } - - ~VDeleter() { - cleanup(); - } - - const T* operator &() const { - return &object; - } - - T* replace() { - cleanup(); - return &object; - } - - operator T() const { - return object; - } - - void operator=(T rhs) { - if (rhs != object) { - cleanup(); - object = rhs; - } - } - - template - bool operator==(V rhs) { - return object == T(rhs); - } - -private: - T object{VK_NULL_HANDLE}; - std::function deleter; - - void cleanup() { - if (object != VK_NULL_HANDLE) { - deleter(object); - } - object = VK_NULL_HANDLE; - } -}; - -class HelloTriangleApplication { -public: - void run() { - initWindow(); - initVulkan(); - mainLoop(); - } - -private: - GLFWwindow* window; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - } - - void initVulkan() { - - } - - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - } - - glfwDestroyWindow(window); - - glfwTerminate(); - } -}; - -int main() { - HelloTriangleApplication app; - - try { - app.run(); - } catch (const std::runtime_error& e) { - std::cerr << e.what() << std::endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/config.json b/config.json index 7b7f1f04..6c974113 100644 --- a/config.json +++ b/config.json @@ -19,6 +19,7 @@ "google_analytics": "UA-60335079-1", "links": { "GitHub Repository": "https://github.com/Overv/VulkanTutorial", + "Support the website": "https://www.paypal.me/AOvervoorde", "Vulkan Specification": "https://www.khronos.org/registry/vulkan/specs/1.0-wsi_extensions/pdf/vkspec.pdf", "Vulkan Quick Reference": "https://www.khronos.org/files/vulkan10-reference-guide.pdf", "LunarG Vulkan SDK": "https://lunarg.com/vulkan-sdk/", diff --git a/daux.patch b/daux.patch index 7571a6d3..46f0212a 100644 --- a/daux.patch +++ b/daux.patch @@ -1,18 +1,18 @@ -From 40008c88788c97fd4b81e1117a5f029d9a457acd Mon Sep 17 00:00:00 2001 +From bbf1e7e34f2dc687cb9507ad6ad1d2597e1f9006 Mon Sep 17 00:00:00 2001 From: Alexander Overvoorde -Date: Mon, 17 Apr 2017 20:02:04 +0200 +Date: Sun, 6 May 2018 17:44:07 +0200 Subject: [PATCH] Adjust theme for Vulkan tutorial --- daux/VulkanLinkProcessor.php | 69 ++++ - templates/content.php | 26 +- + templates/content.php | 25 +- templates/layout/00_layout.php | 2 +- - templates/layout/05_page.php | 17 +- + templates/layout/05_page.php | 22 +- themes/daux/css/theme-blue.min.css | 2 +- themes/daux/css/theme-green.min.css | 2 +- themes/daux/css/theme-navy.min.css | 2 +- themes/daux/css/theme-red.min.css | 2 +- - themes/daux/css/theme.min.css | 2 +- + themes/daux/css/theme.min.css | 24 +- themes/daux/js/daux.js | 12 + themes/daux/js/highlight.pack.js | 576 +++++++++++++++++++++++++++++++- themes/daux/less/components.less | 69 +++- @@ -20,7 +20,7 @@ Subject: [PATCH] Adjust theme for Vulkan tutorial themes/daux/less/structure.less | 27 +- themes/daux/less/theme-blue.less | 10 +- themes/daux_singlepage/css/main.min.css | 2 +- - 16 files changed, 924 insertions(+), 137 deletions(-) + 16 files changed, 948 insertions(+), 139 deletions(-) create mode 100644 daux/VulkanLinkProcessor.php diff --git a/daux/VulkanLinkProcessor.php b/daux/VulkanLinkProcessor.php @@ -99,7 +99,7 @@ index 0000000..4e4d456 + } +?> diff --git a/templates/content.php b/templates/content.php -index 2febe38..03017b7 100644 +index 2febe38..f1083bd 100644 --- a/templates/content.php +++ b/templates/content.php @@ -2,17 +2,14 @@ @@ -122,7 +122,7 @@ index 2febe38..03017b7 100644 -@@ -26,5 +23,24 @@ +@@ -26,5 +23,23 @@ @@ -131,8 +131,7 @@ index 2febe38..03017b7 100644 +