diff --git a/.gitignore b/.gitignore index 2d19fc76..a64ef7ec 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ *.html +ads.txt diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..51cdcf18 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "code/ext/stb"] + path = code/ext/stb + url = https://github.com/nothings/stb.git +[submodule "code/ext/tinyobjloader"] + path = code/ext/tinyobjloader + url = https://github.com/tinyobjloader/tinyobjloader.git diff --git a/02_Development_environment.md b/02_Development_environment.md deleted file mode 100644 index be964b44..00000000 --- a/02_Development_environment.md +++ /dev/null @@ -1,503 +0,0 @@ -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 -steps for installing them differ a bit, which is why they're described -separately here. - -## Windows - -If you're developing for Windows, then I will assume that you are using Visual -Studio 2013 or 2015 to compile your code. The steps are the same for both -versions, but the Vulkan SDK currently only includes debug symbols that are -compatible with Visual Studio 2013. That isn't really a problem in practice, but -it's something that you may wish to take into account. - -### 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) - -Proceed through the installation and pay attention to the install location of -the SDK. The first thing we'll do is verify that your graphics card and driver -properly support Vulkan. Go to the directory where you installed the SDK, open -the `Bin32` directory and run the `cube.exe` demo. You should see the following: - -![](/images/cube_demo.png) - -If you receive an error message then ensure that your drivers are up-to-date, -include the Vulkan runtime and that your graphics card is supported. See the -[introduction chapter](!Introduction) for links to drivers from the major -vendors. - -There are two other programs in this directory that will be useful for -development. The `vkjson_info.exe` program generates a JSON file with a detailed -description of the capabilities of your hardware when using Vulkan. If you are -wondering what support is like for extensions and other optional features among -the graphics cards of your end users, then you can use [this website](http://vulkan.gpuinfo.org/) -to view the results of a wide range of GPUs. - -The `glslangValidator.exe` program will be used to compile shaders from the -human-readable [GLSL](https://en.wikipedia.org/wiki/OpenGL_Shading_Language) to -bytecode. We'll cover this in depth in the [shader modules](!Drawing_a_triangle/Graphics_pipeline_basics/Shader_modules) -chapter. The `Bin32` directory also contains the binaries of the Vulkan loader -and the validation layers, while the `Lib32` directory contains the libraries. - -The `Doc` directory contains useful information about the Vulkan SDK and an -offline version of the entire Vulkan specification. Lastly, there's the -`Include` directory that contains the Vulkan headers. Feel free to explore the -other files, but we won't need them for this tutorial. - -### GLFW - -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 -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. - -You can find the latest release of GLFW on the [official website](http://www.glfw.org/download.html). -In this tutorial we'll be using the 32-bit binaries, but you can of course also -choose to build in 64 bit mode. In that case make sure to link with the Vulkan -SDK binaries in the `Bin` directory. After downloading it, extract the archive -to a convenient location. I've chosen to create a `Libraries` directory in the -Visual Studio directory under documents. - -![](/images/glfw_directory.png) - -### GLM - -Unlike DirectX 12, 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. - -GLM is a header-only library, so just download the [latest version](https://github.com/g-truc/glm/releases) -and store it in a convenient location. You should have a directory structure -similar to the following now: - -![](/images/library_directory.png) - -### Setting up Visual Studio - -Now that you've installed all of the dependencies we can set up a basic Visual -Studio project for Vulkan and write a little bit of code to make sure that -everything works. - -Start Visual Studio and create a new C++ Win32 project. - -![](/images/vs_new_cpp_project.png) - -Click `Next`, select `Console application` as application type and make sure -that `Empty project` is checked. - -![](/images/vs_application_settings.png) - -Press `Finish` to create the project and add a C++ source file. You should -already know how to do that, but the steps are included here for completeness. - -![](/images/vs_new_item.png) - -![](/images/vs_new_source_file.png) - -Now add the following code to the file. Don't worry about trying to -understand it right now; we're just making sure that you can compile and run -Vulkan applications. We'll start from scratch in the next chapter. - -```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; -} -``` - -Let's now configure the project to get rid of the errors. Open the project -properties dialog and ensure that `All Configurations` is selected, because most -of the settings apply to both `Debug` and `Release` mode. - -![](/images/vs_open_project_properties.png) - -![](/images/vs_all_configs.png) - -Go to `C++ -> General -> Additional Include Directories` and press `` -in the dropdown box. - -![](/images/vs_cpp_general.png) - -Add the header directories for Vulkan, GLFW and GLM: - -![](/images/vs_include_dirs.png) - -Next, open the editor for library directories: - -![](/images/vs_link_settings.png) - -And add the locations of the object files for Vulkan and GLFW: - -![](/images/vs_link_dirs.png) - -Go to `Linker -> Input` and press `` in the `Additional Dependencies` -dropdown box. - -![](/images/vs_link_input.png) - -Enter the names of the Vulkan and GLFW object files: - -![](/images/vs_dependencies.png) - -You can now close the project properties dialog. If you did everything right -then you should no longer see any more errors being highlighted in the code. - -Press `F5` to compile and run the project and you should see a command prompt -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). - -## Linux - -These instructions will be aimed at Ubuntu users, but you may be able to follow -along by compiling the LunarG SDK yourself and changing the `apt` commands to -the package manager commands that are appropriate for you. You should already -have a version of GCC installed that supports modern C++ (4.8 or later). You -also need both CMake and make. - -### 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) - -Open a terminal in the directory where you've downloaded the `.run` script, make -it executable and run it: - -```bash -chmod +x vulkansdk-linux-x86_64-xxx.run -./vulkansdk-linux-x86_64-xxx.run -``` - -It will extract all of the files in the SDK to a `VulkanSDK` subdirectory in the -working directory. Move the `VulkanSDK` directory to a convenient place and take -note of its path. Open a terminal in the root directory of the SDK, which will -contain files like `build_examples.sh`. - -The samples in the SDK and one of the libraries that you will later use for your -program depend on the XCB library. This is a C library that is used to interface -with the X Window System. It can be installed in Ubuntu from the `libxcb1-dev` -package. You also need the generic X development files that come with the -`xorg-dev` package. - -```bash -sudo apt install libxcb1-dev xorg-dev -``` - -You can now build the Vulkan examples in the SDK by running: - -```bash -./build_examples.sh -``` - -If compilation was successful, then you should now have a -`./examples/build/cube` executable. Run it from the `examples/build` directory -with `./cube` and ensure that you see the following pop up in a window: - -![](/images/cube_demo_nowindow.png) - -If you receive an error message then ensure that your drivers are up-to-date, -include the Vulkan runtime and that your graphics card is supported. See the -[introduction chapter](!Introduction) for links to drivers from the major -vendors. - -### 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. 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 -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. - -We'll be installing GLFW from source instead of using a package, because the -Vulkan support requires a recent version. You can find the sources on the [official website](http://www.glfw.org/). -Extract the source code to a convenient directory and open a terminal in the -directory with files like `CMakeLists.txt`. - -Run the following commands to generate a makefile and compile GLFW: - -```bash -cmake . -make -``` - -You may see a warning stating `Could NOT find Vulkan`, but you can safely ignore -this message. If compilation was successful, then you can install GLFW into the -system libraries by running: - -```bash -sudo make install -``` - -### GLM - -Unlike DirectX 12, 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 `libglm-dev` package: - -```bash -sudo apt install libglm-dev -``` - -### Setting up a makefile project - -Now that you have installed all of the dependencies, we can set up a basic -makefile project for Vulkan and write a little bit of code to make sure that -everything works. - -Create a new directory at a convenient location with a name like `VulkanTest`. -Create a source file called `main.cpp` and insert the following code. Don't -worry about trying to understand it right now; we're just making sure that you -can compile and run Vulkan applications. We'll start from scratch in the next -chapter. - -```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; -} -``` - -Next, we'll write a makefile to compile and run this basic Vulkan code. Create a -new empty file called `Makefile`. I will assume that you already have some basic -experience with makefiles, like how variables and rules work. If not, you can -get up to speed very quickly with [this tutorial](http://mrbook.org/blog/tutorials/make/). - -We'll first define a couple of variables to simplify the remainder of the file. -Define a `VULKAN_SDK_PATH` variable that refers to the location of the `x86_64` -directory in the LunarG SDK, for example: - -```make -VULKAN_SDK_PATH = /home/user/VulkanSDK/x.x.x.x/x86_64 -``` - -Next, define a `CFLAGS` variable that will specify the basic compiler flags: - -```make -CFLAGS = -std=c++11 -I$(VULKAN_SDK_PATH)/include -``` - -We're going to use modern C++ (`-std=c++11` or `std=c++14`), and we need to be -able to locate `vulkan.h` in the LunarG SDK. - -Similarly, define the linker flags in a `LDFLAGS` variable: - -```make -LDFLAGS = -L$(VULKAN_SDK_PATH)/lib `pkg-config --static --libs glfw3` -lvulkan -``` - -The first flag specifies that we want to be able to find libraries like -`libvulkan.so` in the LunarG SDK's `x86_64/lib` directory. The second component -invokes `pkg-config` to automatically retrieve all of the linker flags necessary -to build an application with GLFW. Finally, `-lvulkan` links with the Vulkan -function loader that comes with the LunarG SDK. - -Specifying the rule to compile `VulkanTest` is straightforward now. Make sure to -use tabs for indentation instead of spaces. - -```make -VulkanTest: main.cpp - g++ $(CFLAGS) -o VulkanTest main.cpp $(LDFLAGS) -``` - -Verify that this rule works by saving the makefile and running `make` in the -directory with `main.cpp` and `Makefile`. This should result in a `VulkanTest` -executable. - -We'll now define two more rules, `test` and `clean`, where the former will -run the executable and the latter will remove a built executable: - -```make -.PHONY: test clean - -test: VulkanTest - ./VulkanTest - -clean: - rm -f VulkanTest -``` - -You will find that `make clean` works perfectly fine, but `make test` will most -likely fail with the following error message: - -```text -./VulkanTest: error while loading shared libraries: libvulkan.so.1: cannot open shared object file: No such file or directory -``` - -That's because `libvulkan.so` is not installed as system library. To alleviate -this problem, explicitly specify the library loading path using the -`LD_LIBRARY_PATH` environment variable: - -```make -test: VulkanTest - LD_LIBRARY_PATH=$(VULKAN_SDK_PATH)/lib ./VulkanTest -``` - -The program should now run successfully, and display the number of Vulkan -extensions. The application should exit with the success return code (`0`) when -you close the empty window. However, there is one more variable that you need to -set. We will start using validation layers in Vulkan and you need to tell the -Vulkan library where to load these from using the `VK_LAYER_PATH` variable: - -```make -test: VulkanTest - LD_LIBRARY_PATH=$(VULKAN_SDK_PATH)/lib VK_LAYER_PATH=$(VULKAN_SDK_PATH)/etc/explicit_layer.d ./VulkanTest -``` - -You should now have a complete makefile that resembles the following: - -```make -VULKAN_SDK_PATH = /home/user/VulkanSDK/x.x.x.x/x86_64 - -CFLAGS = -std=c++11 -I$(VULKAN_SDK_PATH)/include -LDFLAGS = -L$(VULKAN_SDK_PATH)/lib `pkg-config --static --libs glfw3` -lvulkan - -VulkanTest: main.cpp - g++ $(CFLAGS) -o VulkanTest main.cpp $(LDFLAGS) - -.PHONY: test clean - -test: VulkanTest - LD_LIBRARY_PATH=$(VULKAN_SDK_PATH)/lib VK_LAYER_PATH=$(VULKAN_SDK_PATH)/etc/explicit_layer.d ./VulkanTest - -clean: - rm -f VulkanTest -``` - -You can now use this directory as a template for your Vulkan projects. Make a -copy, rename it to something like `HelloTriangle` and remove all of the code -in `main.cpp`. - -Before we move on, let's explore the Vulkan SDK a bit more. There are two -programs in it that will be very useful for development. The -`x86_64/bin/vkjson_info` program generates a JSON file with a detailed -description of the capabilities of your hardware when using Vulkan. If you are -wondering what support is like for extensions and other optional features among -the graphics cards of your end users, then you can use [this website](http://vulkan.gpuinfo.org/) -to view the results of a wide range of GPUs. This program needs to be run with -the same `LD_LIBRARY_PATH` variable as your own programs: - -```bash -LD_LIBRARY_PATH=../lib ./vkjson_info -``` - -The `x86_64/bin/glslangValidator` program will be used to compile shaders from -the human-readable [GLSL](https://en.wikipedia.org/wiki/OpenGL_Shading_Language) -to bytecode. We'll cover this in depth in the [shader modules](!Drawing_a_triangle/Graphics_pipeline_basics/Shader_modules) -chapter. It does not depend on the Vulkan library. - -The `Doc` directory contains useful information about the Vulkan SDK and an -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). diff --git a/03_Drawing_a_triangle/00_Setup/00_Base_code.md b/03_Drawing_a_triangle/00_Setup/00_Base_code.md deleted file mode 100644 index c5e6d59f..00000000 --- a/03_Drawing_a_triangle/00_Setup/00_Base_code.md +++ /dev/null @@ -1,325 +0,0 @@ -## General structure - -In the previous chapter you've created a Vulkan project with all of the proper -configuration and tested it with the sample code. In this chapter we're starting -from scratch with the following code: - -```c++ -#include - -#include -#include -#include - -class HelloTriangleApplication { -public: - void run() { - initVulkan(); - mainLoop(); - } - -private: - void initVulkan() { - - } - - void mainLoop() { - - } -}; - -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; -} -``` - -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. - -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. - -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. - -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. - -## 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! - -## Integrating GLFW - -Vulkan works perfectly fine without a creating a window if you want to use it -off-screen rendering, but it's a lot more exciting to actually show something! -First replace the `#include ` line with - -```c++ -#define GLFW_INCLUDE_VULKAN -#include -``` - -That way GLFW will include its own definitions and automatically load the Vulkan -header with it. Add a `initWindow` function and add a call to it from the `run` -function before the other calls. We'll use that function to initialize GLFW and -create a window. - -```c++ -void run() { - initWindow(); - initVulkan(); - mainLoop(); -} - -private: - void initWindow() { - - } -``` - -The very first call in `initWindow` should be `glfwInit()`, which initializes -the GLFW library. Because GLFW was originally designed to create an OpenGL -context, we need to tell it to not create an OpenGL context with a subsequent -call: - -```c++ -glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); -``` - -Because handling resized windows takes special care that we'll look into later, -disable it for now with another window hint call: - -```c++ -glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); -``` - -All that's left now is creating the actual window. Add a `GLFWwindow* window;` -private class member to store a reference to it and initialize the window with: - -```c++ -window = glfwCreateWindow(800, 600, "Vulkan", nullptr, nullptr); -``` - -The first three parameters specify the width, height and title of the window. -The fourth parameter allows you to optionally specify a monitor to open the -window on and the last parameter is only relevant to OpenGL. - -It's a good idea to use constants instead of hardcoded width and height numbers -because we'll be referring to these values a couple of times in the future. I've -added the following lines above the `HelloTriangleApplication` class definition: - -```c++ -const int WIDTH = 800; -const int HEIGHT = 600; -``` - -and replaced the window creation call with - -```c++ -window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); -``` - -You should now have a `initWindow` function that looks like this: - -```c++ -void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); -} -``` - -To keep the application running until either an error occurs or the window is -closed, we need to add an event loop to the `mainLoop` function as follows: - -```c++ -void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - } - - 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) diff --git a/03_Drawing_a_triangle/00_Setup/02_Validation_layers.md b/03_Drawing_a_triangle/00_Setup/02_Validation_layers.md deleted file mode 100644 index 5a9fa21f..00000000 --- a/03_Drawing_a_triangle/00_Setup/02_Validation_layers.md +++ /dev/null @@ -1,380 +0,0 @@ -## What are validation layers? - -The Vulkan API is designed around the idea of minimal driver overhead and one of -the manifestations of that goal is that there is very limited error checking in -the API by default. Even mistakes as simple as setting enumerations to incorrect -values or passing null pointers to required parameters are generally not -explicitly handled and will simply result in crashes or undefined behavior. -Because Vulkan requires you to be very explicit about everything you're doing, -it's easy to make many small mistakes like using a new GPU feature and -forgetting to request it at logical device creation time. - -However, that doesn't mean that these checks can't be added to the API. Vulkan -introduces an elegant system for this known as *validation layers*. Validation -layers are optional components that hook into Vulkan function calls to apply -additional operations. Common operations in validation layers are: - -* Checking the values of parameters against the specification to detect misuse -* Tracking creation and destruction of objects to find resource leaks -* Checking thread safety by tracking the threads that calls originate from -* Logging every call and its parameters to the standard output -* Tracing Vulkan calls for profiling and replaying - -Here's an example of what the implementation of a function in a diagnostics -validation layer could look like: - -```c++ -VkResult vkCreateInstance( - const VkInstanceCreateInfo* pCreateInfo, - const VkAllocationCallbacks* pAllocator, - VkInstance* instance) { - - if (pCreateInfo == nullptr || instance == nullptr) { - log("Null pointer passed to required parameter!"); - return VK_ERROR_INITIALIZATION_FAILED; - } - - return real_vkCreateInstance(pCreateInfo, pAllocator, instance); -} -``` - -These validation layers can be freely stacked to include all the debugging -functionality that you're interested in. You can simply enable validation layers -for debug builds and completely disable them for release builds, which gives you -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), -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. - -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 -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 -implementations. We'll simply specify the same layers as the instance at logical -device level, which we'll see [later on](!Drawing_a_triangle/Setup/Logical_device_and_queues). - -## Using validation layers - -In this section we'll see how to enable the standard diagnostics layers provided -by the Vulkan SDK. Just like extensions, validation layers need to be enabled by -specifying their name. Instead of having to explicitly specify all of the useful -layers, the SDK allows you to request the `VK_LAYER_LUNARG_standard_validation` -layer that implicitly enables a whole range of useful diagnostics layers. - -Let's first add two configuration variables to the program to specify the layers -to enable and whether to enable them or not. I've chosen to base that value on -whether the program is being compiled in debug mode or not. The `NDEBUG` macro -is part of the C++ standard and means "not debug". - -```c++ -const int WIDTH = 800; -const int HEIGHT = 600; - -const std::vector validationLayers = { - "VK_LAYER_LUNARG_standard_validation" -}; - -#ifdef NDEBUG - const bool enableValidationLayers = false; -#else - const bool enableValidationLayers = true; -#endif -``` - -We'll add a new function `checkValidationLayerSupport` that checks if all of -the requested layers are available. First list all of the available extensions -using the `vkEnumerateInstanceLayerProperties` function. Its usage is identical -to that of `vkEnumerateInstanceExtensionProperties` which was discussed in the -instance creation chapter. - -```c++ -bool checkValidationLayerSupport() { - uint32_t layerCount; - vkEnumerateInstanceLayerProperties(&layerCount, nullptr); - - std::vector availableLayers(layerCount); - vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); - - return false; -} -``` - -Next, check if all of the layers in `validationLayers` exist in the -`availableLayers` list. You may need to include `` for `strcmp`. - -```c++ -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; -``` - -We can now use this function in `createInstance`: - -```c++ -void createInstance() { - if (enableValidationLayers && !checkValidationLayerSupport()) { - throw std::runtime_error("validation layers requested, but not available!"); - } - - ... -} -``` - -Now run the program in debug mode and ensure that the error does not occur. If -it does, then make sure you have properly installed the Vulkan SDK. If none or -very few layers are being reported, then you may be dealing with -[this issue](https://vulkan.lunarg.com/app/issues/578e8c8d5698c020d71580fc) -(requires a LunarG account to view). See that page for help with fixing it. - -Finally, modify the `VkInstanceCreateInfo` struct instantiation to include the -validation layer names if they are enabled: - -```c++ -if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); - createInfo.ppEnabledLayerNames = validationLayers.data(); -} else { - createInfo.enabledLayerCount = 0; -} -``` - -If the check was successful then `vkCreateInstance` should not ever return a -`VK_ERROR_LAYER_NOT_PRESENT` error, but you should run the program to make sure. - -## Message callback - -Unfortunately just enabling the layers doesn't help much, because they currently -have no way to relay the debug messages back to our program. To receive those -messages we have to set up a callback, which requires the `VK_EXT_debug_report` -extension. - -We'll first create a `getRequiredExtensions` function that will return the -required list of extensions based on whether validation layers are enabled or -not: - -```c++ -std::vector getRequiredExtensions() { - std::vector extensions; - - unsigned int glfwExtensionCount = 0; - const char** glfwExtensions; - glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - for (unsigned int i = 0; i < glfwExtensionCount; i++) { - extensions.push_back(glfwExtensions[i]); - } - - if (enableValidationLayers) { - extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); - } - - return extensions; -} -``` - -The extensions specified by GLFW are always required, but the debug report -extension is conditionally added. Note that I've used the -`VK_EXT_DEBUG_REPORT_EXTENSION_NAME` macro here which is equal to the literal -string "VK_EXT_debug_report". Using this macro lets you avoid typos. - -We can now use this function in `createInstance`: - -```c++ -auto extensions = getRequiredExtensions(); -createInfo.enabledExtensionCount = extensions.size(); -createInfo.ppEnabledExtensionNames = extensions.data(); -``` - -Run the program to make sure you don't receive a -`VK_ERROR_EXTENSION_NOT_PRESENT` error. We don't really need to check for the -existence of this extension, because it should be implied by the availability of -the validation layers. - -Now let's see what a callback function looks like. Add a new static member -function called `debugCallback` with the `PFN_vkDebugReportCallbackEXT` -prototype. The `VKAPI_ATTR` and `VKAPI_CALL` ensure that the function has the -right signature for Vulkan to call it. - -```c++ -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; -} -``` - -The first parameter specifies the type of message, which can be a combination of -any of the following bit flags: - -* `VK_DEBUG_REPORT_INFORMATION_BIT_EXT` -* `VK_DEBUG_REPORT_WARNING_BIT_EXT` -* `VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT` -* `VK_DEBUG_REPORT_ERROR_BIT_EXT` -* `VK_DEBUG_REPORT_DEBUG_BIT_EXT` - -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`. - -The `msg` parameter contains the pointer to the message itself. Finally, there's -a `userData` parameter to pass your own data to the callback. - -All that remains now is telling Vulkan about the callback function. Perhaps -somewhat surprisingly, even the debug callback in Vulkan is managed with a -handle that needs to be explicitly created and destroyed. Add a class member for -this handle right under `instance`: - -```c++ -VkDebugReportCallbackEXT callback; -``` - -Now add a function `setupDebugCallback` to be called from `initVulkan` right -after `createInstance`: - -```c++ -void initVulkan() { - createInstance(); - setupDebugCallback(); -} - -void setupDebugCallback() { - if (!enableValidationLayers) return; - -} -``` - -We'll need to fill in a structure with details about the callback: - -```c++ -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; -``` - -The `flags` field allows you to filter which types of messages you would like to -receive. The `pfnCallback` field specifies the pointer to the callback function. -You can optionally pass a pointer to the `pUserData` field which will be passed -along to the callback function via the `userData` parameter. You could use this -to pass a pointer to the `HelloTriangleApplication` class, for example. - -This struct should be passed to the `vkCreateDebugReportCallbackEXT` function to -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. - -```c++ -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; - } -} -``` - -The `vkGetInstanceProcAddr` function will return `nullptr` if the function -couldn't be loaded. We can now call this function to create the extension -object if it's available: - -```c++ -if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, &callback) != VK_SUCCESS) { - throw std::runtime_error("failed to set up debug callback!"); -} -``` - -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`: - -```c++ -void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT callback, const VkAllocationCallbacks* pAllocator) { - auto func = (PFN_vkDestroyDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT"); - if (func != nullptr) { - func(instance, callback, pAllocator); - } -} -``` - -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: - -```c++ -VDeleter callback{instance, DestroyDebugReportCallbackEXT}; -``` - -Make sure to change the line that creates the debug report callback to use the -`replace()` method of the wrapper: - -```c++ -if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { -``` - -When you run the program again you'll see that the error message has -disappeared. If you want to see which call triggered a message, you can add a -breakpoint to the message callback and look at the stack trace. - -## Configuration - -There are a lot more settings for the behavior of validation layers than just -the flags specified in the `VkDebugReportCallbackCreateInfoEXT` struct. Browse -to the Vulkan SDK and go to the `Config` directory. There you will find a -`vk_layer_settings.txt` file that explains how to configure the layers. - -To configure the layer settings for your own application, copy the file to the -`Debug` and `Release` directories of your project and follow the instructions to -set the desired behavior. However, for the remainder of this tutorial I'll -assume that you're using the default settings. - -Throughout this tutorial I'll be making a couple of intentional mistakes to show -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) 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 deleted file mode 100644 index 9252153e..00000000 --- a/03_Drawing_a_triangle/03_Drawing/02_Rendering_and_presentation.md +++ /dev/null @@ -1,360 +0,0 @@ -## Setup - -This is the chapter where everything is going to come together. We're going to -write the `drawFrame` function that will be called from the main loop to put the -triangle on the screen. Create the function and call it from `mainLoop`: - -```c++ -void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - drawFrame(); - } - - glfwDestroyWindow(window); -} - -... - -void drawFrame() { - -} -``` - -## Synchronization - -The `drawFrame` function will perform the following operations: - -* Acquire an image from the swap chain -* Execute the command buffer with that image as attachment in the framebuffer -* Return the image to the swap chain for presentation - -Each of these events is set in motion using a single function call, but they are -executed asynchronously. The function calls will return before the operations -are actually finished and the order of execution is also undefined. That is -unfortunate, because each of the operations depends on the previous one -finishing. - -There are two ways of synchronizing swap chain events: fences and semaphores. -They're both objects that can be used for coordinating operations by having one -operation signal and another operation wait for a fence or semaphore to go from -the unsignaled to signaled state. - -The difference is that the state of fences can be accessed from your program -using calls like `vkWaitForFences` and semaphores cannot be. Fences are mainly -designed to synchronize your application itself with rendering operation, -whereas semaphores are used to synchronize operations within or across command -queues. We want to synchronize the queue operations of draw commands and -presentation, which makes semaphores the best fit. - -## Semaphores - -We'll need one semaphore to signal that an image has been acquired and is ready -for rendering, and another one to signal that rendering has finished and -presentation can happen. Create two class members to store these semaphore -objects: - -```c++ -VDeleter imageAvailableSemaphore{device, vkDestroySemaphore}; -VDeleter renderFinishedSemaphore{device, vkDestroySemaphore}; -``` - -To create the semaphores, we'll add the last `create` function for this part of -the tutorial: `createSemaphores`: - -```c++ -void initVulkan() { - createInstance(); - setupDebugCallback(); - createSurface(); - pickPhysicalDevice(); - createLogicalDevice(); - createSwapChain(); - createImageViews(); - createRenderPass(); - createGraphicsPipeline(); - createFramebuffers(); - createCommandPool(); - createCommandBuffers(); - createSemaphores(); -} - -... - -void createSemaphores() { - -} -``` - -Creating semaphores requires filling in the `VkSemaphoreCreateInfo`, but in the -current version of the API it doesn't actually have any required fields besides -`sType`: - -```c++ -void createSemaphores() { - VkSemaphoreCreateInfo semaphoreInfo = {}; - semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; -} -``` - -Future versions of the Vulkan API or extensions may add functionality for the -`flags` and `pNext` parameters like it does for the other structures. Creating -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) { - - throw std::runtime_error("failed to create semaphores!"); -} -``` - -## 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 -extension feature, so we must use a function with the `vk*KHR` naming -convention: - -```c++ -void drawFrame() { - uint32_t imageIndex; - vkAcquireNextImageKHR(device, swapChain, std::numeric_limits::max(), imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex); -} -``` - -The first two parameters of `vkAcquireNextImageKHR` are the logical device and -the swap chain from which we wish to acquire an image. The third parameter -specifies a timeout in nanoseconds for an image to become available. Using the -maximum value of a 64 bit unsigned integer disables the timeout. - -The next two parameters specify synchronization objects that are to be signaled -when the presentation engine is finished using the image. That's the point in -time where we can start drawing to it. It is possible to specify a semaphore, -fence or both. We're going to use our `imageAvailableSemaphore` for that purpose -here. - -The last parameter specifies a variable to output the index of the swap chain -image that has become available. The index refers to the `VkImage` in our -`swapChainImages` array. We're going to use that index to pick the right command -buffer. - -## Submitting the command buffer - -Queue submission and synchronization is configured through parameters in the -`VkSubmitInfo` structure. - -```c++ -VkSubmitInfo submitInfo = {}; -submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; - -VkSemaphore waitSemaphores[] = {imageAvailableSemaphore}; -VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; -submitInfo.waitSemaphoreCount = 1; -submitInfo.pWaitSemaphores = waitSemaphores; -submitInfo.pWaitDstStageMask = waitStages; -``` - -The first three parameters specify which semaphores to wait on before execution -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` -array corresponds to the semaphore with the same index in `pWaitSemaphores`. - -```c++ -submitInfo.commandBufferCount = 1; -submitInfo.pCommandBuffers = &commandBuffers[imageIndex]; -``` - -The next two parameters specify which command buffers to actually submit for -execution. As mentioned earlier, we should submit the command buffer that binds -the swap chain image we just acquired as color attachment. - -```c++ -VkSemaphore signalSemaphores[] = {renderFinishedSemaphore}; -submitInfo.signalSemaphoreCount = 1; -submitInfo.pSignalSemaphores = signalSemaphores; -``` - -The `signalSemaphoreCount` and `pSignalSemaphores` parameters specify which -semaphores to signal once the command buffer(s) have finished execution. In our -case we're using the `renderFinishedSemaphore` for that purpose. - -```c++ -if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { - throw std::runtime_error("failed to submit draw command buffer!"); -} -``` - -We can now submit the command buffer to the graphics queue using -`vkQueueSubmit`. The function takes an array of `VkSubmitInfo` structures as -argument for efficiency when the workload is much larger. The last parameter -references an optional fence that will be signaled when the command buffers -finish execution. We're using semaphores for synchronization, so we'll just pass -a `VK_NULL_HANDLE`. - -## Subpass dependencies - -Remember that the subpasses in a render pass automatically take care of image -layout transitions. These transitions are controlled by *subpass dependencies*, -which specify memory and execution dependencies between subpasses. We have only -a single subpass right now, but the operations right before and right after this -subpass also count as implicit "subpasses". - -There are two built-in dependencies that take care of the transition at the -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. - -Subpass dependencies are specified in `VkSubpassDependency` structs. Go to the -`createRenderPass` function and add one: - -```c++ -VkSubpassDependency dependency = {}; -dependency.srcSubpass = VK_SUBPASS_EXTERNAL; -dependency.dstSubpass = 0; -``` - -The first two fields specify the indices of the dependency and the dependent -subpass. The special value `VK_SUBPASS_EXTERNAL` refers to the implicit subpass -before or after the render pass depending on whether it is specified in -`srcSubpass` or `dstSubpass`. The index `0` refers to our subpass, which is the -first and only one. The `dstSubpass` must always be higher than `srcSubpass` to -prevent cycles in the dependency graph. - -```c++ -dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; -dependency.srcAccessMask = 0; -``` - -The next two fields specify the operations to wait on and the stages in which -these operations occur. We need to wait for the swap chain to finish reading -from the image before we can access it. This can be accomplished by waiting on -the color attachment output stage itself. - -```c++ -dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; -dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; -``` - -The operations that should wait on this are in the color attachment stage and -involve the reading and writing of the color attachment. These settings will -prevent the transition from happening until it's actually necessary (and -allowed): when we want to start writing colors to it. - -```c++ -renderPassInfo.dependencyCount = 1; -renderPassInfo.pDependencies = &dependency; -``` - -The `VkRenderPassCreateInfo` struct has two fields to specify an array of -dependencies. - -## Presentation - -The last step of drawing a frame is submitting the result back to the swap chain -to have it eventually show up on the screen. Presentation is configured through -a `VkPresentInfoKHR` structure at the end of the `drawFrame` function. - -```c++ -VkPresentInfoKHR presentInfo = {}; -presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; - -presentInfo.waitSemaphoreCount = 1; -presentInfo.pWaitSemaphores = signalSemaphores; -``` - -The first two parameters specify which semaphores to wait on before presentation -can happen, just like `VkSubmitInfo`. - -```c++ -VkSwapchainKHR swapChains[] = {swapChain}; -presentInfo.swapchainCount = 1; -presentInfo.pSwapchains = swapChains; -presentInfo.pImageIndices = &imageIndex; -``` - -The next two parameters specify the swap chains to present images to and the -index of the image for each swap chain. This will almost always be a single one. - -```c++ -presentInfo.pResults = nullptr; // Optional -``` - -There is one last optional parameter called `pResults`. It allows you to specify -an array of `VkResult` values to check for every individual swap chain if -presentation was successful. It's not necessary if you're only using a single -swap chain, because you can simply use the return value of the present function. - -```c++ -vkQueuePresentKHR(presentQueue, &presentInfo); -``` - -The `vkQueuePresentKHR` function submits the request to present an image to the -swap chain. We'll add error handling for both `vkAcquireNextImageKHR` and -`vkQueuePresentKHR` in the next chapter, because their failure does not -necessarily mean that the program should terminate, unlike the functions we've -seen so far. - -If you did everything correctly up to this point, then you should now see -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: - -![](/images/semaphore_in_use.png) - -Remember that all of the operations in `drawFrame` are asynchronous. That means -that when we exit the loop in `mainLoop`, drawing and presentation operations -may still be going on. Cleaning up resources while that is happening is a bad -idea. - -To fix that problem, we should wait for the logical device to finish operations -before exiting `mainLoop` and destroying the window: - -```c++ -void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - drawFrame(); - } - - vkDeviceWaitIdle(device); - - glfwDestroyWindow(window); -} -``` - -You can also wait for operations in a specific command queue to be finished with -`vkQueueWaitIdle`. These functions can be used as a very rudimentary way to -perform synchronization. You'll see that the program now exits without problems -when closing the window. - -## Conclusion - -About 800 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 -now to reread the code and build a mental model of the purpose of all of the -Vulkan objects in the program and how they relate to each other. We'll be -building on top of that knowledge to extend the functionality of the program -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) diff --git a/03_Drawing_a_triangle/04_Swap_chain_recreation.md b/03_Drawing_a_triangle/04_Swap_chain_recreation.md deleted file mode 100644 index 322dc929..00000000 --- a/03_Drawing_a_triangle/04_Swap_chain_recreation.md +++ /dev/null @@ -1,196 +0,0 @@ -## Introduction - -The application we have now successfully draws a triangle, but there are some -circumstances that it isn't handling properly yet. It is possible for the window -surface to change such that the swap chain is no longer compatible with it. One -of the reasons that could cause this to happen is the size of the window -changing. We have to catch these events and recreate the swap chain. - -## Recreating the swap chain - -Create a new `recreateSwapChain` function that calls `createSwapChain` and all -of the creation functions for the objects that depend on the swap chain or the -window size. - -```c++ -void recreateSwapChain() { - vkDeviceWaitIdle(device); - - createSwapChain(); - createImageViews(); - createRenderPass(); - createGraphicsPipeline(); - createFramebuffers(); - createCommandBuffers(); -} -``` - -We first call `vkDeviceWaitIdle`, because just like in the last chapter, we -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. - -```c++ -VkSwapchainKHR oldSwapChain = swapChain; -createInfo.oldSwapchain = oldSwapChain; - -VkSwapchainKHR newSwapChain; -if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &newSwapChain) != VK_SUCCESS) { - throw std::runtime_error("failed to create swap chain!"); -} - -swapChain = newSwapChain; -``` - -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. - -```c++ -swapChain = newSwapChain; -``` - -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. - -```c++ -if (commandBuffers.size() > 0) { - vkFreeCommandBuffers(device, commandPool, commandBuffers.size(), commandBuffers.data()); -} - -commandBuffers.resize(swapChainFramebuffers.size()); -``` - -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! - -## Window resizing - -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(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - - glfwSetWindowUserPointer(window, this); - glfwSetWindowSizeCallback(window, HelloTriangleApplication::onWindowResized); -} - -... - -static void onWindowResized(GLFWwindow* window, int width, int height) { - if (width == 0 || height == 0) return; - - HelloTriangleApplication* app = reinterpret_cast(glfwGetWindowUserPointer(window)); - app->recreateSwapChain(); -} -``` - -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. - -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`: - -```c++ -int width, height; -glfwGetWindowSize(window, &width, &height); - -VkExtent2D actualExtent = {width, height}; -``` - -## 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 -`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. -* `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. - -```c++ -VkResult result = vkAcquireNextImageKHR(device, swapChain, std::numeric_limits::max(), imageAvailableSemaphore, 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!"); -} -``` - -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. - -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. - -```c++ -result = vkQueuePresentKHR(presentQueue, &presentInfo); - -if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { - recreateSwapChain(); -} else if (result != VK_SUCCESS) { - throw std::runtime_error("failed to present swap chain image!"); -} -``` - -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. - -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 diff --git a/05_Uniform_buffers/01_Descriptor_pool_and_sets.md b/05_Uniform_buffers/01_Descriptor_pool_and_sets.md deleted file mode 100644 index 119bd8f7..00000000 --- a/05_Uniform_buffers/01_Descriptor_pool_and_sets.md +++ /dev/null @@ -1,245 +0,0 @@ -## 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 -uniform buffer descriptor. - -## Descriptor pool - -Descriptor sets can't be created directly, they must be allocated from a pool -like command buffers. The equivalent for descriptor sets is unsurprisingly -called a *descriptor pool*. We'll write a new function `createDescriptorPool` -to set it up. - -```c++ -void initVulkan() { - ... - createUniformBuffer(); - createDescriptorPool(); - ... -} - -... - -void createDescriptorPool() { - -} -``` - -We first need to describe which descriptor types our descriptor sets are going -to contain and how many of them, using `VkDescriptorPoolSize` structures. - -```c++ -VkDescriptorPoolSize poolSize = {}; -poolSize.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; -poolSize.descriptorCount = 1; -``` - -We only have a single descriptor right now with the uniform buffer type. This -pool size structure is referenced by the main `VkDescriptorPoolCreateInfo`: - -```c++ -VkDescriptorPoolCreateInfo poolInfo = {}; -poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; -poolInfo.poolSizeCount = 1; -poolInfo.pPoolSizes = &poolSize; -``` - -We also need to specify the maximum number of descriptor sets that will be -allocated: - -```c++ -poolInfo.maxSets = 1; -``` - -The structure has an optional flag similar to command pools that determines if -individual descriptor sets can be freed or not: -`VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT`. We're not going to touch -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}; - -... - -if (vkCreateDescriptorPool(device, &poolInfo, nullptr, descriptorPool.replace()) != 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. - -## Descriptor set - -We can now allocate the descriptor set itself. Add a `createDescriptorSet` -function for that purpose: - -```c++ -void initVulkan() { - ... - createDescriptorPool(); - createDescriptorSet(); - ... -} - -... - -void createDescriptorSet() { - -} -``` - -A descriptor set allocation is described with a `VkDescriptorSetAllocateInfo` -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}; -VkDescriptorSetAllocateInfo allocInfo = {}; -allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; -allocInfo.descriptorPool = descriptorPool; -allocInfo.descriptorSetCount = 1; -allocInfo.pSetLayouts = layouts; -``` - -Add a class member to hold the descriptor set handle and allocate it with -`vkAllocateDescriptorSets`: - -```c++ -VDeleter descriptorPool{device, vkDestroyDescriptorPool}; -VkDescriptorSet descriptorSet; - -... - -if (vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet) != VK_SUCCESS) { - throw std::runtime_error("failed to allocate descriptor set!"); -} -``` - -You don't need to use a deleter for 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 -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 -descriptor, are configured with a `VkDescriptorBufferInfo` struct. This -structure specifies the buffer and the region within it that contains the data -for the descriptor: - -```c++ -VkDescriptorBufferInfo bufferInfo = {}; -bufferInfo.buffer = uniformBuffer; -bufferInfo.offset = 0; -bufferInfo.range = sizeof(UniformBufferObject); -``` - -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.dstBinding = 0; -descriptorWrite.dstArrayElement = 0; -``` - -The first two fields specify the descriptor set to update and the binding. We -gave our uniform buffer binding index `0`. Remember that descriptors can be -arrays, so we also need to specify the first index in the array that we want to -update. We're not using an array, so the index is simply `0`. - -```c++ -descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; -descriptorWrite.descriptorCount = 1; -``` - -We need to specify the type of descriptor again. It's possible to update -multiple descriptors at once in an array, starting at index `dstArrayElement`. -The `descriptorCount` field specifies how many array elements you want to -update. - -```c++ -descriptorWrite.pBufferInfo = &bufferInfo; -descriptorWrite.pImageInfo = nullptr; // Optional -descriptorWrite.pTexelBufferView = nullptr; // Optional -``` - -The last field references an array with `descriptorCount` structs that actually -configure the descriptors. It depends on the type of descriptor which one of the -three you actually need to use. The `pBufferInfo` field is used for descriptors -that refer to buffer data, `pImageInfo` is used for descriptors that refer to -image data, and `pTexelBufferView` is used for descriptors that refer to buffer -views. Our descriptor is based on buffers, so we're using `pBufferInfo`. - -```c++ -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. - -## Using a descriptor set - -We now need to update the `createCommandBuffers` function to actually bind the -descriptor set to the descriptors in the shader with `cmdBindDescriptorSets`: - -```c++ -vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr); -``` - -Unlike vertex and index buffers, descriptor sets are not unique to graphics -pipelines. Therefore we need to specify if we want to bind descriptor sets to -the graphics or compute pipeline. The next parameter is the layout that the -descriptors are based on. The next three parameters specify the index of the -first descriptor set, the number of sets to bind, and the array of sets to bind. -We'll get back to this in a moment. The last two parameters specify an array of -offsets that are used for dynamic descriptors. We'll look at these in a future -chapter. - -If you run your program now, then you'll notice that unfortunately nothing is -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 -this: - -```c++ -rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; -rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; -``` - -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 -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 -each descriptor set when creating the pipeline layout. Shaders can then -reference specific descriptor sets like this: - -```c++ -layout(set = 0, binding = 0) uniform UniformBufferObject { ... } -``` - -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) diff --git a/README.md b/README.md index 7613a09f..be8a300b 100644 --- a/README.md +++ b/README.md @@ -2,23 +2,40 @@ 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 -patch is based on commit `d45ccff`. +The actual site runs daux.io with a custom theme and a few modifications (https://github.com/Overv/daux.io) and this is built into a [Docker image](https://hub.docker.com/r/overv/vulkan-tutorial). Use issues and pull requests to provide feedback related to the website. If you 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 ([English](https://raw.githubusercontent.com/Overv/VulkanTutorial/master/ebook/Vulkan%20Tutorial%20en.epub), [French](https://raw.githubusercontent.com/Overv/VulkanTutorial/master/ebook/Vulkan%20Tutorial%20fr.epub)) +* PDF ([English](https://raw.githubusercontent.com/Overv/VulkanTutorial/master/ebook/Vulkan%20Tutorial%20en.pdf), [French](https://raw.githubusercontent.com/Overv/VulkanTutorial/master/ebook/Vulkan%20Tutorial%20fr.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 (tested with version 1.0.2) +* `pandoc`: Building a PDF and EPUB from the Markdown code (tested with version 2.13) + +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. @@ -53,8 +70,8 @@ For either of these options, you'll need php and a patch'ed daux. ### Clone, patch, and rebuild daux -1. Clone [daux](https://github.com/justinwalsh/daux.io) - * `git clone https://github.com/justinwalsh/daux.io.git` +1. Clone [daux](https://github.com/dauxio/daux.io) + * `git clone https://github.com/dauxio/daux.io.git` 2. Make a new branch at the older revision that the VulkanTutorial patch is against: * `git checkout d45ccff -b vtpatch` @@ -74,7 +91,7 @@ For either of these options, you'll need php and a patch'ed daux. ### Using Daux to serve rendered files on the fly Once you've completed the above, follow the instructions on the daux site -for how to [run daux using a web server](https://github.com/justinwalsh/daux.io/blob/master/README.md#running-remotely). +for how to [run daux using a web server](https://github.com/dauxio/daux.io/blob/master/README.md#running-remotely). As a simple option considering you have php installed, you can also use php's built in development web server if you just need to locally see what things @@ -107,7 +124,7 @@ necessary. Now with the above done, we can generate the static files. Asuming the daux.io and VulkanTutorial directories are next to each other, go into the `daux.io` directory and run a command similar to: -`php generate -s ../VulkanTutorial -d ../VulkanTutorial\out`. +`php generate -s ../VulkanTutorial -d ../VulkanTutorial/out`. `-s` tells it where to find the documentation, while `-d` tells it where to put the generated files. diff --git a/build_ebook.py b/build_ebook.py new file mode 100644 index 00000000..c94ffbf7 --- /dev/null +++ b/build_ebook.py @@ -0,0 +1,112 @@ +import subprocess +import datetime +import os +import re + + +def create_ebook(path): + + name_path = path + print('\n Creating \"' + name_path + '\" ebook') + # Recursively gather all markdown files in the right order + markdownFiles = [] + + for root, subdirs, files in os.walk(name_path): + 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" + # "02_Development_environment.md" -> "02_Development_environment" + title = fn.split('.')[0] + # "02_Development_environment" -> "02 Development environment" + title = title.replace('_', ' ') + # "02 Development environment" -> "Development environment" + title = ' '.join(title.split(' ')[1:]) + + 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) + + # 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 ' + name_path + '.pdf', '--pdf-engine=xelatex']) + + print('building epub...') + + subprocess.check_output( + ['pandoc', 'ebook.md', '--toc', '-o', 'ebook/Vulkan Tutorial ' + name_path + '.epub', '--epub-cover-image=ebook/cover.png']) + + # Clean up + os.remove('ebook.md') + + +# 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', '--export-filename=images/' + + parts[0] + '.png', 'images/' + fn], stderr=subprocess.STDOUT) + generatedPngs.append('images/' + parts[0] + '.png') + +create_ebook('en') +create_ebook('fr') + +for fn in generatedPngs: + os.remove(fn) diff --git a/code/.gitignore b/code/.gitignore new file mode 100644 index 00000000..01e00f3a --- /dev/null +++ b/code/.gitignore @@ -0,0 +1 @@ +CMakeLists.txt.user diff --git a/code/00_base_code.cpp b/code/00_base_code.cpp new file mode 100644 index 00000000..e2aba60d --- /dev/null +++ b/code/00_base_code.cpp @@ -0,0 +1,60 @@ +#define GLFW_INCLUDE_VULKAN +#include + +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t 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 51% rename from code/instance_creation.cpp rename to code/01_instance_creation.cpp index 20c4a082..2bad54e7 100644 --- a/code/instance_creation.cpp +++ b/code/01_instance_creation.cpp @@ -3,68 +3,10 @@ #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; - } -}; +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; class HelloTriangleApplication { public: @@ -72,12 +14,13 @@ class HelloTriangleApplication { 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); @@ -103,7 +50,7 @@ class HelloTriangleApplication { } void createInstance() { - VkApplicationInfo appInfo = {}; + VkApplicationInfo appInfo{}; appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; appInfo.pApplicationName = "Hello Triangle"; appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); @@ -111,11 +58,11 @@ class HelloTriangleApplication { appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); appInfo.apiVersion = VK_API_VERSION_1_0; - VkInstanceCreateInfo createInfo = {}; + VkInstanceCreateInfo createInfo{}; 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/02_validation_layers.cpp b/code/02_validation_layers.cpp new file mode 100644 index 00000000..8e7fac42 --- /dev/null +++ b/code/02_validation_layers.cpp @@ -0,0 +1,201 @@ +#define GLFW_INCLUDE_VULKAN +#include + +#include +#include +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +class HelloTriangleApplication { +public: + void run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + } + } + + void cleanup() { + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + 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(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; + } else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + 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_UTILS_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 VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << 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/physical_device_selection.cpp b/code/03_physical_device_selection.cpp similarity index 56% rename from code/physical_device_selection.cpp rename to code/03_physical_device_selection.cpp index 5c1d657b..3ccba31b 100644 --- a/code/physical_device_selection.cpp +++ b/code/03_physical_device_selection.cpp @@ -3,15 +3,16 @@ #include #include -#include #include #include +#include +#include -const int WIDTH = 800; -const int HEIGHT = 600; +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; const std::vector validationLayers = { - "VK_LAYER_LUNARG_standard_validation" + "VK_LAYER_KHRONOS_validation" }; #ifdef NDEBUG @@ -20,85 +21,27 @@ const bool enableValidationLayers = false; const bool enableValidationLayers = true; #endif -VkResult CreateDebugReportCallbackEXT(VkInstance instance, const VkDebugReportCallbackCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugReportCallbackEXT* pCallback) { - auto func = (PFN_vkCreateDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT"); +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); if (func != nullptr) { - return func(instance, pCreateInfo, pAllocator, pCallback); + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); } else { return VK_ERROR_EXTENSION_NOT_PRESENT; } } -void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT callback, const VkAllocationCallbacks* pAllocator) { - auto func = (PFN_vkDestroyDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT"); +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); if (func != nullptr) { - func(instance, callback, pAllocator); + func(instance, debugMessenger, pAllocator); } } -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; + std::optional graphicsFamily; bool isComplete() { - return graphicsFamily >= 0; + return graphicsFamily.has_value(); } }; @@ -108,14 +51,14 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; @@ -130,7 +73,7 @@ class HelloTriangleApplication { void initVulkan() { createInstance(); - setupDebugCallback(); + setupDebugMessenger(); pickPhysicalDevice(); } @@ -138,6 +81,14 @@ class HelloTriangleApplication { while (!glfwWindowShouldClose(window)) { glfwPollEvents(); } + } + + void cleanup() { + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroyInstance(instance, nullptr); glfwDestroyWindow(window); @@ -149,7 +100,7 @@ class HelloTriangleApplication { throw std::runtime_error("validation layers requested, but not available!"); } - VkApplicationInfo appInfo = {}; + VkApplicationInfo appInfo{}; appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; appInfo.pApplicationName = "Hello Triangle"; appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); @@ -157,36 +108,48 @@ class HelloTriangleApplication { appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); appInfo.apiVersion = VK_API_VERSION_1_0; - VkInstanceCreateInfo createInfo = {}; + VkInstanceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; } else { createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } - void setupDebugCallback() { + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { 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; + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { - throw std::runtime_error("failed to set up debug callback!"); + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); } } @@ -230,7 +193,7 @@ class HelloTriangleApplication { int i = 0; for (const auto& queueFamily : queueFamilies) { - if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { indices.graphicsFamily = i; } @@ -245,18 +208,14 @@ 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); + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); } return extensions; @@ -287,8 +246,8 @@ class HelloTriangleApplication { return true; } - 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; + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; return VK_FALSE; } @@ -299,10 +258,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 59% rename from code/logical_device.cpp rename to code/04_logical_device.cpp index 47f5e896..7fe1d157 100644 --- a/code/logical_device.cpp +++ b/code/04_logical_device.cpp @@ -3,15 +3,16 @@ #include #include -#include #include #include +#include +#include -const int WIDTH = 800; -const int HEIGHT = 600; +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; const std::vector validationLayers = { - "VK_LAYER_LUNARG_standard_validation" + "VK_LAYER_KHRONOS_validation" }; #ifdef NDEBUG @@ -20,85 +21,27 @@ const bool enableValidationLayers = false; const bool enableValidationLayers = true; #endif -VkResult CreateDebugReportCallbackEXT(VkInstance instance, const VkDebugReportCallbackCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugReportCallbackEXT* pCallback) { - auto func = (PFN_vkCreateDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT"); +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); if (func != nullptr) { - return func(instance, pCreateInfo, pAllocator, pCallback); + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); } else { return VK_ERROR_EXTENSION_NOT_PRESENT; } } -void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT callback, const VkAllocationCallbacks* pAllocator) { - auto func = (PFN_vkDestroyDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT"); +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); if (func != nullptr) { - func(instance, callback, pAllocator); + func(instance, debugMessenger, pAllocator); } } -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; + std::optional graphicsFamily; bool isComplete() { - return graphicsFamily >= 0; + return graphicsFamily.has_value(); } }; @@ -108,17 +51,17 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; - VDeleter device{vkDestroyDevice}; + VkDevice device; VkQueue graphicsQueue; @@ -133,7 +76,7 @@ class HelloTriangleApplication { void initVulkan() { createInstance(); - setupDebugCallback(); + setupDebugMessenger(); pickPhysicalDevice(); createLogicalDevice(); } @@ -142,6 +85,16 @@ class HelloTriangleApplication { while (!glfwWindowShouldClose(window)) { glfwPollEvents(); } + } + + void cleanup() { + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroyInstance(instance, nullptr); glfwDestroyWindow(window); @@ -153,7 +106,7 @@ class HelloTriangleApplication { throw std::runtime_error("validation layers requested, but not available!"); } - VkApplicationInfo appInfo = {}; + VkApplicationInfo appInfo{}; appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; appInfo.pApplicationName = "Hello Triangle"; appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); @@ -161,36 +114,48 @@ class HelloTriangleApplication { appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); appInfo.apiVersion = VK_API_VERSION_1_0; - VkInstanceCreateInfo createInfo = {}; + VkInstanceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; } else { createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } - void setupDebugCallback() { + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { 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; + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { - throw std::runtime_error("failed to set up debug callback!"); + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); } } @@ -220,17 +185,17 @@ class HelloTriangleApplication { void createLogicalDevice() { QueueFamilyIndices indices = findQueueFamilies(physicalDevice); - VkDeviceQueueCreateInfo queueCreateInfo = {}; + VkDeviceQueueCreateInfo queueCreateInfo{}; queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; - queueCreateInfo.queueFamilyIndex = indices.graphicsFamily; + queueCreateInfo.queueFamilyIndex = indices.graphicsFamily.value(); queueCreateInfo.queueCount = 1; float queuePriority = 1.0f; queueCreateInfo.pQueuePriorities = &queuePriority; - VkPhysicalDeviceFeatures deviceFeatures = {}; + VkPhysicalDeviceFeatures deviceFeatures{}; - VkDeviceCreateInfo createInfo = {}; + VkDeviceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; createInfo.pQueueCreateInfos = &queueCreateInfo; @@ -239,19 +204,19 @@ 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!"); } - vkGetDeviceQueue(device, indices.graphicsFamily, 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); } bool isDeviceSuitable(VkPhysicalDevice device) { @@ -271,7 +236,7 @@ class HelloTriangleApplication { int i = 0; for (const auto& queueFamily : queueFamilies) { - if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { indices.graphicsFamily = i; } @@ -286,18 +251,14 @@ 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); + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); } return extensions; @@ -328,8 +289,8 @@ class HelloTriangleApplication { return true; } - 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; + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; return VK_FALSE; } @@ -340,10 +301,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 58% rename from code/window_surface.cpp rename to code/05_window_surface.cpp index de4a36d4..bdfcbd34 100644 --- a/code/window_surface.cpp +++ b/code/05_window_surface.cpp @@ -3,16 +3,17 @@ #include #include -#include #include #include +#include +#include #include -const int WIDTH = 800; -const int HEIGHT = 600; +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; const std::vector validationLayers = { - "VK_LAYER_LUNARG_standard_validation" + "VK_LAYER_KHRONOS_validation" }; #ifdef NDEBUG @@ -21,86 +22,28 @@ const bool enableValidationLayers = false; const bool enableValidationLayers = true; #endif -VkResult CreateDebugReportCallbackEXT(VkInstance instance, const VkDebugReportCallbackCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugReportCallbackEXT* pCallback) { - auto func = (PFN_vkCreateDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT"); +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); if (func != nullptr) { - return func(instance, pCreateInfo, pAllocator, pCallback); + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); } else { return VK_ERROR_EXTENSION_NOT_PRESENT; } } -void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT callback, const VkAllocationCallbacks* pAllocator) { - auto func = (PFN_vkDestroyDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT"); +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); if (func != nullptr) { - func(instance, callback, pAllocator); + func(instance, debugMessenger, pAllocator); } } -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; + std::optional graphicsFamily; + std::optional presentFamily; bool isComplete() { - return graphicsFamily >= 0 && presentFamily >= 0; + return graphicsFamily.has_value() && presentFamily.has_value(); } }; @@ -110,17 +53,18 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + VkSurfaceKHR surface; VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; - VDeleter device{vkDestroyDevice}; + VkDevice device; VkQueue graphicsQueue; VkQueue presentQueue; @@ -136,7 +80,7 @@ class HelloTriangleApplication { void initVulkan() { createInstance(); - setupDebugCallback(); + setupDebugMessenger(); createSurface(); pickPhysicalDevice(); createLogicalDevice(); @@ -146,6 +90,17 @@ class HelloTriangleApplication { while (!glfwWindowShouldClose(window)) { glfwPollEvents(); } + } + + void cleanup() { + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); glfwDestroyWindow(window); @@ -157,7 +112,7 @@ class HelloTriangleApplication { throw std::runtime_error("validation layers requested, but not available!"); } - VkApplicationInfo appInfo = {}; + VkApplicationInfo appInfo{}; appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; appInfo.pApplicationName = "Hello Triangle"; appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); @@ -165,41 +120,53 @@ class HelloTriangleApplication { appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); appInfo.apiVersion = VK_API_VERSION_1_0; - VkInstanceCreateInfo createInfo = {}; + VkInstanceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; } else { createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } - void setupDebugCallback() { + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { 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; + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { - throw std::runtime_error("failed to set up debug callback!"); + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); } } 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!"); } } @@ -231,11 +198,11 @@ class HelloTriangleApplication { QueueFamilyIndices indices = findQueueFamilies(physicalDevice); std::vector queueCreateInfos; - std::set uniqueQueueFamilies = {indices.graphicsFamily, indices.presentFamily}; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; float queuePriority = 1.0f; - for (int queueFamily : uniqueQueueFamilies) { - VkDeviceQueueCreateInfo queueCreateInfo = {}; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; queueCreateInfo.queueFamilyIndex = queueFamily; queueCreateInfo.queueCount = 1; @@ -243,31 +210,31 @@ class HelloTriangleApplication { queueCreateInfos.push_back(queueCreateInfo); } - VkPhysicalDeviceFeatures deviceFeatures = {}; + VkPhysicalDeviceFeatures deviceFeatures{}; - VkDeviceCreateInfo createInfo = {}; + 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!"); } - vkGetDeviceQueue(device, indices.graphicsFamily, 0, &graphicsQueue); - vkGetDeviceQueue(device, indices.presentFamily, 0, &presentQueue); + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); } bool isDeviceSuitable(VkPhysicalDevice device) { @@ -287,14 +254,14 @@ class HelloTriangleApplication { int i = 0; for (const auto& queueFamily : queueFamilies) { - if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { indices.graphicsFamily = i; } VkBool32 presentSupport = false; vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); - if (queueFamily.queueCount > 0 && presentSupport) { + if (presentSupport) { indices.presentFamily = i; } @@ -309,18 +276,14 @@ 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); + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); } return extensions; @@ -351,8 +314,8 @@ class HelloTriangleApplication { return true; } - 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; + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; return VK_FALSE; } @@ -363,7 +326,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 66% rename from code/swap_chain_creation.cpp rename to code/06_swap_chain_creation.cpp index 954e6584..cbca98ad 100644 --- a/code/swap_chain_creation.cpp +++ b/code/06_swap_chain_creation.cpp @@ -3,17 +3,20 @@ #include #include -#include #include #include #include +#include +#include +#include +#include #include -const int WIDTH = 800; -const int HEIGHT = 600; +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; const std::vector validationLayers = { - "VK_LAYER_LUNARG_standard_validation" + "VK_LAYER_KHRONOS_validation" }; const std::vector deviceExtensions = { @@ -26,86 +29,28 @@ const bool enableValidationLayers = false; const bool enableValidationLayers = true; #endif -VkResult CreateDebugReportCallbackEXT(VkInstance instance, const VkDebugReportCallbackCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugReportCallbackEXT* pCallback) { - auto func = (PFN_vkCreateDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT"); +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); if (func != nullptr) { - return func(instance, pCreateInfo, pAllocator, pCallback); + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); } else { return VK_ERROR_EXTENSION_NOT_PRESENT; } } -void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT callback, const VkAllocationCallbacks* pAllocator) { - auto func = (PFN_vkDestroyDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT"); +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); if (func != nullptr) { - func(instance, callback, pAllocator); + func(instance, debugMessenger, pAllocator); } } -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; + std::optional graphicsFamily; + std::optional presentFamily; bool isComplete() { - return graphicsFamily >= 0 && presentFamily >= 0; + return graphicsFamily.has_value() && presentFamily.has_value(); } }; @@ -121,22 +66,23 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + 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; @@ -152,7 +98,7 @@ class HelloTriangleApplication { void initVulkan() { createInstance(); - setupDebugCallback(); + setupDebugMessenger(); createSurface(); pickPhysicalDevice(); createLogicalDevice(); @@ -163,6 +109,18 @@ class HelloTriangleApplication { while (!glfwWindowShouldClose(window)) { glfwPollEvents(); } + } + + void cleanup() { + vkDestroySwapchainKHR(device, swapChain, nullptr); + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); glfwDestroyWindow(window); @@ -174,7 +132,7 @@ class HelloTriangleApplication { throw std::runtime_error("validation layers requested, but not available!"); } - VkApplicationInfo appInfo = {}; + VkApplicationInfo appInfo{}; appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; appInfo.pApplicationName = "Hello Triangle"; appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); @@ -182,41 +140,53 @@ class HelloTriangleApplication { appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); appInfo.apiVersion = VK_API_VERSION_1_0; - VkInstanceCreateInfo createInfo = {}; + VkInstanceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; } else { createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } - void setupDebugCallback() { + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { 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; + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { - throw std::runtime_error("failed to set up debug callback!"); + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); } } 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,11 +218,11 @@ class HelloTriangleApplication { QueueFamilyIndices indices = findQueueFamilies(physicalDevice); std::vector queueCreateInfos; - std::set uniqueQueueFamilies = {indices.graphicsFamily, indices.presentFamily}; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; float queuePriority = 1.0f; - for (int queueFamily : uniqueQueueFamilies) { - VkDeviceQueueCreateInfo queueCreateInfo = {}; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; queueCreateInfo.queueFamilyIndex = queueFamily; queueCreateInfo.queueCount = 1; @@ -260,32 +230,32 @@ class HelloTriangleApplication { queueCreateInfos.push_back(queueCreateInfo); } - VkPhysicalDeviceFeatures deviceFeatures = {}; + VkPhysicalDeviceFeatures deviceFeatures{}; - VkDeviceCreateInfo createInfo = {}; + 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!"); } - vkGetDeviceQueue(device, indices.graphicsFamily, 0, &graphicsQueue); - vkGetDeviceQueue(device, indices.presentFamily, 0, &presentQueue); + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); } void createSwapChain() { @@ -300,7 +270,7 @@ class HelloTriangleApplication { imageCount = swapChainSupport.capabilities.maxImageCount; } - VkSwapchainCreateInfoKHR createInfo = {}; + VkSwapchainCreateInfoKHR createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; createInfo.surface = surface; @@ -312,7 +282,7 @@ class HelloTriangleApplication { createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; QueueFamilyIndices indices = findQueueFamilies(physicalDevice); - uint32_t queueFamilyIndices[] = {(uint32_t) indices.graphicsFamily, (uint32_t) indices.presentFamily}; + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; if (indices.graphicsFamily != indices.presentFamily) { createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; @@ -329,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()); @@ -342,12 +312,8 @@ class HelloTriangleApplication { } 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) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { return availableFormat; } } @@ -355,29 +321,31 @@ class HelloTriangleApplication { return availableFormats[0]; } - VkPresentModeKHR chooseSwapPresentMode(const std::vector availablePresentModes) { - VkPresentModeKHR bestMode = VK_PRESENT_MODE_FIFO_KHR; - + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { 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; + return VK_PRESENT_MODE_FIFO_KHR; } VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { if (capabilities.currentExtent.width != std::numeric_limits::max()) { return capabilities.currentExtent; } else { - VkExtent2D actualExtent = {WIDTH, HEIGHT}; + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); - 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; } } @@ -447,14 +415,14 @@ class HelloTriangleApplication { int i = 0; for (const auto& queueFamily : queueFamilies) { - if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { indices.graphicsFamily = i; } VkBool32 presentSupport = false; vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); - if (queueFamily.queueCount > 0 && presentSupport) { + if (presentSupport) { indices.presentFamily = i; } @@ -469,18 +437,14 @@ 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); + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); } return extensions; @@ -511,8 +475,8 @@ class HelloTriangleApplication { return true; } - 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; + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; return VK_FALSE; } @@ -523,10 +487,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 66% rename from code/graphics_pipeline.cpp rename to code/07_image_views.cpp index 34b1d80c..26c20fac 100644 --- a/code/graphics_pipeline.cpp +++ b/code/07_image_views.cpp @@ -3,18 +3,20 @@ #include #include -#include -#include #include #include #include +#include +#include +#include +#include #include -const int WIDTH = 800; -const int HEIGHT = 600; +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; const std::vector validationLayers = { - "VK_LAYER_LUNARG_standard_validation" + "VK_LAYER_KHRONOS_validation" }; const std::vector deviceExtensions = { @@ -27,86 +29,28 @@ const bool enableValidationLayers = false; const bool enableValidationLayers = true; #endif -VkResult CreateDebugReportCallbackEXT(VkInstance instance, const VkDebugReportCallbackCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugReportCallbackEXT* pCallback) { - auto func = (PFN_vkCreateDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT"); +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); if (func != nullptr) { - return func(instance, pCreateInfo, pAllocator, pCallback); + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); } else { return VK_ERROR_EXTENSION_NOT_PRESENT; } } -void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT callback, const VkAllocationCallbacks* pAllocator) { - auto func = (PFN_vkDestroyDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT"); +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); if (func != nullptr) { - func(instance, callback, pAllocator); + func(instance, debugMessenger, pAllocator); } } -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; + std::optional graphicsFamily; + std::optional presentFamily; bool isComplete() { - return graphicsFamily >= 0 && presentFamily >= 0; + return graphicsFamily.has_value() && presentFamily.has_value(); } }; @@ -122,26 +66,27 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + 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(); @@ -154,19 +99,34 @@ class HelloTriangleApplication { void initVulkan() { createInstance(); - setupDebugCallback(); + setupDebugMessenger(); createSurface(); pickPhysicalDevice(); 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) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); glfwDestroyWindow(window); @@ -178,7 +138,7 @@ class HelloTriangleApplication { throw std::runtime_error("validation layers requested, but not available!"); } - VkApplicationInfo appInfo = {}; + VkApplicationInfo appInfo{}; appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; appInfo.pApplicationName = "Hello Triangle"; appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); @@ -186,41 +146,53 @@ class HelloTriangleApplication { appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); appInfo.apiVersion = VK_API_VERSION_1_0; - VkInstanceCreateInfo createInfo = {}; + VkInstanceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; } else { createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } - void setupDebugCallback() { + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { 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; + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { - throw std::runtime_error("failed to set up debug callback!"); + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); } } 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!"); } } @@ -252,11 +224,11 @@ class HelloTriangleApplication { QueueFamilyIndices indices = findQueueFamilies(physicalDevice); std::vector queueCreateInfos; - std::set uniqueQueueFamilies = {indices.graphicsFamily, indices.presentFamily}; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; float queuePriority = 1.0f; - for (int queueFamily : uniqueQueueFamilies) { - VkDeviceQueueCreateInfo queueCreateInfo = {}; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; queueCreateInfo.queueFamilyIndex = queueFamily; queueCreateInfo.queueCount = 1; @@ -264,32 +236,32 @@ class HelloTriangleApplication { queueCreateInfos.push_back(queueCreateInfo); } - VkPhysicalDeviceFeatures deviceFeatures = {}; + VkPhysicalDeviceFeatures deviceFeatures{}; - VkDeviceCreateInfo createInfo = {}; + 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!"); } - vkGetDeviceQueue(device, indices.graphicsFamily, 0, &graphicsQueue); - vkGetDeviceQueue(device, indices.presentFamily, 0, &presentQueue); + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); } void createSwapChain() { @@ -304,7 +276,7 @@ class HelloTriangleApplication { imageCount = swapChainSupport.capabilities.maxImageCount; } - VkSwapchainCreateInfoKHR createInfo = {}; + VkSwapchainCreateInfoKHR createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; createInfo.surface = surface; @@ -316,7 +288,7 @@ class HelloTriangleApplication { createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; QueueFamilyIndices indices = findQueueFamilies(physicalDevice); - uint32_t queueFamilyIndices[] = {(uint32_t) indices.graphicsFamily, (uint32_t) indices.presentFamily}; + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; if (indices.graphicsFamily != indices.presentFamily) { createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; @@ -333,10 +305,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,10 +318,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++) { - VkImageViewCreateInfo createInfo = {}; + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; createInfo.image = swapChainImages[i]; createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; @@ -364,23 +336,15 @@ 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}; - } - for (const auto& availableFormat : availableFormats) { - if (availableFormat.format == VK_FORMAT_B8G8R8A8_UNORM && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { return availableFormat; } } @@ -388,29 +352,31 @@ class HelloTriangleApplication { return availableFormats[0]; } - VkPresentModeKHR chooseSwapPresentMode(const std::vector availablePresentModes) { - VkPresentModeKHR bestMode = VK_PRESENT_MODE_FIFO_KHR; - + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { 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; + return VK_PRESENT_MODE_FIFO_KHR; } VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { if (capabilities.currentExtent.width != std::numeric_limits::max()) { return capabilities.currentExtent; } else { - VkExtent2D actualExtent = {WIDTH, HEIGHT}; + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); - 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; } } @@ -480,14 +446,14 @@ class HelloTriangleApplication { int i = 0; for (const auto& queueFamily : queueFamilies) { - if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { indices.graphicsFamily = i; } VkBool32 presentSupport = false; vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); - if (queueFamily.queueCount > 0 && presentSupport) { + if (presentSupport) { indices.presentFamily = i; } @@ -502,18 +468,14 @@ 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); + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); } return extensions; @@ -544,8 +506,8 @@ class HelloTriangleApplication { return true; } - 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; + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; return VK_FALSE; } @@ -556,10 +518,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 67% rename from code/image_views.cpp rename to code/08_graphics_pipeline.cpp index 7bd4a6ef..4c337f75 100644 --- a/code/image_views.cpp +++ b/code/08_graphics_pipeline.cpp @@ -3,17 +3,20 @@ #include #include -#include #include #include #include +#include +#include +#include +#include #include -const int WIDTH = 800; -const int HEIGHT = 600; +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; const std::vector validationLayers = { - "VK_LAYER_LUNARG_standard_validation" + "VK_LAYER_KHRONOS_validation" }; const std::vector deviceExtensions = { @@ -26,86 +29,28 @@ const bool enableValidationLayers = false; const bool enableValidationLayers = true; #endif -VkResult CreateDebugReportCallbackEXT(VkInstance instance, const VkDebugReportCallbackCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugReportCallbackEXT* pCallback) { - auto func = (PFN_vkCreateDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT"); +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); if (func != nullptr) { - return func(instance, pCreateInfo, pAllocator, pCallback); + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); } else { return VK_ERROR_EXTENSION_NOT_PRESENT; } } -void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT callback, const VkAllocationCallbacks* pAllocator) { - auto func = (PFN_vkDestroyDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT"); +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); if (func != nullptr) { - func(instance, callback, pAllocator); + func(instance, debugMessenger, pAllocator); } } -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; + std::optional graphicsFamily; + std::optional presentFamily; bool isComplete() { - return graphicsFamily >= 0 && presentFamily >= 0; + return graphicsFamily.has_value() && presentFamily.has_value(); } }; @@ -121,26 +66,27 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + 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(); @@ -153,18 +99,35 @@ class HelloTriangleApplication { void initVulkan() { createInstance(); - setupDebugCallback(); + setupDebugMessenger(); createSurface(); pickPhysicalDevice(); 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) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); glfwDestroyWindow(window); @@ -176,7 +139,7 @@ class HelloTriangleApplication { throw std::runtime_error("validation layers requested, but not available!"); } - VkApplicationInfo appInfo = {}; + VkApplicationInfo appInfo{}; appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; appInfo.pApplicationName = "Hello Triangle"; appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); @@ -184,41 +147,53 @@ class HelloTriangleApplication { appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); appInfo.apiVersion = VK_API_VERSION_1_0; - VkInstanceCreateInfo createInfo = {}; + VkInstanceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; } else { createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } - void setupDebugCallback() { + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { 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; + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { - throw std::runtime_error("failed to set up debug callback!"); + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); } } 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!"); } } @@ -250,11 +225,11 @@ class HelloTriangleApplication { QueueFamilyIndices indices = findQueueFamilies(physicalDevice); std::vector queueCreateInfos; - std::set uniqueQueueFamilies = {indices.graphicsFamily, indices.presentFamily}; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; float queuePriority = 1.0f; - for (int queueFamily : uniqueQueueFamilies) { - VkDeviceQueueCreateInfo queueCreateInfo = {}; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; queueCreateInfo.queueFamilyIndex = queueFamily; queueCreateInfo.queueCount = 1; @@ -262,32 +237,32 @@ class HelloTriangleApplication { queueCreateInfos.push_back(queueCreateInfo); } - VkPhysicalDeviceFeatures deviceFeatures = {}; + VkPhysicalDeviceFeatures deviceFeatures{}; - VkDeviceCreateInfo createInfo = {}; + 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!"); } - vkGetDeviceQueue(device, indices.graphicsFamily, 0, &graphicsQueue); - vkGetDeviceQueue(device, indices.presentFamily, 0, &presentQueue); + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); } void createSwapChain() { @@ -302,7 +277,7 @@ class HelloTriangleApplication { imageCount = swapChainSupport.capabilities.maxImageCount; } - VkSwapchainCreateInfoKHR createInfo = {}; + VkSwapchainCreateInfoKHR createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; createInfo.surface = surface; @@ -314,7 +289,7 @@ class HelloTriangleApplication { createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; QueueFamilyIndices indices = findQueueFamilies(physicalDevice); - uint32_t queueFamilyIndices[] = {(uint32_t) indices.graphicsFamily, (uint32_t) indices.presentFamily}; + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; if (indices.graphicsFamily != indices.presentFamily) { createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; @@ -331,10 +306,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,10 +319,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++) { - VkImageViewCreateInfo createInfo = {}; + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; createInfo.image = swapChainImages[i]; createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; @@ -362,19 +337,19 @@ 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!"); } } } - 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}; - } + void createGraphicsPipeline() { + + } + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { for (const auto& availableFormat : availableFormats) { - if (availableFormat.format == VK_FORMAT_B8G8R8A8_UNORM && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { return availableFormat; } } @@ -382,29 +357,31 @@ class HelloTriangleApplication { return availableFormats[0]; } - VkPresentModeKHR chooseSwapPresentMode(const std::vector availablePresentModes) { - VkPresentModeKHR bestMode = VK_PRESENT_MODE_FIFO_KHR; - + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { 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; + return VK_PRESENT_MODE_FIFO_KHR; } VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { if (capabilities.currentExtent.width != std::numeric_limits::max()) { return capabilities.currentExtent; } else { - VkExtent2D actualExtent = {WIDTH, HEIGHT}; + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); - 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; } } @@ -474,14 +451,14 @@ class HelloTriangleApplication { int i = 0; for (const auto& queueFamily : queueFamilies) { - if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { indices.graphicsFamily = i; } VkBool32 presentSupport = false; vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); - if (queueFamily.queueCount > 0 && presentSupport) { + if (presentSupport) { indices.presentFamily = i; } @@ -496,18 +473,14 @@ 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); + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); } return extensions; @@ -538,8 +511,8 @@ class HelloTriangleApplication { return true; } - 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; + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; return VK_FALSE; } @@ -550,10 +523,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 70% rename from code/shader_base.frag rename to code/09_shader_base.frag index 14fc5862..36176035 100644 --- a/code/shader_base.frag +++ b/code/09_shader_base.frag @@ -1,5 +1,4 @@ #version 450 -#extension GL_ARB_separate_shader_objects : enable layout(location = 0) in vec3 fragColor; @@ -7,4 +6,4 @@ layout(location = 0) out vec4 outColor; void main() { outColor = vec4(fragColor, 1.0); -} \ No newline at end of file +} diff --git a/code/shader_base.vert b/code/09_shader_base.vert similarity index 75% rename from code/shader_base.vert rename to code/09_shader_base.vert index 0430a01c..9bd71d4d 100644 --- a/code/shader_base.vert +++ b/code/09_shader_base.vert @@ -1,9 +1,4 @@ #version 450 -#extension GL_ARB_separate_shader_objects : enable - -out gl_PerVertex { - vec4 gl_Position; -}; layout(location = 0) out vec3 fragColor; @@ -22,4 +17,4 @@ vec3 colors[3] = vec3[]( void main() { gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); fragColor = colors[gl_VertexIndex]; -} \ No newline at end of file +} diff --git a/code/shader_modules.cpp b/code/09_shader_modules.cpp similarity index 66% rename from code/shader_modules.cpp rename to code/09_shader_modules.cpp index 982f1905..9de7078c 100644 --- a/code/shader_modules.cpp +++ b/code/09_shader_modules.cpp @@ -2,19 +2,22 @@ #include #include -#include -#include #include +#include #include #include #include +#include +#include +#include +#include #include -const int WIDTH = 800; -const int HEIGHT = 600; +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; const std::vector validationLayers = { - "VK_LAYER_LUNARG_standard_validation" + "VK_LAYER_KHRONOS_validation" }; const std::vector deviceExtensions = { @@ -27,86 +30,28 @@ const bool enableValidationLayers = false; const bool enableValidationLayers = true; #endif -VkResult CreateDebugReportCallbackEXT(VkInstance instance, const VkDebugReportCallbackCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugReportCallbackEXT* pCallback) { - auto func = (PFN_vkCreateDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT"); +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); if (func != nullptr) { - return func(instance, pCreateInfo, pAllocator, pCallback); + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); } else { return VK_ERROR_EXTENSION_NOT_PRESENT; } } -void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT callback, const VkAllocationCallbacks* pAllocator) { - auto func = (PFN_vkDestroyDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT"); +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); if (func != nullptr) { - func(instance, callback, pAllocator); + func(instance, debugMessenger, pAllocator); } } -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; + std::optional graphicsFamily; + std::optional presentFamily; bool isComplete() { - return graphicsFamily >= 0 && presentFamily >= 0; + return graphicsFamily.has_value() && presentFamily.has_value(); } }; @@ -122,26 +67,27 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + 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(); @@ -154,7 +100,7 @@ class HelloTriangleApplication { void initVulkan() { createInstance(); - setupDebugCallback(); + setupDebugMessenger(); createSurface(); pickPhysicalDevice(); createLogicalDevice(); @@ -167,6 +113,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) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); glfwDestroyWindow(window); @@ -178,7 +140,7 @@ class HelloTriangleApplication { throw std::runtime_error("validation layers requested, but not available!"); } - VkApplicationInfo appInfo = {}; + VkApplicationInfo appInfo{}; appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; appInfo.pApplicationName = "Hello Triangle"; appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); @@ -186,41 +148,53 @@ class HelloTriangleApplication { appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); appInfo.apiVersion = VK_API_VERSION_1_0; - VkInstanceCreateInfo createInfo = {}; + VkInstanceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; } else { createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } - void setupDebugCallback() { + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { 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; + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { - throw std::runtime_error("failed to set up debug callback!"); + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); } } 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!"); } } @@ -252,11 +226,11 @@ class HelloTriangleApplication { QueueFamilyIndices indices = findQueueFamilies(physicalDevice); std::vector queueCreateInfos; - std::set uniqueQueueFamilies = {indices.graphicsFamily, indices.presentFamily}; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; float queuePriority = 1.0f; - for (int queueFamily : uniqueQueueFamilies) { - VkDeviceQueueCreateInfo queueCreateInfo = {}; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; queueCreateInfo.queueFamilyIndex = queueFamily; queueCreateInfo.queueCount = 1; @@ -264,32 +238,32 @@ class HelloTriangleApplication { queueCreateInfos.push_back(queueCreateInfo); } - VkPhysicalDeviceFeatures deviceFeatures = {}; + VkPhysicalDeviceFeatures deviceFeatures{}; - VkDeviceCreateInfo createInfo = {}; + 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!"); } - vkGetDeviceQueue(device, indices.graphicsFamily, 0, &graphicsQueue); - vkGetDeviceQueue(device, indices.presentFamily, 0, &presentQueue); + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); } void createSwapChain() { @@ -304,7 +278,7 @@ class HelloTriangleApplication { imageCount = swapChainSupport.capabilities.maxImageCount; } - VkSwapchainCreateInfoKHR createInfo = {}; + VkSwapchainCreateInfoKHR createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; createInfo.surface = surface; @@ -316,7 +290,7 @@ class HelloTriangleApplication { createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; QueueFamilyIndices indices = findQueueFamilies(physicalDevice); - uint32_t queueFamilyIndices[] = {(uint32_t) indices.graphicsFamily, (uint32_t) indices.presentFamily}; + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; if (indices.graphicsFamily != indices.presentFamily) { createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; @@ -333,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()); @@ -346,10 +320,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++) { - VkImageViewCreateInfo createInfo = {}; + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; createInfo.image = swapChainImages[i]; createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; @@ -364,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!"); } } @@ -374,48 +348,44 @@ 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 = {}; + 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 = {}; + 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}; + + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); } - void createShaderModule(const std::vector& code, VDeleter& shaderModule) { - VkShaderModuleCreateInfo createInfo = {}; + 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) { - 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) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { return availableFormat; } } @@ -423,29 +393,31 @@ class HelloTriangleApplication { return availableFormats[0]; } - VkPresentModeKHR chooseSwapPresentMode(const std::vector availablePresentModes) { - VkPresentModeKHR bestMode = VK_PRESENT_MODE_FIFO_KHR; - + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { 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; + return VK_PRESENT_MODE_FIFO_KHR; } VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { if (capabilities.currentExtent.width != std::numeric_limits::max()) { return capabilities.currentExtent; } else { - VkExtent2D actualExtent = {WIDTH, HEIGHT}; + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); - 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; } } @@ -515,14 +487,14 @@ class HelloTriangleApplication { int i = 0; for (const auto& queueFamily : queueFamilies) { - if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { indices.graphicsFamily = i; } VkBool32 presentSupport = false; vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); - if (queueFamily.queueCount > 0 && presentSupport) { + if (presentSupport) { indices.presentFamily = i; } @@ -537,18 +509,14 @@ 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); + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); } return extensions; @@ -597,8 +565,8 @@ class HelloTriangleApplication { 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; + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; return VK_FALSE; } @@ -609,10 +577,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 66% rename from code/fixed_functions.cpp rename to code/10_fixed_functions.cpp index 364c39b5..7abe5003 100644 --- a/code/fixed_functions.cpp +++ b/code/10_fixed_functions.cpp @@ -2,19 +2,22 @@ #include #include -#include -#include #include +#include #include #include #include +#include +#include +#include +#include #include -const int WIDTH = 800; -const int HEIGHT = 600; +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; const std::vector validationLayers = { - "VK_LAYER_LUNARG_standard_validation" + "VK_LAYER_KHRONOS_validation" }; const std::vector deviceExtensions = { @@ -27,86 +30,28 @@ const bool enableValidationLayers = false; const bool enableValidationLayers = true; #endif -VkResult CreateDebugReportCallbackEXT(VkInstance instance, const VkDebugReportCallbackCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugReportCallbackEXT* pCallback) { - auto func = (PFN_vkCreateDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT"); +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); if (func != nullptr) { - return func(instance, pCreateInfo, pAllocator, pCallback); + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); } else { return VK_ERROR_EXTENSION_NOT_PRESENT; } } -void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT callback, const VkAllocationCallbacks* pAllocator) { - auto func = (PFN_vkDestroyDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT"); +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); if (func != nullptr) { - func(instance, callback, pAllocator); + func(instance, debugMessenger, pAllocator); } } -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; + std::optional graphicsFamily; + std::optional presentFamily; bool isComplete() { - return graphicsFamily >= 0 && presentFamily >= 0; + return graphicsFamily.has_value() && presentFamily.has_value(); } }; @@ -122,30 +67,29 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + 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(); @@ -158,7 +102,7 @@ class HelloTriangleApplication { void initVulkan() { createInstance(); - setupDebugCallback(); + setupDebugMessenger(); createSurface(); pickPhysicalDevice(); createLogicalDevice(); @@ -171,6 +115,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) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); glfwDestroyWindow(window); @@ -182,7 +144,7 @@ class HelloTriangleApplication { throw std::runtime_error("validation layers requested, but not available!"); } - VkApplicationInfo appInfo = {}; + VkApplicationInfo appInfo{}; appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; appInfo.pApplicationName = "Hello Triangle"; appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); @@ -190,41 +152,53 @@ class HelloTriangleApplication { appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); appInfo.apiVersion = VK_API_VERSION_1_0; - VkInstanceCreateInfo createInfo = {}; + VkInstanceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; } else { createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } - void setupDebugCallback() { + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { 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; + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { - throw std::runtime_error("failed to set up debug callback!"); + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); } } 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!"); } } @@ -256,11 +230,11 @@ class HelloTriangleApplication { QueueFamilyIndices indices = findQueueFamilies(physicalDevice); std::vector queueCreateInfos; - std::set uniqueQueueFamilies = {indices.graphicsFamily, indices.presentFamily}; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; float queuePriority = 1.0f; - for (int queueFamily : uniqueQueueFamilies) { - VkDeviceQueueCreateInfo queueCreateInfo = {}; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; queueCreateInfo.queueFamilyIndex = queueFamily; queueCreateInfo.queueCount = 1; @@ -268,32 +242,32 @@ class HelloTriangleApplication { queueCreateInfos.push_back(queueCreateInfo); } - VkPhysicalDeviceFeatures deviceFeatures = {}; + VkPhysicalDeviceFeatures deviceFeatures{}; - VkDeviceCreateInfo createInfo = {}; + 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!"); } - vkGetDeviceQueue(device, indices.graphicsFamily, 0, &graphicsQueue); - vkGetDeviceQueue(device, indices.presentFamily, 0, &presentQueue); + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); } void createSwapChain() { @@ -308,7 +282,7 @@ class HelloTriangleApplication { imageCount = swapChainSupport.capabilities.maxImageCount; } - VkSwapchainCreateInfoKHR createInfo = {}; + VkSwapchainCreateInfoKHR createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; createInfo.surface = surface; @@ -320,7 +294,7 @@ class HelloTriangleApplication { createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; QueueFamilyIndices indices = findQueueFamilies(physicalDevice); - uint32_t queueFamilyIndices[] = {(uint32_t) indices.graphicsFamily, (uint32_t) indices.presentFamily}; + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; if (indices.graphicsFamily != indices.presentFamily) { createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; @@ -337,10 +311,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,10 +324,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++) { - VkImageViewCreateInfo createInfo = {}; + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; createInfo.image = swapChainImages[i]; createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; @@ -368,7 +342,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,18 +352,16 @@ 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 = {}; + 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 = {}; + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; fragShaderStageInfo.module = fragShaderModule; @@ -397,36 +369,22 @@ class HelloTriangleApplication { VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; - VkPipelineVertexInputStateCreateInfo vertexInputInfo = {}; + VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; vertexInputInfo.vertexBindingDescriptionCount = 0; vertexInputInfo.vertexAttributeDescriptionCount = 0; - VkPipelineInputAssemblyStateCreateInfo inputAssembly = {}; + 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 = {}; + 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 = {}; + VkPipelineRasterizationStateCreateInfo rasterizer{}; rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; rasterizer.depthClampEnable = VK_FALSE; rasterizer.rasterizerDiscardEnable = VK_FALSE; @@ -436,16 +394,16 @@ class HelloTriangleApplication { rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; rasterizer.depthBiasEnable = VK_FALSE; - VkPipelineMultisampleStateCreateInfo multisampling = {}; + VkPipelineMultisampleStateCreateInfo multisampling{}; multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; multisampling.sampleShadingEnable = VK_FALSE; multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; - VkPipelineColorBlendAttachmentState colorBlendAttachment = {}; + 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 = {}; + VkPipelineColorBlendStateCreateInfo colorBlending{}; colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; colorBlending.logicOpEnable = VK_FALSE; colorBlending.logicOp = VK_LOGIC_OP_COPY; @@ -456,38 +414,45 @@ class HelloTriangleApplication { colorBlending.blendConstants[2] = 0.0f; colorBlending.blendConstants[3] = 0.0f; - VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; + std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; 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) { - VkShaderModuleCreateInfo createInfo = {}; + 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) { - 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) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { return availableFormat; } } @@ -495,29 +460,31 @@ class HelloTriangleApplication { return availableFormats[0]; } - VkPresentModeKHR chooseSwapPresentMode(const std::vector availablePresentModes) { - VkPresentModeKHR bestMode = VK_PRESENT_MODE_FIFO_KHR; - + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { 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; + return VK_PRESENT_MODE_FIFO_KHR; } VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { if (capabilities.currentExtent.width != std::numeric_limits::max()) { return capabilities.currentExtent; } else { - VkExtent2D actualExtent = {WIDTH, HEIGHT}; + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); - 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; } } @@ -587,14 +554,14 @@ class HelloTriangleApplication { int i = 0; for (const auto& queueFamily : queueFamilies) { - if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { indices.graphicsFamily = i; } VkBool32 presentSupport = false; vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); - if (queueFamily.queueCount > 0 && presentSupport) { + if (presentSupport) { indices.presentFamily = i; } @@ -609,18 +576,14 @@ 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); + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); } return extensions; @@ -669,8 +632,8 @@ class HelloTriangleApplication { 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; + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; return VK_FALSE; } @@ -681,10 +644,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/11_render_passes.cpp similarity index 67% rename from code/render_passes.cpp rename to code/11_render_passes.cpp index d239acdf..3310eb00 100644 --- a/code/render_passes.cpp +++ b/code/11_render_passes.cpp @@ -2,19 +2,22 @@ #include #include -#include -#include #include +#include #include #include #include +#include +#include +#include +#include #include -const int WIDTH = 800; -const int HEIGHT = 600; +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; const std::vector validationLayers = { - "VK_LAYER_LUNARG_standard_validation" + "VK_LAYER_KHRONOS_validation" }; const std::vector deviceExtensions = { @@ -27,86 +30,28 @@ const bool enableValidationLayers = false; const bool enableValidationLayers = true; #endif -VkResult CreateDebugReportCallbackEXT(VkInstance instance, const VkDebugReportCallbackCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugReportCallbackEXT* pCallback) { - auto func = (PFN_vkCreateDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT"); +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); if (func != nullptr) { - return func(instance, pCreateInfo, pAllocator, pCallback); + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); } else { return VK_ERROR_EXTENSION_NOT_PRESENT; } } -void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT callback, const VkAllocationCallbacks* pAllocator) { - auto func = (PFN_vkDestroyDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT"); +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); if (func != nullptr) { - func(instance, callback, pAllocator); + func(instance, debugMessenger, pAllocator); } } -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; + std::optional graphicsFamily; + std::optional presentFamily; bool isComplete() { - return graphicsFamily >= 0 && presentFamily >= 0; + return graphicsFamily.has_value() && presentFamily.has_value(); } }; @@ -122,30 +67,30 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + 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; void initWindow() { glfwInit(); @@ -158,7 +103,7 @@ class HelloTriangleApplication { void initVulkan() { createInstance(); - setupDebugCallback(); + setupDebugMessenger(); createSurface(); pickPhysicalDevice(); createLogicalDevice(); @@ -172,6 +117,25 @@ class HelloTriangleApplication { 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) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); glfwDestroyWindow(window); @@ -183,7 +147,7 @@ class HelloTriangleApplication { throw std::runtime_error("validation layers requested, but not available!"); } - VkApplicationInfo appInfo = {}; + VkApplicationInfo appInfo{}; appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; appInfo.pApplicationName = "Hello Triangle"; appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); @@ -191,41 +155,53 @@ class HelloTriangleApplication { appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); appInfo.apiVersion = VK_API_VERSION_1_0; - VkInstanceCreateInfo createInfo = {}; + VkInstanceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; } else { createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } - void setupDebugCallback() { + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { 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; + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { - throw std::runtime_error("failed to set up debug callback!"); + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); } } 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!"); } } @@ -257,11 +233,11 @@ class HelloTriangleApplication { QueueFamilyIndices indices = findQueueFamilies(physicalDevice); std::vector queueCreateInfos; - std::set uniqueQueueFamilies = {indices.graphicsFamily, indices.presentFamily}; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; float queuePriority = 1.0f; - for (int queueFamily : uniqueQueueFamilies) { - VkDeviceQueueCreateInfo queueCreateInfo = {}; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; queueCreateInfo.queueFamilyIndex = queueFamily; queueCreateInfo.queueCount = 1; @@ -269,32 +245,32 @@ class HelloTriangleApplication { queueCreateInfos.push_back(queueCreateInfo); } - VkPhysicalDeviceFeatures deviceFeatures = {}; + VkPhysicalDeviceFeatures deviceFeatures{}; - VkDeviceCreateInfo createInfo = {}; + 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!"); } - vkGetDeviceQueue(device, indices.graphicsFamily, 0, &graphicsQueue); - vkGetDeviceQueue(device, indices.presentFamily, 0, &presentQueue); + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); } void createSwapChain() { @@ -309,7 +285,7 @@ class HelloTriangleApplication { imageCount = swapChainSupport.capabilities.maxImageCount; } - VkSwapchainCreateInfoKHR createInfo = {}; + VkSwapchainCreateInfoKHR createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; createInfo.surface = surface; @@ -321,7 +297,7 @@ class HelloTriangleApplication { createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; QueueFamilyIndices indices = findQueueFamilies(physicalDevice); - uint32_t queueFamilyIndices[] = {(uint32_t) indices.graphicsFamily, (uint32_t) indices.presentFamily}; + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; if (indices.graphicsFamily != indices.presentFamily) { createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; @@ -338,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()); @@ -351,10 +327,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++) { - VkImageViewCreateInfo createInfo = {}; + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; createInfo.image = swapChainImages[i]; createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; @@ -369,14 +345,14 @@ 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 createRenderPass() { - VkAttachmentDescription colorAttachment = {}; + VkAttachmentDescription colorAttachment{}; colorAttachment.format = swapChainImageFormat; colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; @@ -386,23 +362,23 @@ class HelloTriangleApplication { colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - VkAttachmentReference colorAttachmentRef = {}; + VkAttachmentReference colorAttachmentRef{}; colorAttachmentRef.attachment = 0; colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - VkSubpassDescription subpass = {}; + VkSubpassDescription subpass{}; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass.colorAttachmentCount = 1; subpass.pColorAttachments = &colorAttachmentRef; - VkRenderPassCreateInfo renderPassInfo = {}; + VkRenderPassCreateInfo renderPassInfo{}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; renderPassInfo.attachmentCount = 1; 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!"); } } @@ -411,18 +387,16 @@ 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 = {}; + 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 = {}; + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; fragShaderStageInfo.module = fragShaderModule; @@ -430,36 +404,22 @@ class HelloTriangleApplication { VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; - VkPipelineVertexInputStateCreateInfo vertexInputInfo = {}; + VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; vertexInputInfo.vertexBindingDescriptionCount = 0; vertexInputInfo.vertexAttributeDescriptionCount = 0; - VkPipelineInputAssemblyStateCreateInfo inputAssembly = {}; + 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 = {}; + 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 = {}; + VkPipelineRasterizationStateCreateInfo rasterizer{}; rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; rasterizer.depthClampEnable = VK_FALSE; rasterizer.rasterizerDiscardEnable = VK_FALSE; @@ -469,16 +429,16 @@ class HelloTriangleApplication { rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; rasterizer.depthBiasEnable = VK_FALSE; - VkPipelineMultisampleStateCreateInfo multisampling = {}; + VkPipelineMultisampleStateCreateInfo multisampling{}; multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; multisampling.sampleShadingEnable = VK_FALSE; multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; - VkPipelineColorBlendAttachmentState colorBlendAttachment = {}; + 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 = {}; + VkPipelineColorBlendStateCreateInfo colorBlending{}; colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; colorBlending.logicOpEnable = VK_FALSE; colorBlending.logicOp = VK_LOGIC_OP_COPY; @@ -489,38 +449,45 @@ class HelloTriangleApplication { colorBlending.blendConstants[2] = 0.0f; colorBlending.blendConstants[3] = 0.0f; - VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; + std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; 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) { - VkShaderModuleCreateInfo createInfo = {}; + 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) { - 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) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { return availableFormat; } } @@ -528,29 +495,31 @@ class HelloTriangleApplication { return availableFormats[0]; } - VkPresentModeKHR chooseSwapPresentMode(const std::vector availablePresentModes) { - VkPresentModeKHR bestMode = VK_PRESENT_MODE_FIFO_KHR; - + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { 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; + return VK_PRESENT_MODE_FIFO_KHR; } VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { if (capabilities.currentExtent.width != std::numeric_limits::max()) { return capabilities.currentExtent; } else { - VkExtent2D actualExtent = {WIDTH, HEIGHT}; + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); - 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; } } @@ -620,14 +589,14 @@ class HelloTriangleApplication { int i = 0; for (const auto& queueFamily : queueFamilies) { - if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { indices.graphicsFamily = i; } VkBool32 presentSupport = false; vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); - if (queueFamily.queueCount > 0 && presentSupport) { + if (presentSupport) { indices.presentFamily = i; } @@ -642,18 +611,14 @@ 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); + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); } return extensions; @@ -702,8 +667,8 @@ class HelloTriangleApplication { 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; + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; return VK_FALSE; } @@ -714,10 +679,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/12_graphics_pipeline_complete.cpp similarity index 68% rename from code/graphics_pipeline_complete.cpp rename to code/12_graphics_pipeline_complete.cpp index c22f9892..a30f38be 100644 --- a/code/graphics_pipeline_complete.cpp +++ b/code/12_graphics_pipeline_complete.cpp @@ -2,19 +2,22 @@ #include #include -#include -#include #include +#include #include #include #include +#include +#include +#include +#include #include -const int WIDTH = 800; -const int HEIGHT = 600; +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; const std::vector validationLayers = { - "VK_LAYER_LUNARG_standard_validation" + "VK_LAYER_KHRONOS_validation" }; const std::vector deviceExtensions = { @@ -27,86 +30,28 @@ const bool enableValidationLayers = false; const bool enableValidationLayers = true; #endif -VkResult CreateDebugReportCallbackEXT(VkInstance instance, const VkDebugReportCallbackCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugReportCallbackEXT* pCallback) { - auto func = (PFN_vkCreateDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT"); +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); if (func != nullptr) { - return func(instance, pCreateInfo, pAllocator, pCallback); + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); } else { return VK_ERROR_EXTENSION_NOT_PRESENT; } } -void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT callback, const VkAllocationCallbacks* pAllocator) { - auto func = (PFN_vkDestroyDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT"); +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); if (func != nullptr) { - func(instance, callback, pAllocator); + func(instance, debugMessenger, pAllocator); } } -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; + std::optional graphicsFamily; + std::optional presentFamily; bool isComplete() { - return graphicsFamily >= 0 && presentFamily >= 0; + return graphicsFamily.has_value() && presentFamily.has_value(); } }; @@ -122,31 +67,31 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + 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; + VkPipeline graphicsPipeline; void initWindow() { glfwInit(); @@ -159,7 +104,7 @@ class HelloTriangleApplication { void initVulkan() { createInstance(); - setupDebugCallback(); + setupDebugMessenger(); createSurface(); pickPhysicalDevice(); createLogicalDevice(); @@ -173,6 +118,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) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); glfwDestroyWindow(window); @@ -184,7 +149,7 @@ class HelloTriangleApplication { throw std::runtime_error("validation layers requested, but not available!"); } - VkApplicationInfo appInfo = {}; + VkApplicationInfo appInfo{}; appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; appInfo.pApplicationName = "Hello Triangle"; appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); @@ -192,41 +157,53 @@ class HelloTriangleApplication { appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); appInfo.apiVersion = VK_API_VERSION_1_0; - VkInstanceCreateInfo createInfo = {}; + VkInstanceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; } else { createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } - void setupDebugCallback() { + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { 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; + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { - throw std::runtime_error("failed to set up debug callback!"); + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); } } 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!"); } } @@ -258,11 +235,11 @@ class HelloTriangleApplication { QueueFamilyIndices indices = findQueueFamilies(physicalDevice); std::vector queueCreateInfos; - std::set uniqueQueueFamilies = {indices.graphicsFamily, indices.presentFamily}; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; float queuePriority = 1.0f; - for (int queueFamily : uniqueQueueFamilies) { - VkDeviceQueueCreateInfo queueCreateInfo = {}; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; queueCreateInfo.queueFamilyIndex = queueFamily; queueCreateInfo.queueCount = 1; @@ -270,32 +247,32 @@ class HelloTriangleApplication { queueCreateInfos.push_back(queueCreateInfo); } - VkPhysicalDeviceFeatures deviceFeatures = {}; + VkPhysicalDeviceFeatures deviceFeatures{}; - VkDeviceCreateInfo createInfo = {}; + 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!"); } - vkGetDeviceQueue(device, indices.graphicsFamily, 0, &graphicsQueue); - vkGetDeviceQueue(device, indices.presentFamily, 0, &presentQueue); + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); } void createSwapChain() { @@ -310,7 +287,7 @@ class HelloTriangleApplication { imageCount = swapChainSupport.capabilities.maxImageCount; } - VkSwapchainCreateInfoKHR createInfo = {}; + VkSwapchainCreateInfoKHR createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; createInfo.surface = surface; @@ -322,7 +299,7 @@ class HelloTriangleApplication { createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; QueueFamilyIndices indices = findQueueFamilies(physicalDevice); - uint32_t queueFamilyIndices[] = {(uint32_t) indices.graphicsFamily, (uint32_t) indices.presentFamily}; + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; if (indices.graphicsFamily != indices.presentFamily) { createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; @@ -339,10 +316,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,10 +329,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++) { - VkImageViewCreateInfo createInfo = {}; + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; createInfo.image = swapChainImages[i]; createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; @@ -370,14 +347,14 @@ 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 createRenderPass() { - VkAttachmentDescription colorAttachment = {}; + VkAttachmentDescription colorAttachment{}; colorAttachment.format = swapChainImageFormat; colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; @@ -387,23 +364,23 @@ class HelloTriangleApplication { colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - VkAttachmentReference colorAttachmentRef = {}; + VkAttachmentReference colorAttachmentRef{}; colorAttachmentRef.attachment = 0; colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - VkSubpassDescription subpass = {}; + VkSubpassDescription subpass{}; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass.colorAttachmentCount = 1; subpass.pColorAttachments = &colorAttachmentRef; - VkRenderPassCreateInfo renderPassInfo = {}; + VkRenderPassCreateInfo renderPassInfo{}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; renderPassInfo.attachmentCount = 1; 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!"); } } @@ -412,18 +389,16 @@ 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 = {}; + 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 = {}; + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; fragShaderStageInfo.module = fragShaderModule; @@ -431,36 +406,22 @@ class HelloTriangleApplication { VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; - VkPipelineVertexInputStateCreateInfo vertexInputInfo = {}; + VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; vertexInputInfo.vertexBindingDescriptionCount = 0; vertexInputInfo.vertexAttributeDescriptionCount = 0; - VkPipelineInputAssemblyStateCreateInfo inputAssembly = {}; + 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 = {}; + 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 = {}; + VkPipelineRasterizationStateCreateInfo rasterizer{}; rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; rasterizer.depthClampEnable = VK_FALSE; rasterizer.rasterizerDiscardEnable = VK_FALSE; @@ -470,16 +431,16 @@ class HelloTriangleApplication { rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; rasterizer.depthBiasEnable = VK_FALSE; - VkPipelineMultisampleStateCreateInfo multisampling = {}; + VkPipelineMultisampleStateCreateInfo multisampling{}; multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; multisampling.sampleShadingEnable = VK_FALSE; multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; - VkPipelineColorBlendAttachmentState colorBlendAttachment = {}; + 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 = {}; + VkPipelineColorBlendStateCreateInfo colorBlending{}; colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; colorBlending.logicOpEnable = VK_FALSE; colorBlending.logicOp = VK_LOGIC_OP_COPY; @@ -490,16 +451,25 @@ class HelloTriangleApplication { colorBlending.blendConstants[2] = 0.0f; colorBlending.blendConstants[3] = 0.0f; - VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; + std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; 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 = {}; + VkGraphicsPipelineCreateInfo pipelineInfo{}; pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; pipelineInfo.stageCount = 2; pipelineInfo.pStages = shaderStages; @@ -509,38 +479,37 @@ class HelloTriangleApplication { pipelineInfo.pRasterizationState = &rasterizer; pipelineInfo.pMultisampleState = &multisampling; pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = &dynamicState; 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) { + 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) { - VkShaderModuleCreateInfo createInfo = {}; + 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) { - 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) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { return availableFormat; } } @@ -548,29 +517,31 @@ class HelloTriangleApplication { return availableFormats[0]; } - VkPresentModeKHR chooseSwapPresentMode(const std::vector availablePresentModes) { - VkPresentModeKHR bestMode = VK_PRESENT_MODE_FIFO_KHR; - + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { 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; + return VK_PRESENT_MODE_FIFO_KHR; } VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { if (capabilities.currentExtent.width != std::numeric_limits::max()) { return capabilities.currentExtent; } else { - VkExtent2D actualExtent = {WIDTH, HEIGHT}; + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); - 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; } } @@ -640,14 +611,14 @@ class HelloTriangleApplication { int i = 0; for (const auto& queueFamily : queueFamilies) { - if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { indices.graphicsFamily = i; } VkBool32 presentSupport = false; vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); - if (queueFamily.queueCount > 0 && presentSupport) { + if (presentSupport) { indices.presentFamily = i; } @@ -662,18 +633,14 @@ 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); + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); } return extensions; @@ -722,8 +689,8 @@ class HelloTriangleApplication { 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; + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; return VK_FALSE; } @@ -734,10 +701,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/13_framebuffers.cpp similarity index 68% rename from code/framebuffers.cpp rename to code/13_framebuffers.cpp index 598ccc92..95192d66 100644 --- a/code/framebuffers.cpp +++ b/code/13_framebuffers.cpp @@ -2,19 +2,22 @@ #include #include -#include -#include #include +#include #include #include #include +#include +#include +#include +#include #include -const int WIDTH = 800; -const int HEIGHT = 600; +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; const std::vector validationLayers = { - "VK_LAYER_LUNARG_standard_validation" + "VK_LAYER_KHRONOS_validation" }; const std::vector deviceExtensions = { @@ -27,86 +30,28 @@ const bool enableValidationLayers = false; const bool enableValidationLayers = true; #endif -VkResult CreateDebugReportCallbackEXT(VkInstance instance, const VkDebugReportCallbackCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugReportCallbackEXT* pCallback) { - auto func = (PFN_vkCreateDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT"); +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); if (func != nullptr) { - return func(instance, pCreateInfo, pAllocator, pCallback); + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); } else { return VK_ERROR_EXTENSION_NOT_PRESENT; } } -void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT callback, const VkAllocationCallbacks* pAllocator) { - auto func = (PFN_vkDestroyDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT"); +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); if (func != nullptr) { - func(instance, callback, pAllocator); + func(instance, debugMessenger, pAllocator); } } -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; + std::optional graphicsFamily; + std::optional presentFamily; bool isComplete() { - return graphicsFamily >= 0 && presentFamily >= 0; + return graphicsFamily.has_value() && presentFamily.has_value(); } }; @@ -122,31 +67,32 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + 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(); @@ -159,7 +105,7 @@ class HelloTriangleApplication { void initVulkan() { createInstance(); - setupDebugCallback(); + setupDebugMessenger(); createSurface(); pickPhysicalDevice(); createLogicalDevice(); @@ -174,6 +120,30 @@ class HelloTriangleApplication { 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) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); glfwDestroyWindow(window); @@ -185,7 +155,7 @@ class HelloTriangleApplication { throw std::runtime_error("validation layers requested, but not available!"); } - VkApplicationInfo appInfo = {}; + VkApplicationInfo appInfo{}; appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; appInfo.pApplicationName = "Hello Triangle"; appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); @@ -193,41 +163,53 @@ class HelloTriangleApplication { appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); appInfo.apiVersion = VK_API_VERSION_1_0; - VkInstanceCreateInfo createInfo = {}; + VkInstanceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; } else { createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } - void setupDebugCallback() { + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { 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; + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { - throw std::runtime_error("failed to set up debug callback!"); + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); } } 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!"); } } @@ -259,11 +241,11 @@ class HelloTriangleApplication { QueueFamilyIndices indices = findQueueFamilies(physicalDevice); std::vector queueCreateInfos; - std::set uniqueQueueFamilies = {indices.graphicsFamily, indices.presentFamily}; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; float queuePriority = 1.0f; - for (int queueFamily : uniqueQueueFamilies) { - VkDeviceQueueCreateInfo queueCreateInfo = {}; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; queueCreateInfo.queueFamilyIndex = queueFamily; queueCreateInfo.queueCount = 1; @@ -271,32 +253,32 @@ class HelloTriangleApplication { queueCreateInfos.push_back(queueCreateInfo); } - VkPhysicalDeviceFeatures deviceFeatures = {}; + VkPhysicalDeviceFeatures deviceFeatures{}; - VkDeviceCreateInfo createInfo = {}; + 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!"); } - vkGetDeviceQueue(device, indices.graphicsFamily, 0, &graphicsQueue); - vkGetDeviceQueue(device, indices.presentFamily, 0, &presentQueue); + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); } void createSwapChain() { @@ -311,7 +293,7 @@ class HelloTriangleApplication { imageCount = swapChainSupport.capabilities.maxImageCount; } - VkSwapchainCreateInfoKHR createInfo = {}; + VkSwapchainCreateInfoKHR createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; createInfo.surface = surface; @@ -323,7 +305,7 @@ class HelloTriangleApplication { createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; QueueFamilyIndices indices = findQueueFamilies(physicalDevice); - uint32_t queueFamilyIndices[] = {(uint32_t) indices.graphicsFamily, (uint32_t) indices.presentFamily}; + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; if (indices.graphicsFamily != indices.presentFamily) { createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; @@ -340,10 +322,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,10 +335,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++) { - VkImageViewCreateInfo createInfo = {}; + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; createInfo.image = swapChainImages[i]; createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; @@ -371,14 +353,14 @@ 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 createRenderPass() { - VkAttachmentDescription colorAttachment = {}; + VkAttachmentDescription colorAttachment{}; colorAttachment.format = swapChainImageFormat; colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; @@ -388,23 +370,23 @@ class HelloTriangleApplication { colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - VkAttachmentReference colorAttachmentRef = {}; + VkAttachmentReference colorAttachmentRef{}; colorAttachmentRef.attachment = 0; colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - VkSubpassDescription subpass = {}; + VkSubpassDescription subpass{}; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass.colorAttachmentCount = 1; subpass.pColorAttachments = &colorAttachmentRef; - VkRenderPassCreateInfo renderPassInfo = {}; + VkRenderPassCreateInfo renderPassInfo{}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; renderPassInfo.attachmentCount = 1; 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!"); } } @@ -413,18 +395,16 @@ 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 = {}; + 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 = {}; + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; fragShaderStageInfo.module = fragShaderModule; @@ -432,36 +412,22 @@ class HelloTriangleApplication { VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; - VkPipelineVertexInputStateCreateInfo vertexInputInfo = {}; + VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; vertexInputInfo.vertexBindingDescriptionCount = 0; vertexInputInfo.vertexAttributeDescriptionCount = 0; - VkPipelineInputAssemblyStateCreateInfo inputAssembly = {}; + 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 = {}; + 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 = {}; + VkPipelineRasterizationStateCreateInfo rasterizer{}; rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; rasterizer.depthClampEnable = VK_FALSE; rasterizer.rasterizerDiscardEnable = VK_FALSE; @@ -471,16 +437,16 @@ class HelloTriangleApplication { rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; rasterizer.depthBiasEnable = VK_FALSE; - VkPipelineMultisampleStateCreateInfo multisampling = {}; + VkPipelineMultisampleStateCreateInfo multisampling{}; multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; multisampling.sampleShadingEnable = VK_FALSE; multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; - VkPipelineColorBlendAttachmentState colorBlendAttachment = {}; + 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 = {}; + VkPipelineColorBlendStateCreateInfo colorBlending{}; colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; colorBlending.logicOpEnable = VK_FALSE; colorBlending.logicOp = VK_LOGIC_OP_COPY; @@ -491,16 +457,25 @@ class HelloTriangleApplication { colorBlending.blendConstants[2] = 0.0f; colorBlending.blendConstants[3] = 0.0f; - VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; + std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; 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 = {}; + VkGraphicsPipelineCreateInfo pipelineInfo{}; pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; pipelineInfo.stageCount = 2; pipelineInfo.pStages = shaderStages; @@ -510,25 +485,29 @@ class HelloTriangleApplication { pipelineInfo.pRasterizationState = &rasterizer; pipelineInfo.pMultisampleState = &multisampling; pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = &dynamicState; 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) { + 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[] = { swapChainImageViews[i] }; - VkFramebufferCreateInfo framebufferInfo = {}; + VkFramebufferCreateInfo framebufferInfo{}; framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; framebufferInfo.renderPass = renderPass; framebufferInfo.attachmentCount = 1; @@ -537,34 +516,29 @@ 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!"); } } } - void createShaderModule(const std::vector& code, VDeleter& shaderModule) { - VkShaderModuleCreateInfo createInfo = {}; + 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) { - 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) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { return availableFormat; } } @@ -572,29 +546,31 @@ class HelloTriangleApplication { return availableFormats[0]; } - VkPresentModeKHR chooseSwapPresentMode(const std::vector availablePresentModes) { - VkPresentModeKHR bestMode = VK_PRESENT_MODE_FIFO_KHR; - + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { 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; + return VK_PRESENT_MODE_FIFO_KHR; } VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { if (capabilities.currentExtent.width != std::numeric_limits::max()) { return capabilities.currentExtent; } else { - VkExtent2D actualExtent = {WIDTH, HEIGHT}; + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); - 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; } } @@ -664,14 +640,14 @@ class HelloTriangleApplication { int i = 0; for (const auto& queueFamily : queueFamilies) { - if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { indices.graphicsFamily = i; } VkBool32 presentSupport = false; vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); - if (queueFamily.queueCount > 0 && presentSupport) { + if (presentSupport) { indices.presentFamily = i; } @@ -686,18 +662,14 @@ 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); + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); } return extensions; @@ -746,8 +718,8 @@ class HelloTriangleApplication { 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; + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; return VK_FALSE; } @@ -758,10 +730,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 64% rename from code/command_buffers.cpp rename to code/14_command_buffers.cpp index f1da66b0..8332b5b1 100644 --- a/code/command_buffers.cpp +++ b/code/14_command_buffers.cpp @@ -2,19 +2,22 @@ #include #include -#include -#include #include +#include #include #include #include +#include +#include +#include +#include #include -const int WIDTH = 800; -const int HEIGHT = 600; +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; const std::vector validationLayers = { - "VK_LAYER_LUNARG_standard_validation" + "VK_LAYER_KHRONOS_validation" }; const std::vector deviceExtensions = { @@ -27,86 +30,28 @@ const bool enableValidationLayers = false; const bool enableValidationLayers = true; #endif -VkResult CreateDebugReportCallbackEXT(VkInstance instance, const VkDebugReportCallbackCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugReportCallbackEXT* pCallback) { - auto func = (PFN_vkCreateDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT"); +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); if (func != nullptr) { - return func(instance, pCreateInfo, pAllocator, pCallback); + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); } else { return VK_ERROR_EXTENSION_NOT_PRESENT; } } -void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT callback, const VkAllocationCallbacks* pAllocator) { - auto func = (PFN_vkDestroyDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT"); +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); if (func != nullptr) { - func(instance, callback, pAllocator); + func(instance, debugMessenger, pAllocator); } } -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; + std::optional graphicsFamily; + std::optional presentFamily; bool isComplete() { - return graphicsFamily >= 0 && presentFamily >= 0; + return graphicsFamily.has_value() && presentFamily.has_value(); } }; @@ -122,34 +67,35 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + 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 commandBuffers; + std::vector swapChainImageViews; + std::vector swapChainFramebuffers; + + VkRenderPass renderPass; + VkPipelineLayout pipelineLayout; + VkPipeline graphicsPipeline; + + VkCommandPool commandPool; + VkCommandBuffer commandBuffer; void initWindow() { glfwInit(); @@ -162,7 +108,7 @@ class HelloTriangleApplication { void initVulkan() { createInstance(); - setupDebugCallback(); + setupDebugMessenger(); createSurface(); pickPhysicalDevice(); createLogicalDevice(); @@ -172,13 +118,39 @@ class HelloTriangleApplication { createGraphicsPipeline(); createFramebuffers(); createCommandPool(); - createCommandBuffers(); + createCommandBuffer(); } void mainLoop() { 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) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); glfwDestroyWindow(window); @@ -190,7 +162,7 @@ class HelloTriangleApplication { throw std::runtime_error("validation layers requested, but not available!"); } - VkApplicationInfo appInfo = {}; + VkApplicationInfo appInfo{}; appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; appInfo.pApplicationName = "Hello Triangle"; appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); @@ -198,41 +170,53 @@ class HelloTriangleApplication { appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); appInfo.apiVersion = VK_API_VERSION_1_0; - VkInstanceCreateInfo createInfo = {}; + VkInstanceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; } else { createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } - void setupDebugCallback() { + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { 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; + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { - throw std::runtime_error("failed to set up debug callback!"); + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); } } 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!"); } } @@ -264,11 +248,11 @@ class HelloTriangleApplication { QueueFamilyIndices indices = findQueueFamilies(physicalDevice); std::vector queueCreateInfos; - std::set uniqueQueueFamilies = {indices.graphicsFamily, indices.presentFamily}; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; float queuePriority = 1.0f; - for (int queueFamily : uniqueQueueFamilies) { - VkDeviceQueueCreateInfo queueCreateInfo = {}; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; queueCreateInfo.queueFamilyIndex = queueFamily; queueCreateInfo.queueCount = 1; @@ -276,32 +260,32 @@ class HelloTriangleApplication { queueCreateInfos.push_back(queueCreateInfo); } - VkPhysicalDeviceFeatures deviceFeatures = {}; + VkPhysicalDeviceFeatures deviceFeatures{}; - VkDeviceCreateInfo createInfo = {}; + 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!"); } - vkGetDeviceQueue(device, indices.graphicsFamily, 0, &graphicsQueue); - vkGetDeviceQueue(device, indices.presentFamily, 0, &presentQueue); + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); } void createSwapChain() { @@ -316,7 +300,7 @@ class HelloTriangleApplication { imageCount = swapChainSupport.capabilities.maxImageCount; } - VkSwapchainCreateInfoKHR createInfo = {}; + VkSwapchainCreateInfoKHR createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; createInfo.surface = surface; @@ -328,7 +312,7 @@ class HelloTriangleApplication { createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; QueueFamilyIndices indices = findQueueFamilies(physicalDevice); - uint32_t queueFamilyIndices[] = {(uint32_t) indices.graphicsFamily, (uint32_t) indices.presentFamily}; + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; if (indices.graphicsFamily != indices.presentFamily) { createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; @@ -345,10 +329,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,10 +342,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++) { - VkImageViewCreateInfo createInfo = {}; + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; createInfo.image = swapChainImages[i]; createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; @@ -376,14 +360,14 @@ 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 createRenderPass() { - VkAttachmentDescription colorAttachment = {}; + VkAttachmentDescription colorAttachment{}; colorAttachment.format = swapChainImageFormat; colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; @@ -393,23 +377,23 @@ class HelloTriangleApplication { colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - VkAttachmentReference colorAttachmentRef = {}; + VkAttachmentReference colorAttachmentRef{}; colorAttachmentRef.attachment = 0; colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - VkSubpassDescription subpass = {}; + VkSubpassDescription subpass{}; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass.colorAttachmentCount = 1; subpass.pColorAttachments = &colorAttachmentRef; - VkRenderPassCreateInfo renderPassInfo = {}; + VkRenderPassCreateInfo renderPassInfo{}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; renderPassInfo.attachmentCount = 1; 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!"); } } @@ -418,18 +402,16 @@ 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 = {}; + 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 = {}; + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; fragShaderStageInfo.module = fragShaderModule; @@ -437,36 +419,22 @@ class HelloTriangleApplication { VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; - VkPipelineVertexInputStateCreateInfo vertexInputInfo = {}; + VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; vertexInputInfo.vertexBindingDescriptionCount = 0; vertexInputInfo.vertexAttributeDescriptionCount = 0; - VkPipelineInputAssemblyStateCreateInfo inputAssembly = {}; + 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 = {}; + 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 = {}; + VkPipelineRasterizationStateCreateInfo rasterizer{}; rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; rasterizer.depthClampEnable = VK_FALSE; rasterizer.rasterizerDiscardEnable = VK_FALSE; @@ -476,16 +444,16 @@ class HelloTriangleApplication { rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; rasterizer.depthBiasEnable = VK_FALSE; - VkPipelineMultisampleStateCreateInfo multisampling = {}; + VkPipelineMultisampleStateCreateInfo multisampling{}; multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; multisampling.sampleShadingEnable = VK_FALSE; multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; - VkPipelineColorBlendAttachmentState colorBlendAttachment = {}; + 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 = {}; + VkPipelineColorBlendStateCreateInfo colorBlending{}; colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; colorBlending.logicOpEnable = VK_FALSE; colorBlending.logicOp = VK_LOGIC_OP_COPY; @@ -496,16 +464,25 @@ class HelloTriangleApplication { colorBlending.blendConstants[2] = 0.0f; colorBlending.blendConstants[3] = 0.0f; - VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; + std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; 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 = {}; + VkGraphicsPipelineCreateInfo pipelineInfo{}; pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; pipelineInfo.stageCount = 2; pipelineInfo.pStages = shaderStages; @@ -515,25 +492,29 @@ class HelloTriangleApplication { pipelineInfo.pRasterizationState = &rasterizer; pipelineInfo.pMultisampleState = &multisampling; pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = &dynamicState; 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) { + 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[] = { swapChainImageViews[i] }; - VkFramebufferCreateInfo framebufferInfo = {}; + VkFramebufferCreateInfo framebufferInfo{}; framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; framebufferInfo.renderPass = renderPass; framebufferInfo.attachmentCount = 1; @@ -542,7 +523,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!"); } } @@ -551,82 +532,91 @@ class HelloTriangleApplication { void createCommandPool() { QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); - VkCommandPoolCreateInfo poolInfo = {}; + VkCommandPoolCreateInfo poolInfo{}; poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; - poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily; - - if (vkCreateCommandPool(device, &poolInfo, nullptr, commandPool.replace()) != VK_SUCCESS) { + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); + + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { throw std::runtime_error("failed to create command pool!"); } } - void createCommandBuffers() { - commandBuffers.resize(swapChainFramebuffers.size()); - - VkCommandBufferAllocateInfo allocInfo = {}; + void createCommandBuffer() { + 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(); + allocInfo.commandBufferCount = 1; - if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) { + if (vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer) != 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; + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; - vkBeginCommandBuffer(commandBuffers[i], &beginInfo); + if (vkBeginCommandBuffer(commandBuffer, &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; + VkRenderPassBeginInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = renderPass; + renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex]; + renderPassInfo.renderArea.offset = {0, 0}; + renderPassInfo.renderArea.extent = swapChainExtent; - VkClearValue clearColor = {0.0f, 0.0f, 0.0f, 1.0f}; - renderPassInfo.clearValueCount = 1; - renderPassInfo.pClearValues = &clearColor; + VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; + renderPassInfo.clearValueCount = 1; + renderPassInfo.pClearValues = &clearColor; - vkCmdBeginRenderPass(commandBuffers[i], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); - vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); - vkCmdDraw(commandBuffers[i], 3, 1, 0, 0); + 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; + vkCmdSetViewport(commandBuffer, 0, 1, &viewport); - vkCmdEndRenderPass(commandBuffers[i]); + VkRect2D scissor{}; + scissor.offset = {0, 0}; + scissor.extent = swapChainExtent; + vkCmdSetScissor(commandBuffer, 0, 1, &scissor); - if (vkEndCommandBuffer(commandBuffers[i]) != VK_SUCCESS) { - throw std::runtime_error("failed to record command buffer!"); - } + vkCmdDraw(commandBuffer, 3, 1, 0, 0); + + vkCmdEndRenderPass(commandBuffer); + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); } } - void createShaderModule(const std::vector& code, VDeleter& shaderModule) { - VkShaderModuleCreateInfo createInfo = {}; + 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) { - 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) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { return availableFormat; } } @@ -634,29 +624,31 @@ class HelloTriangleApplication { return availableFormats[0]; } - VkPresentModeKHR chooseSwapPresentMode(const std::vector availablePresentModes) { - VkPresentModeKHR bestMode = VK_PRESENT_MODE_FIFO_KHR; - + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { 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; + return VK_PRESENT_MODE_FIFO_KHR; } VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { if (capabilities.currentExtent.width != std::numeric_limits::max()) { return capabilities.currentExtent; } else { - VkExtent2D actualExtent = {WIDTH, HEIGHT}; + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); - 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; } } @@ -726,14 +718,14 @@ class HelloTriangleApplication { int i = 0; for (const auto& queueFamily : queueFamilies) { - if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { indices.graphicsFamily = i; } VkBool32 presentSupport = false; vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); - if (queueFamily.queueCount > 0 && presentSupport) { + if (presentSupport) { indices.presentFamily = i; } @@ -748,18 +740,14 @@ 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); + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); } return extensions; @@ -808,8 +796,8 @@ class HelloTriangleApplication { 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; + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; return VK_FALSE; } @@ -820,10 +808,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 63% rename from code/hello_triangle.cpp rename to code/15_hello_triangle.cpp index d09876d5..bab9fd63 100644 --- a/code/hello_triangle.cpp +++ b/code/15_hello_triangle.cpp @@ -2,19 +2,24 @@ #include #include -#include -#include #include +#include #include #include #include +#include +#include +#include +#include #include -const int WIDTH = 800; -const int HEIGHT = 600; +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const int MAX_FRAMES_IN_FLIGHT = 2; const std::vector validationLayers = { - "VK_LAYER_LUNARG_standard_validation" + "VK_LAYER_KHRONOS_validation" }; const std::vector deviceExtensions = { @@ -27,86 +32,28 @@ const bool enableValidationLayers = false; const bool enableValidationLayers = true; #endif -VkResult CreateDebugReportCallbackEXT(VkInstance instance, const VkDebugReportCallbackCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugReportCallbackEXT* pCallback) { - auto func = (PFN_vkCreateDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT"); +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); if (func != nullptr) { - return func(instance, pCreateInfo, pAllocator, pCallback); + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); } else { return VK_ERROR_EXTENSION_NOT_PRESENT; } } -void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT callback, const VkAllocationCallbacks* pAllocator) { - auto func = (PFN_vkDestroyDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT"); +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); if (func != nullptr) { - func(instance, callback, pAllocator); + func(instance, debugMessenger, pAllocator); } } -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; + std::optional graphicsFamily; + std::optional presentFamily; bool isComplete() { - return graphicsFamily >= 0 && presentFamily >= 0; + return graphicsFamily.has_value() && presentFamily.has_value(); } }; @@ -122,37 +69,39 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + 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 commandBuffers; - - VDeleter imageAvailableSemaphore{device, vkDestroySemaphore}; - VDeleter renderFinishedSemaphore{device, vkDestroySemaphore}; + std::vector swapChainImageViews; + std::vector swapChainFramebuffers; + + VkRenderPass renderPass; + VkPipelineLayout pipelineLayout; + VkPipeline graphicsPipeline; + + VkCommandPool commandPool; + VkCommandBuffer commandBuffer; + + VkSemaphore imageAvailableSemaphore; + VkSemaphore renderFinishedSemaphore; + VkFence inFlightFence; void initWindow() { glfwInit(); @@ -165,7 +114,7 @@ class HelloTriangleApplication { void initVulkan() { createInstance(); - setupDebugCallback(); + setupDebugMessenger(); createSurface(); pickPhysicalDevice(); createLogicalDevice(); @@ -175,8 +124,8 @@ class HelloTriangleApplication { createGraphicsPipeline(); createFramebuffers(); createCommandPool(); - createCommandBuffers(); - createSemaphores(); + createCommandBuffer(); + createSyncObjects(); } void mainLoop() { @@ -186,6 +135,36 @@ class HelloTriangleApplication { } vkDeviceWaitIdle(device); + } + + void cleanup() { + vkDestroySemaphore(device, renderFinishedSemaphore, nullptr); + vkDestroySemaphore(device, imageAvailableSemaphore, nullptr); + vkDestroyFence(device, inFlightFence, 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) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); glfwDestroyWindow(window); @@ -197,7 +176,7 @@ class HelloTriangleApplication { throw std::runtime_error("validation layers requested, but not available!"); } - VkApplicationInfo appInfo = {}; + VkApplicationInfo appInfo{}; appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; appInfo.pApplicationName = "Hello Triangle"; appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); @@ -205,41 +184,53 @@ class HelloTriangleApplication { appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); appInfo.apiVersion = VK_API_VERSION_1_0; - VkInstanceCreateInfo createInfo = {}; + VkInstanceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; } else { createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } - void setupDebugCallback() { + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { 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; + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { - throw std::runtime_error("failed to set up debug callback!"); + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); } } 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!"); } } @@ -271,11 +262,11 @@ class HelloTriangleApplication { QueueFamilyIndices indices = findQueueFamilies(physicalDevice); std::vector queueCreateInfos; - std::set uniqueQueueFamilies = {indices.graphicsFamily, indices.presentFamily}; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; float queuePriority = 1.0f; - for (int queueFamily : uniqueQueueFamilies) { - VkDeviceQueueCreateInfo queueCreateInfo = {}; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; queueCreateInfo.queueFamilyIndex = queueFamily; queueCreateInfo.queueCount = 1; @@ -283,32 +274,32 @@ class HelloTriangleApplication { queueCreateInfos.push_back(queueCreateInfo); } - VkPhysicalDeviceFeatures deviceFeatures = {}; + VkPhysicalDeviceFeatures deviceFeatures{}; - VkDeviceCreateInfo createInfo = {}; + 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!"); } - vkGetDeviceQueue(device, indices.graphicsFamily, 0, &graphicsQueue); - vkGetDeviceQueue(device, indices.presentFamily, 0, &presentQueue); + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); } void createSwapChain() { @@ -323,7 +314,7 @@ class HelloTriangleApplication { imageCount = swapChainSupport.capabilities.maxImageCount; } - VkSwapchainCreateInfoKHR createInfo = {}; + VkSwapchainCreateInfoKHR createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; createInfo.surface = surface; @@ -335,7 +326,7 @@ class HelloTriangleApplication { createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; QueueFamilyIndices indices = findQueueFamilies(physicalDevice); - uint32_t queueFamilyIndices[] = {(uint32_t) indices.graphicsFamily, (uint32_t) indices.presentFamily}; + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; if (indices.graphicsFamily != indices.presentFamily) { createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; @@ -352,7 +343,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,10 +356,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++) { - VkImageViewCreateInfo createInfo = {}; + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; createInfo.image = swapChainImages[i]; createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; @@ -383,14 +374,14 @@ 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 createRenderPass() { - VkAttachmentDescription colorAttachment = {}; + VkAttachmentDescription colorAttachment{}; colorAttachment.format = swapChainImageFormat; colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; @@ -400,24 +391,24 @@ class HelloTriangleApplication { colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - VkAttachmentReference colorAttachmentRef = {}; + VkAttachmentReference colorAttachmentRef{}; colorAttachmentRef.attachment = 0; colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - VkSubpassDescription subpass = {}; + VkSubpassDescription subpass{}; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass.colorAttachmentCount = 1; subpass.pColorAttachments = &colorAttachmentRef; - VkSubpassDependency dependency = {}; + 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; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; - VkRenderPassCreateInfo renderPassInfo = {}; + VkRenderPassCreateInfo renderPassInfo{}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; renderPassInfo.attachmentCount = 1; renderPassInfo.pAttachments = &colorAttachment; @@ -426,7 +417,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,18 +426,16 @@ 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 = {}; + 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 = {}; + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; fragShaderStageInfo.module = fragShaderModule; @@ -454,36 +443,22 @@ class HelloTriangleApplication { VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; - VkPipelineVertexInputStateCreateInfo vertexInputInfo = {}; + VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; vertexInputInfo.vertexBindingDescriptionCount = 0; vertexInputInfo.vertexAttributeDescriptionCount = 0; - VkPipelineInputAssemblyStateCreateInfo inputAssembly = {}; + 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 = {}; + 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 = {}; + VkPipelineRasterizationStateCreateInfo rasterizer{}; rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; rasterizer.depthClampEnable = VK_FALSE; rasterizer.rasterizerDiscardEnable = VK_FALSE; @@ -493,16 +468,16 @@ class HelloTriangleApplication { rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; rasterizer.depthBiasEnable = VK_FALSE; - VkPipelineMultisampleStateCreateInfo multisampling = {}; + VkPipelineMultisampleStateCreateInfo multisampling{}; multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; multisampling.sampleShadingEnable = VK_FALSE; multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; - VkPipelineColorBlendAttachmentState colorBlendAttachment = {}; + 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 = {}; + VkPipelineColorBlendStateCreateInfo colorBlending{}; colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; colorBlending.logicOpEnable = VK_FALSE; colorBlending.logicOp = VK_LOGIC_OP_COPY; @@ -513,16 +488,25 @@ class HelloTriangleApplication { colorBlending.blendConstants[2] = 0.0f; colorBlending.blendConstants[3] = 0.0f; - VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; + std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; 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 = {}; + VkGraphicsPipelineCreateInfo pipelineInfo{}; pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; pipelineInfo.stageCount = 2; pipelineInfo.pStages = shaderStages; @@ -532,25 +516,29 @@ class HelloTriangleApplication { pipelineInfo.pRasterizationState = &rasterizer; pipelineInfo.pMultisampleState = &multisampling; pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = &dynamicState; 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) { + 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[] = { swapChainImageViews[i] }; - VkFramebufferCreateInfo framebufferInfo = {}; + VkFramebufferCreateInfo framebufferInfo{}; framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; framebufferInfo.renderPass = renderPass; framebufferInfo.attachmentCount = 1; @@ -559,7 +547,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!"); } } @@ -568,76 +556,101 @@ class HelloTriangleApplication { void createCommandPool() { QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); - VkCommandPoolCreateInfo poolInfo = {}; + VkCommandPoolCreateInfo poolInfo{}; poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; - poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); - 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() { - commandBuffers.resize(swapChainFramebuffers.size()); - - VkCommandBufferAllocateInfo allocInfo = {}; + void createCommandBuffer() { + 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(); + allocInfo.commandBufferCount = 1; - if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) { + if (vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer) != 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; + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; - vkBeginCommandBuffer(commandBuffers[i], &beginInfo); + if (vkBeginCommandBuffer(commandBuffer, &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; + VkRenderPassBeginInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = renderPass; + renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex]; + renderPassInfo.renderArea.offset = {0, 0}; + renderPassInfo.renderArea.extent = swapChainExtent; - VkClearValue clearColor = {0.0f, 0.0f, 0.0f, 1.0f}; - renderPassInfo.clearValueCount = 1; - renderPassInfo.pClearValues = &clearColor; + VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; + renderPassInfo.clearValueCount = 1; + renderPassInfo.pClearValues = &clearColor; - vkCmdBeginRenderPass(commandBuffers[i], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); - vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); - vkCmdDraw(commandBuffers[i], 3, 1, 0, 0); + 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; + vkCmdSetViewport(commandBuffer, 0, 1, &viewport); - vkCmdEndRenderPass(commandBuffers[i]); + VkRect2D scissor{}; + scissor.offset = {0, 0}; + scissor.extent = swapChainExtent; + vkCmdSetScissor(commandBuffer, 0, 1, &scissor); - if (vkEndCommandBuffer(commandBuffers[i]) != VK_SUCCESS) { - throw std::runtime_error("failed to record command buffer!"); - } + vkCmdDraw(commandBuffer, 3, 1, 0, 0); + + vkCmdEndRenderPass(commandBuffer); + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); } } - - void createSemaphores() { - VkSemaphoreCreateInfo semaphoreInfo = {}; + + void createSyncObjects() { + 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!"); + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphore) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphore) != VK_SUCCESS || + vkCreateFence(device, &fenceInfo, nullptr, &inFlightFence) != VK_SUCCESS) { + throw std::runtime_error("failed to create synchronization objects for a frame!"); } + } void drawFrame() { + vkWaitForFences(device, 1, &inFlightFence, VK_TRUE, UINT64_MAX); + vkResetFences(device, 1, &inFlightFence); + uint32_t imageIndex; - vkAcquireNextImageKHR(device, swapChain, std::numeric_limits::max(), imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex); + vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex); - VkSubmitInfo submitInfo = {}; + vkResetCommandBuffer(commandBuffer, /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffer, imageIndex); + + VkSubmitInfo submitInfo{}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; VkSemaphore waitSemaphores[] = {imageAvailableSemaphore}; @@ -647,17 +660,17 @@ class HelloTriangleApplication { submitInfo.pWaitDstStageMask = waitStages; submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &commandBuffers[imageIndex]; + submitInfo.pCommandBuffers = &commandBuffer; VkSemaphore signalSemaphores[] = {renderFinishedSemaphore}; submitInfo.signalSemaphoreCount = 1; submitInfo.pSignalSemaphores = signalSemaphores; - if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFence) != VK_SUCCESS) { throw std::runtime_error("failed to submit draw command buffer!"); } - VkPresentInfoKHR presentInfo = {}; + VkPresentInfoKHR presentInfo{}; presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; presentInfo.waitSemaphoreCount = 1; @@ -672,28 +685,23 @@ class HelloTriangleApplication { vkQueuePresentKHR(presentQueue, &presentInfo); } - void createShaderModule(const std::vector& code, VDeleter& shaderModule) { - VkShaderModuleCreateInfo createInfo = {}; + 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) { - 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) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { return availableFormat; } } @@ -701,28 +709,30 @@ class HelloTriangleApplication { return availableFormats[0]; } - VkPresentModeKHR chooseSwapPresentMode(const std::vector availablePresentModes) { - VkPresentModeKHR bestMode = VK_PRESENT_MODE_FIFO_KHR; - + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { 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; + return VK_PRESENT_MODE_FIFO_KHR; } VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { if (capabilities.currentExtent.width != std::numeric_limits::max()) { return capabilities.currentExtent; } else { - VkExtent2D actualExtent = {WIDTH, HEIGHT}; + int width, height; + glfwGetFramebufferSize(window, &width, &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)); + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); return actualExtent; } @@ -793,14 +803,14 @@ class HelloTriangleApplication { int i = 0; for (const auto& queueFamily : queueFamilies) { - if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { indices.graphicsFamily = i; } VkBool32 presentSupport = false; vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); - if (queueFamily.queueCount > 0 && presentSupport) { + if (presentSupport) { indices.presentFamily = i; } @@ -815,18 +825,14 @@ 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); + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); } return extensions; @@ -875,8 +881,8 @@ class HelloTriangleApplication { 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; + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; return VK_FALSE; } @@ -887,10 +893,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/16_frames_in_flight.cpp b/code/16_frames_in_flight.cpp new file mode 100644 index 00000000..c5746983 --- /dev/null +++ b/code/16_frames_in_flight.cpp @@ -0,0 +1,914 @@ +#define GLFW_INCLUDE_VULKAN +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const int MAX_FRAMES_IN_FLIGHT = 2; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; + +class HelloTriangleApplication { +public: + void run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + 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; + VkPipelineLayout pipelineLayout; + VkPipeline graphicsPipeline; + + VkCommandPool commandPool; + std::vector commandBuffers; + + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + std::vector inFlightFences; + uint32_t currentFrame = 0; + + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createGraphicsPipeline(); + createFramebuffers(); + createCommandPool(); + createCommandBuffers(); + createSyncObjects(); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + drawFrame(); + } + + 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) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + 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(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; + } else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + 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.value(), indices.presentFamily.value()}; + + float queuePriority = 1.0f; + for (uint32_t 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{}; + + 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.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 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[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + 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; + + createInfo.oldSwapchain = VK_NULL_HANDLE; + + 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 (size_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.image = swapChainImages[i]; + createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + createInfo.format = swapChainImageFormat; + createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + createInfo.subresourceRange.baseMipLevel = 0; + createInfo.subresourceRange.levelCount = 1; + createInfo.subresourceRange.baseArrayLayer = 0; + createInfo.subresourceRange.layerCount = 1; + + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create image views!"); + } + } + } + + 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; + + VkAttachmentReference colorAttachmentRef{}; + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; + + 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_WRITE_BIT; + + VkRenderPassCreateInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassInfo.attachmentCount = 1; + renderPassInfo.pAttachments = &colorAttachment; + 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 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; + vertexInputInfo.vertexBindingDescriptionCount = 0; + vertexInputInfo.vertexAttributeDescriptionCount = 0; + + VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; + inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + inputAssembly.primitiveRestartEnable = VK_FALSE; + + VkPipelineViewportStateCreateInfo viewportState{}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.scissorCount = 1; + + 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_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; + + 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; + + std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.setLayoutCount = 0; + pipelineLayoutInfo.pushConstantRangeCount = 0; + + 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.pDynamicState = &dynamicState; + 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++) { + 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 createCommandPool() { + QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); + + VkCommandPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); + + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create command pool!"); + } + } + + void createCommandBuffers() { + commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); + + 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!"); + } + } + + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + + if (vkBeginCommandBuffer(commandBuffer, &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[imageIndex]; + renderPassInfo.renderArea.offset = {0, 0}; + renderPassInfo.renderArea.extent = swapChainExtent; + + VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; + renderPassInfo.clearValueCount = 1; + renderPassInfo.pClearValues = &clearColor; + + vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); + + 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; + vkCmdSetViewport(commandBuffer, 0, 1, &viewport); + + VkRect2D scissor{}; + scissor.offset = {0, 0}; + scissor.extent = swapChainExtent; + vkCmdSetScissor(commandBuffer, 0, 1, &scissor); + + vkCmdDraw(commandBuffer, 3, 1, 0, 0); + + vkCmdEndRenderPass(commandBuffer); + + if (vkEndCommandBuffer(commandBuffer) != 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 drawFrame() { + vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX); + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + uint32_t imageIndex; + vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + + vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffers[currentFrame], 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[currentFrame]; + + VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[currentFrame]}; + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = signalSemaphores; + + 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; + + vkQueuePresentKHR(presentQueue, &presentInfo); + + 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) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; + } + + 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::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.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(); + } + + return indices.isComplete() && extensionsSupported && swapChainAdequate; + } + + 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.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (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_UTILS_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(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << 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/swap_chain_recreation.cpp b/code/17_swap_chain_recreation.cpp similarity index 62% rename from code/swap_chain_recreation.cpp rename to code/17_swap_chain_recreation.cpp index 21f09e57..05854eff 100644 --- a/code/swap_chain_recreation.cpp +++ b/code/17_swap_chain_recreation.cpp @@ -2,19 +2,24 @@ #include #include -#include -#include #include +#include #include #include #include +#include +#include +#include +#include #include -const int WIDTH = 800; -const int HEIGHT = 600; +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const int MAX_FRAMES_IN_FLIGHT = 2; const std::vector validationLayers = { - "VK_LAYER_LUNARG_standard_validation" + "VK_LAYER_KHRONOS_validation" }; const std::vector deviceExtensions = { @@ -27,86 +32,28 @@ const bool enableValidationLayers = false; const bool enableValidationLayers = true; #endif -VkResult CreateDebugReportCallbackEXT(VkInstance instance, const VkDebugReportCallbackCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugReportCallbackEXT* pCallback) { - auto func = (PFN_vkCreateDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT"); +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); if (func != nullptr) { - return func(instance, pCreateInfo, pAllocator, pCallback); + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); } else { return VK_ERROR_EXTENSION_NOT_PRESENT; } } -void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT callback, const VkAllocationCallbacks* pAllocator) { - auto func = (PFN_vkDestroyDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT"); +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); if (func != nullptr) { - func(instance, callback, pAllocator); + func(instance, debugMessenger, pAllocator); } } -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; + std::optional graphicsFamily; + std::optional presentFamily; bool isComplete() { - return graphicsFamily >= 0 && presentFamily >= 0; + return graphicsFamily.has_value() && presentFamily.has_value(); } }; @@ -122,37 +69,42 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + 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; + uint32_t currentFrame = 0; + + bool framebufferResized = false; void initWindow() { glfwInit(); @@ -160,14 +112,18 @@ 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() { createInstance(); - setupDebugCallback(); + setupDebugMessenger(); createSurface(); pickPhysicalDevice(); createLogicalDevice(); @@ -178,7 +134,7 @@ class HelloTriangleApplication { createFramebuffers(); createCommandPool(); createCommandBuffers(); - createSemaphores(); + createSyncObjects(); } void mainLoop() { @@ -188,28 +144,65 @@ class HelloTriangleApplication { } vkDeviceWaitIdle(device); + } - glfwDestroyWindow(window); + void cleanupSwapChain() { + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } - glfwTerminate(); + 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(); + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + + vkDestroyRenderPass(device, renderPass, 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) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); } void recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + vkDeviceWaitIdle(device); + cleanupSwapChain(); + createSwapChain(); createImageViews(); - createRenderPass(); - createGraphicsPipeline(); createFramebuffers(); - createCommandBuffers(); } void createInstance() { @@ -217,7 +210,7 @@ class HelloTriangleApplication { throw std::runtime_error("validation layers requested, but not available!"); } - VkApplicationInfo appInfo = {}; + VkApplicationInfo appInfo{}; appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; appInfo.pApplicationName = "Hello Triangle"; appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); @@ -225,41 +218,53 @@ class HelloTriangleApplication { appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); appInfo.apiVersion = VK_API_VERSION_1_0; - VkInstanceCreateInfo createInfo = {}; + VkInstanceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; } else { createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } - void setupDebugCallback() { + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { 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; + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { - throw std::runtime_error("failed to set up debug callback!"); + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); } } 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!"); } } @@ -291,11 +296,11 @@ class HelloTriangleApplication { QueueFamilyIndices indices = findQueueFamilies(physicalDevice); std::vector queueCreateInfos; - std::set uniqueQueueFamilies = {indices.graphicsFamily, indices.presentFamily}; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; float queuePriority = 1.0f; - for (int queueFamily : uniqueQueueFamilies) { - VkDeviceQueueCreateInfo queueCreateInfo = {}; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; queueCreateInfo.queueFamilyIndex = queueFamily; queueCreateInfo.queueCount = 1; @@ -303,32 +308,32 @@ class HelloTriangleApplication { queueCreateInfos.push_back(queueCreateInfo); } - VkPhysicalDeviceFeatures deviceFeatures = {}; + VkPhysicalDeviceFeatures deviceFeatures{}; - VkDeviceCreateInfo createInfo = {}; + 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!"); } - vkGetDeviceQueue(device, indices.graphicsFamily, 0, &graphicsQueue); - vkGetDeviceQueue(device, indices.presentFamily, 0, &presentQueue); + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); } void createSwapChain() { @@ -343,7 +348,7 @@ class HelloTriangleApplication { imageCount = swapChainSupport.capabilities.maxImageCount; } - VkSwapchainCreateInfoKHR createInfo = {}; + VkSwapchainCreateInfoKHR createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; createInfo.surface = surface; @@ -355,7 +360,7 @@ class HelloTriangleApplication { createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; QueueFamilyIndices indices = findQueueFamilies(physicalDevice); - uint32_t queueFamilyIndices[] = {(uint32_t) indices.graphicsFamily, (uint32_t) indices.presentFamily}; + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; if (indices.graphicsFamily != indices.presentFamily) { createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; @@ -370,16 +375,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,10 +388,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++) { - VkImageViewCreateInfo createInfo = {}; + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; createInfo.image = swapChainImages[i]; createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; @@ -407,14 +406,14 @@ 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 createRenderPass() { - VkAttachmentDescription colorAttachment = {}; + VkAttachmentDescription colorAttachment{}; colorAttachment.format = swapChainImageFormat; colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; @@ -424,24 +423,24 @@ class HelloTriangleApplication { colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - VkAttachmentReference colorAttachmentRef = {}; + VkAttachmentReference colorAttachmentRef{}; colorAttachmentRef.attachment = 0; colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - VkSubpassDescription subpass = {}; + VkSubpassDescription subpass{}; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass.colorAttachmentCount = 1; subpass.pColorAttachments = &colorAttachmentRef; - VkSubpassDependency dependency = {}; + 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; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; - VkRenderPassCreateInfo renderPassInfo = {}; + VkRenderPassCreateInfo renderPassInfo{}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; renderPassInfo.attachmentCount = 1; renderPassInfo.pAttachments = &colorAttachment; @@ -450,7 +449,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,18 +458,16 @@ 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 = {}; + 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 = {}; + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; fragShaderStageInfo.module = fragShaderModule; @@ -478,36 +475,22 @@ class HelloTriangleApplication { VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; - VkPipelineVertexInputStateCreateInfo vertexInputInfo = {}; + VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; vertexInputInfo.vertexBindingDescriptionCount = 0; vertexInputInfo.vertexAttributeDescriptionCount = 0; - VkPipelineInputAssemblyStateCreateInfo inputAssembly = {}; + 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 = {}; + 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 = {}; + VkPipelineRasterizationStateCreateInfo rasterizer{}; rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; rasterizer.depthClampEnable = VK_FALSE; rasterizer.rasterizerDiscardEnable = VK_FALSE; @@ -517,16 +500,16 @@ class HelloTriangleApplication { rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; rasterizer.depthBiasEnable = VK_FALSE; - VkPipelineMultisampleStateCreateInfo multisampling = {}; + VkPipelineMultisampleStateCreateInfo multisampling{}; multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; multisampling.sampleShadingEnable = VK_FALSE; multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; - VkPipelineColorBlendAttachmentState colorBlendAttachment = {}; + 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 = {}; + VkPipelineColorBlendStateCreateInfo colorBlending{}; colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; colorBlending.logicOpEnable = VK_FALSE; colorBlending.logicOp = VK_LOGIC_OP_COPY; @@ -537,16 +520,25 @@ class HelloTriangleApplication { colorBlending.blendConstants[2] = 0.0f; colorBlending.blendConstants[3] = 0.0f; - VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; + std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; 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 = {}; + VkGraphicsPipelineCreateInfo pipelineInfo{}; pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; pipelineInfo.stageCount = 2; pipelineInfo.pStages = shaderStages; @@ -556,25 +548,29 @@ class HelloTriangleApplication { pipelineInfo.pRasterizationState = &rasterizer; pipelineInfo.pMultisampleState = &multisampling; pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = &dynamicState; 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) { + 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[] = { swapChainImageViews[i] }; - VkFramebufferCreateInfo framebufferInfo = {}; + VkFramebufferCreateInfo framebufferInfo{}; framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; framebufferInfo.renderPass = renderPass; framebufferInfo.attachmentCount = 1; @@ -583,7 +579,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!"); } } @@ -592,23 +588,20 @@ class HelloTriangleApplication { void createCommandPool() { QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); - VkCommandPoolCreateInfo poolInfo = {}; + VkCommandPoolCreateInfo poolInfo{}; poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; - poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); - 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(MAX_FRAMES_IN_FLIGHT); - commandBuffers.resize(swapChainFramebuffers.size()); - - VkCommandBufferAllocateInfo allocInfo = {}; + VkCommandBufferAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; allocInfo.commandPool = commandPool; allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; @@ -617,53 +610,80 @@ class HelloTriangleApplication { 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; + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; - vkBeginCommandBuffer(commandBuffers[i], &beginInfo); + if (vkBeginCommandBuffer(commandBuffer, &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; + VkRenderPassBeginInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = renderPass; + renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex]; + renderPassInfo.renderArea.offset = {0, 0}; + renderPassInfo.renderArea.extent = swapChainExtent; - VkClearValue clearColor = {0.0f, 0.0f, 0.0f, 1.0f}; - renderPassInfo.clearValueCount = 1; - renderPassInfo.pClearValues = &clearColor; + VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; + renderPassInfo.clearValueCount = 1; + renderPassInfo.pClearValues = &clearColor; - vkCmdBeginRenderPass(commandBuffers[i], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); - vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); - vkCmdDraw(commandBuffers[i], 3, 1, 0, 0); + 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; + vkCmdSetViewport(commandBuffer, 0, 1, &viewport); - vkCmdEndRenderPass(commandBuffers[i]); + VkRect2D scissor{}; + scissor.offset = {0, 0}; + scissor.extent = swapChainExtent; + vkCmdSetScissor(commandBuffer, 0, 1, &scissor); - if (vkEndCommandBuffer(commandBuffers[i]) != VK_SUCCESS) { - throw std::runtime_error("failed to record command buffer!"); - } + vkCmdDraw(commandBuffer, 3, 1, 0, 0); + + vkCmdEndRenderPass(commandBuffer); + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); } } - - void createSemaphores() { - VkSemaphoreCreateInfo semaphoreInfo = {}; + + 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, UINT64_MAX); + uint32_t imageIndex; - VkResult result = vkAcquireNextImageKHR(device, swapChain, std::numeric_limits::max(), imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex); + VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); if (result == VK_ERROR_OUT_OF_DATE_KHR) { recreateSwapChain(); @@ -672,27 +692,32 @@ class HelloTriangleApplication { throw std::runtime_error("failed to acquire swap chain image!"); } - VkSubmitInfo submitInfo = {}; + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffers[currentFrame], 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; submitInfo.pWaitDstStageMask = waitStages; submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &commandBuffers[imageIndex]; + submitInfo.pCommandBuffers = &commandBuffers[currentFrame]; - 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!"); } - VkPresentInfoKHR presentInfo = {}; + VkPresentInfoKHR presentInfo{}; presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; presentInfo.waitSemaphoreCount = 1; @@ -706,35 +731,33 @@ 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) { - VkShaderModuleCreateInfo createInfo = {}; + 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) { - 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) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { return availableFormat; } } @@ -742,18 +765,14 @@ class HelloTriangleApplication { return availableFormats[0]; } - VkPresentModeKHR chooseSwapPresentMode(const std::vector availablePresentModes) { - VkPresentModeKHR bestMode = VK_PRESENT_MODE_FIFO_KHR; - + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { 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; + return VK_PRESENT_MODE_FIFO_KHR; } VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { @@ -761,12 +780,15 @@ 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)); + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); return actualExtent; } @@ -837,14 +859,14 @@ class HelloTriangleApplication { int i = 0; for (const auto& queueFamily : queueFamilies) { - if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { indices.graphicsFamily = i; } VkBool32 presentSupport = false; vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); - if (queueFamily.queueCount > 0 && presentSupport) { + if (presentSupport) { indices.presentFamily = i; } @@ -859,18 +881,14 @@ 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); + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); } return extensions; @@ -919,8 +937,8 @@ class HelloTriangleApplication { 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; + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; return VK_FALSE; } @@ -931,10 +949,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/18_shader_vertexbuffer.frag similarity index 70% rename from code/shader_ubo.frag rename to code/18_shader_vertexbuffer.frag index 14fc5862..7c5b0e74 100644 --- a/code/shader_ubo.frag +++ b/code/18_shader_vertexbuffer.frag @@ -1,10 +1,9 @@ -#version 450 -#extension GL_ARB_separate_shader_objects : enable - -layout(location = 0) in vec3 fragColor; - -layout(location = 0) out vec4 outColor; - -void main() { - outColor = vec4(fragColor, 1.0); -} \ No newline at end of file +#version 450 + +layout(location = 0) in vec3 fragColor; + +layout(location = 0) out vec4 outColor; + +void main() { + outColor = vec4(fragColor, 1.0); +} diff --git a/code/shader_vertexbuffer.vert b/code/18_shader_vertexbuffer.vert similarity index 66% rename from code/shader_vertexbuffer.vert rename to code/18_shader_vertexbuffer.vert index a27f0120..9f27f542 100644 --- a/code/shader_vertexbuffer.vert +++ b/code/18_shader_vertexbuffer.vert @@ -1,16 +1,11 @@ -#version 450 -#extension GL_ARB_separate_shader_objects : enable - -layout(location = 0) in vec2 inPosition; -layout(location = 1) in vec3 inColor; - -layout(location = 0) out vec3 fragColor; - -out gl_PerVertex { - vec4 gl_Position; -}; - -void main() { - gl_Position = vec4(inPosition, 0.0, 1.0); - fragColor = inColor; -} \ No newline at end of file +#version 450 + +layout(location = 0) in vec2 inPosition; +layout(location = 1) in vec3 inColor; + +layout(location = 0) out vec3 fragColor; + +void main() { + gl_Position = vec4(inPosition, 0.0, 1.0); + fragColor = inColor; +} diff --git a/code/vertex_input.cpp b/code/18_vertex_input.cpp similarity index 63% rename from code/vertex_input.cpp rename to code/18_vertex_input.cpp index c0725821..34aae683 100644 --- a/code/vertex_input.cpp +++ b/code/18_vertex_input.cpp @@ -4,20 +4,25 @@ #include #include -#include -#include #include +#include #include #include #include +#include +#include +#include #include +#include #include -const int WIDTH = 800; -const int HEIGHT = 600; +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const int MAX_FRAMES_IN_FLIGHT = 2; const std::vector validationLayers = { - "VK_LAYER_LUNARG_standard_validation" + "VK_LAYER_KHRONOS_validation" }; const std::vector deviceExtensions = { @@ -30,86 +35,28 @@ const bool enableValidationLayers = false; const bool enableValidationLayers = true; #endif -VkResult CreateDebugReportCallbackEXT(VkInstance instance, const VkDebugReportCallbackCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugReportCallbackEXT* pCallback) { - auto func = (PFN_vkCreateDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT"); +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); if (func != nullptr) { - return func(instance, pCreateInfo, pAllocator, pCallback); + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); } else { return VK_ERROR_EXTENSION_NOT_PRESENT; } } -void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT callback, const VkAllocationCallbacks* pAllocator) { - auto func = (PFN_vkDestroyDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT"); +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); if (func != nullptr) { - func(instance, callback, pAllocator); + func(instance, debugMessenger, pAllocator); } } -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; + std::optional graphicsFamily; + std::optional presentFamily; bool isComplete() { - return graphicsFamily >= 0 && presentFamily >= 0; + return graphicsFamily.has_value() && presentFamily.has_value(); } }; @@ -124,7 +71,7 @@ struct Vertex { glm::vec3 color; static VkVertexInputBindingDescription getBindingDescription() { - VkVertexInputBindingDescription bindingDescription = {}; + VkVertexInputBindingDescription bindingDescription{}; bindingDescription.binding = 0; bindingDescription.stride = sizeof(Vertex); bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; @@ -133,7 +80,7 @@ struct Vertex { } static std::array getAttributeDescriptions() { - std::array attributeDescriptions = {}; + std::array attributeDescriptions{}; attributeDescriptions[0].binding = 0; attributeDescriptions[0].location = 0; @@ -161,37 +108,42 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + 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; + uint32_t currentFrame = 0; + + bool framebufferResized = false; void initWindow() { glfwInit(); @@ -199,14 +151,18 @@ 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() { createInstance(); - setupDebugCallback(); + setupDebugMessenger(); createSurface(); pickPhysicalDevice(); createLogicalDevice(); @@ -217,7 +173,7 @@ class HelloTriangleApplication { createFramebuffers(); createCommandPool(); createCommandBuffers(); - createSemaphores(); + createSyncObjects(); } void mainLoop() { @@ -227,28 +183,64 @@ class HelloTriangleApplication { } vkDeviceWaitIdle(device); + } - glfwDestroyWindow(window); + void cleanupSwapChain() { + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } - glfwTerminate(); + 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(); + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, 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) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); } void recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + vkDeviceWaitIdle(device); + cleanupSwapChain(); + createSwapChain(); createImageViews(); - createRenderPass(); - createGraphicsPipeline(); createFramebuffers(); - createCommandBuffers(); } void createInstance() { @@ -256,7 +248,7 @@ class HelloTriangleApplication { throw std::runtime_error("validation layers requested, but not available!"); } - VkApplicationInfo appInfo = {}; + VkApplicationInfo appInfo{}; appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; appInfo.pApplicationName = "Hello Triangle"; appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); @@ -264,41 +256,53 @@ class HelloTriangleApplication { appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); appInfo.apiVersion = VK_API_VERSION_1_0; - VkInstanceCreateInfo createInfo = {}; + VkInstanceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; } else { createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } - void setupDebugCallback() { + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { 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; + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { - throw std::runtime_error("failed to set up debug callback!"); + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); } } 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!"); } } @@ -330,11 +334,11 @@ class HelloTriangleApplication { QueueFamilyIndices indices = findQueueFamilies(physicalDevice); std::vector queueCreateInfos; - std::set uniqueQueueFamilies = {indices.graphicsFamily, indices.presentFamily}; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; float queuePriority = 1.0f; - for (int queueFamily : uniqueQueueFamilies) { - VkDeviceQueueCreateInfo queueCreateInfo = {}; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; queueCreateInfo.queueFamilyIndex = queueFamily; queueCreateInfo.queueCount = 1; @@ -342,32 +346,32 @@ class HelloTriangleApplication { queueCreateInfos.push_back(queueCreateInfo); } - VkPhysicalDeviceFeatures deviceFeatures = {}; + VkPhysicalDeviceFeatures deviceFeatures{}; - VkDeviceCreateInfo createInfo = {}; + 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!"); } - vkGetDeviceQueue(device, indices.graphicsFamily, 0, &graphicsQueue); - vkGetDeviceQueue(device, indices.presentFamily, 0, &presentQueue); + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); } void createSwapChain() { @@ -382,7 +386,7 @@ class HelloTriangleApplication { imageCount = swapChainSupport.capabilities.maxImageCount; } - VkSwapchainCreateInfoKHR createInfo = {}; + VkSwapchainCreateInfoKHR createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; createInfo.surface = surface; @@ -394,7 +398,7 @@ class HelloTriangleApplication { createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; QueueFamilyIndices indices = findQueueFamilies(physicalDevice); - uint32_t queueFamilyIndices[] = {(uint32_t) indices.graphicsFamily, (uint32_t) indices.presentFamily}; + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; if (indices.graphicsFamily != indices.presentFamily) { createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; @@ -409,16 +413,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,10 +426,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++) { - VkImageViewCreateInfo createInfo = {}; + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; createInfo.image = swapChainImages[i]; createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; @@ -446,14 +444,14 @@ 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 createRenderPass() { - VkAttachmentDescription colorAttachment = {}; + VkAttachmentDescription colorAttachment{}; colorAttachment.format = swapChainImageFormat; colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; @@ -463,24 +461,24 @@ class HelloTriangleApplication { colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - VkAttachmentReference colorAttachmentRef = {}; + VkAttachmentReference colorAttachmentRef{}; colorAttachmentRef.attachment = 0; colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - VkSubpassDescription subpass = {}; + VkSubpassDescription subpass{}; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass.colorAttachmentCount = 1; subpass.pColorAttachments = &colorAttachmentRef; - VkSubpassDependency dependency = {}; + 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; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; - VkRenderPassCreateInfo renderPassInfo = {}; + VkRenderPassCreateInfo renderPassInfo{}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; renderPassInfo.attachmentCount = 1; renderPassInfo.pAttachments = &colorAttachment; @@ -489,7 +487,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,18 +496,16 @@ 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 = {}; + 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 = {}; + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; fragShaderStageInfo.module = fragShaderModule; @@ -517,42 +513,28 @@ class HelloTriangleApplication { VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; - VkPipelineVertexInputStateCreateInfo vertexInputInfo = {}; + 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 = attributeDescriptions.size(); + vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()); vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); - VkPipelineInputAssemblyStateCreateInfo inputAssembly = {}; + 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 = {}; + 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 = {}; + VkPipelineRasterizationStateCreateInfo rasterizer{}; rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; rasterizer.depthClampEnable = VK_FALSE; rasterizer.rasterizerDiscardEnable = VK_FALSE; @@ -562,16 +544,16 @@ class HelloTriangleApplication { rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; rasterizer.depthBiasEnable = VK_FALSE; - VkPipelineMultisampleStateCreateInfo multisampling = {}; + VkPipelineMultisampleStateCreateInfo multisampling{}; multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; multisampling.sampleShadingEnable = VK_FALSE; multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; - VkPipelineColorBlendAttachmentState colorBlendAttachment = {}; + 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 = {}; + VkPipelineColorBlendStateCreateInfo colorBlending{}; colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; colorBlending.logicOpEnable = VK_FALSE; colorBlending.logicOp = VK_LOGIC_OP_COPY; @@ -582,16 +564,25 @@ class HelloTriangleApplication { colorBlending.blendConstants[2] = 0.0f; colorBlending.blendConstants[3] = 0.0f; - VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; + std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; 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 = {}; + VkGraphicsPipelineCreateInfo pipelineInfo{}; pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; pipelineInfo.stageCount = 2; pipelineInfo.pStages = shaderStages; @@ -601,25 +592,29 @@ class HelloTriangleApplication { pipelineInfo.pRasterizationState = &rasterizer; pipelineInfo.pMultisampleState = &multisampling; pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = &dynamicState; 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) { + 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[] = { swapChainImageViews[i] }; - VkFramebufferCreateInfo framebufferInfo = {}; + VkFramebufferCreateInfo framebufferInfo{}; framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; framebufferInfo.renderPass = renderPass; framebufferInfo.attachmentCount = 1; @@ -628,7 +623,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!"); } } @@ -637,23 +632,20 @@ class HelloTriangleApplication { void createCommandPool() { QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); - VkCommandPoolCreateInfo poolInfo = {}; + VkCommandPoolCreateInfo poolInfo{}; poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; - poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); - 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()); + commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); - VkCommandBufferAllocateInfo allocInfo = {}; + VkCommandBufferAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; allocInfo.commandPool = commandPool; allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; @@ -662,53 +654,80 @@ class HelloTriangleApplication { 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; + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; - vkBeginCommandBuffer(commandBuffers[i], &beginInfo); + if (vkBeginCommandBuffer(commandBuffer, &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; + VkRenderPassBeginInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = renderPass; + renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex]; + renderPassInfo.renderArea.offset = {0, 0}; + renderPassInfo.renderArea.extent = swapChainExtent; - VkClearValue clearColor = {0.0f, 0.0f, 0.0f, 1.0f}; - renderPassInfo.clearValueCount = 1; - renderPassInfo.pClearValues = &clearColor; + VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; + renderPassInfo.clearValueCount = 1; + renderPassInfo.pClearValues = &clearColor; - vkCmdBeginRenderPass(commandBuffers[i], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); - vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); - vkCmdDraw(commandBuffers[i], 3, 1, 0, 0); + 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; + vkCmdSetViewport(commandBuffer, 0, 1, &viewport); - vkCmdEndRenderPass(commandBuffers[i]); + VkRect2D scissor{}; + scissor.offset = {0, 0}; + scissor.extent = swapChainExtent; + vkCmdSetScissor(commandBuffer, 0, 1, &scissor); - if (vkEndCommandBuffer(commandBuffers[i]) != VK_SUCCESS) { - throw std::runtime_error("failed to record command buffer!"); - } + vkCmdDraw(commandBuffer, 3, 1, 0, 0); + + vkCmdEndRenderPass(commandBuffer); + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); } } - void createSemaphores() { - VkSemaphoreCreateInfo semaphoreInfo = {}; + 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, UINT64_MAX); + uint32_t imageIndex; - VkResult result = vkAcquireNextImageKHR(device, swapChain, std::numeric_limits::max(), imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex); + VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); if (result == VK_ERROR_OUT_OF_DATE_KHR) { recreateSwapChain(); @@ -717,27 +736,32 @@ class HelloTriangleApplication { throw std::runtime_error("failed to acquire swap chain image!"); } - VkSubmitInfo submitInfo = {}; + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffers[currentFrame], 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; submitInfo.pWaitDstStageMask = waitStages; submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &commandBuffers[imageIndex]; + submitInfo.pCommandBuffers = &commandBuffers[currentFrame]; - 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!"); } - VkPresentInfoKHR presentInfo = {}; + VkPresentInfoKHR presentInfo{}; presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; presentInfo.waitSemaphoreCount = 1; @@ -751,35 +775,33 @@ 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) { - VkShaderModuleCreateInfo createInfo = {}; + 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) { - 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) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { return availableFormat; } } @@ -787,18 +809,14 @@ class HelloTriangleApplication { return availableFormats[0]; } - VkPresentModeKHR chooseSwapPresentMode(const std::vector availablePresentModes) { - VkPresentModeKHR bestMode = VK_PRESENT_MODE_FIFO_KHR; - + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { 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; + return VK_PRESENT_MODE_FIFO_KHR; } VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { @@ -806,12 +824,15 @@ 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)); + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); return actualExtent; } @@ -882,14 +903,14 @@ class HelloTriangleApplication { int i = 0; for (const auto& queueFamily : queueFamilies) { - if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { indices.graphicsFamily = i; } VkBool32 presentSupport = false; vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); - if (queueFamily.queueCount > 0 && presentSupport) { + if (presentSupport) { indices.presentFamily = i; } @@ -904,18 +925,14 @@ 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); + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); } return extensions; @@ -964,8 +981,8 @@ class HelloTriangleApplication { 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; + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; return VK_FALSE; } @@ -976,10 +993,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/19_vertex_buffer.cpp similarity index 64% rename from code/vertex_buffer.cpp rename to code/19_vertex_buffer.cpp index b3253243..89b20051 100644 --- a/code/vertex_buffer.cpp +++ b/code/19_vertex_buffer.cpp @@ -4,20 +4,25 @@ #include #include -#include -#include #include +#include #include #include #include +#include +#include +#include #include +#include #include -const int WIDTH = 800; -const int HEIGHT = 600; +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const int MAX_FRAMES_IN_FLIGHT = 2; const std::vector validationLayers = { - "VK_LAYER_LUNARG_standard_validation" + "VK_LAYER_KHRONOS_validation" }; const std::vector deviceExtensions = { @@ -30,86 +35,28 @@ const bool enableValidationLayers = false; const bool enableValidationLayers = true; #endif -VkResult CreateDebugReportCallbackEXT(VkInstance instance, const VkDebugReportCallbackCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugReportCallbackEXT* pCallback) { - auto func = (PFN_vkCreateDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT"); +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); if (func != nullptr) { - return func(instance, pCreateInfo, pAllocator, pCallback); + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); } else { return VK_ERROR_EXTENSION_NOT_PRESENT; } } -void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT callback, const VkAllocationCallbacks* pAllocator) { - auto func = (PFN_vkDestroyDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT"); +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); if (func != nullptr) { - func(instance, callback, pAllocator); + func(instance, debugMessenger, pAllocator); } } -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; + std::optional graphicsFamily; + std::optional presentFamily; bool isComplete() { - return graphicsFamily >= 0 && presentFamily >= 0; + return graphicsFamily.has_value() && presentFamily.has_value(); } }; @@ -124,7 +71,7 @@ struct Vertex { glm::vec3 color; static VkVertexInputBindingDescription getBindingDescription() { - VkVertexInputBindingDescription bindingDescription = {}; + VkVertexInputBindingDescription bindingDescription{}; bindingDescription.binding = 0; bindingDescription.stride = sizeof(Vertex); bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; @@ -133,7 +80,7 @@ struct Vertex { } static std::array getAttributeDescriptions() { - std::array attributeDescriptions = {}; + std::array attributeDescriptions{}; attributeDescriptions[0].binding = 0; attributeDescriptions[0].location = 0; @@ -161,41 +108,46 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + 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; + uint32_t currentFrame = 0; + + bool framebufferResized = false; void initWindow() { glfwInit(); @@ -203,14 +155,18 @@ 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() { createInstance(); - setupDebugCallback(); + setupDebugMessenger(); createSurface(); pickPhysicalDevice(); createLogicalDevice(); @@ -222,7 +178,7 @@ class HelloTriangleApplication { createCommandPool(); createVertexBuffer(); createCommandBuffers(); - createSemaphores(); + createSyncObjects(); } void mainLoop() { @@ -232,28 +188,67 @@ class HelloTriangleApplication { } vkDeviceWaitIdle(device); + } - glfwDestroyWindow(window); + void cleanupSwapChain() { + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } - glfwTerminate(); + 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(); + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, 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) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); } void recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + vkDeviceWaitIdle(device); + cleanupSwapChain(); + createSwapChain(); createImageViews(); - createRenderPass(); - createGraphicsPipeline(); createFramebuffers(); - createCommandBuffers(); } void createInstance() { @@ -261,7 +256,7 @@ class HelloTriangleApplication { throw std::runtime_error("validation layers requested, but not available!"); } - VkApplicationInfo appInfo = {}; + VkApplicationInfo appInfo{}; appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; appInfo.pApplicationName = "Hello Triangle"; appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); @@ -269,41 +264,53 @@ class HelloTriangleApplication { appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); appInfo.apiVersion = VK_API_VERSION_1_0; - VkInstanceCreateInfo createInfo = {}; + VkInstanceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; } else { createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } - void setupDebugCallback() { + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { 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; + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { - throw std::runtime_error("failed to set up debug callback!"); + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); } } 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!"); } } @@ -335,11 +342,11 @@ class HelloTriangleApplication { QueueFamilyIndices indices = findQueueFamilies(physicalDevice); std::vector queueCreateInfos; - std::set uniqueQueueFamilies = {indices.graphicsFamily, indices.presentFamily}; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; float queuePriority = 1.0f; - for (int queueFamily : uniqueQueueFamilies) { - VkDeviceQueueCreateInfo queueCreateInfo = {}; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; queueCreateInfo.queueFamilyIndex = queueFamily; queueCreateInfo.queueCount = 1; @@ -347,32 +354,32 @@ class HelloTriangleApplication { queueCreateInfos.push_back(queueCreateInfo); } - VkPhysicalDeviceFeatures deviceFeatures = {}; + VkPhysicalDeviceFeatures deviceFeatures{}; - VkDeviceCreateInfo createInfo = {}; + 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!"); } - vkGetDeviceQueue(device, indices.graphicsFamily, 0, &graphicsQueue); - vkGetDeviceQueue(device, indices.presentFamily, 0, &presentQueue); + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); } void createSwapChain() { @@ -387,7 +394,7 @@ class HelloTriangleApplication { imageCount = swapChainSupport.capabilities.maxImageCount; } - VkSwapchainCreateInfoKHR createInfo = {}; + VkSwapchainCreateInfoKHR createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; createInfo.surface = surface; @@ -399,7 +406,7 @@ class HelloTriangleApplication { createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; QueueFamilyIndices indices = findQueueFamilies(physicalDevice); - uint32_t queueFamilyIndices[] = {(uint32_t) indices.graphicsFamily, (uint32_t) indices.presentFamily}; + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; if (indices.graphicsFamily != indices.presentFamily) { createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; @@ -414,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()); @@ -433,10 +434,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++) { - VkImageViewCreateInfo createInfo = {}; + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; createInfo.image = swapChainImages[i]; createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; @@ -451,14 +452,14 @@ 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 createRenderPass() { - VkAttachmentDescription colorAttachment = {}; + VkAttachmentDescription colorAttachment{}; colorAttachment.format = swapChainImageFormat; colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; @@ -468,24 +469,24 @@ class HelloTriangleApplication { colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - VkAttachmentReference colorAttachmentRef = {}; + VkAttachmentReference colorAttachmentRef{}; colorAttachmentRef.attachment = 0; colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - VkSubpassDescription subpass = {}; + VkSubpassDescription subpass{}; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass.colorAttachmentCount = 1; subpass.pColorAttachments = &colorAttachmentRef; - VkSubpassDependency dependency = {}; + 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; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; - VkRenderPassCreateInfo renderPassInfo = {}; + VkRenderPassCreateInfo renderPassInfo{}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; renderPassInfo.attachmentCount = 1; renderPassInfo.pAttachments = &colorAttachment; @@ -494,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!"); } } @@ -503,18 +504,16 @@ 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 = {}; + 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 = {}; + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; fragShaderStageInfo.module = fragShaderModule; @@ -522,42 +521,28 @@ class HelloTriangleApplication { VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; - VkPipelineVertexInputStateCreateInfo vertexInputInfo = {}; + 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 = attributeDescriptions.size(); + vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()); vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); - VkPipelineInputAssemblyStateCreateInfo inputAssembly = {}; + 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 = {}; + 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 = {}; + VkPipelineRasterizationStateCreateInfo rasterizer{}; rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; rasterizer.depthClampEnable = VK_FALSE; rasterizer.rasterizerDiscardEnable = VK_FALSE; @@ -567,16 +552,16 @@ class HelloTriangleApplication { rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; rasterizer.depthBiasEnable = VK_FALSE; - VkPipelineMultisampleStateCreateInfo multisampling = {}; + VkPipelineMultisampleStateCreateInfo multisampling{}; multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; multisampling.sampleShadingEnable = VK_FALSE; multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; - VkPipelineColorBlendAttachmentState colorBlendAttachment = {}; + 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 = {}; + VkPipelineColorBlendStateCreateInfo colorBlending{}; colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; colorBlending.logicOpEnable = VK_FALSE; colorBlending.logicOp = VK_LOGIC_OP_COPY; @@ -587,16 +572,25 @@ class HelloTriangleApplication { colorBlending.blendConstants[2] = 0.0f; colorBlending.blendConstants[3] = 0.0f; - VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; + std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; 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 = {}; + VkGraphicsPipelineCreateInfo pipelineInfo{}; pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; pipelineInfo.stageCount = 2; pipelineInfo.pStages = shaderStages; @@ -606,25 +600,29 @@ class HelloTriangleApplication { pipelineInfo.pRasterizationState = &rasterizer; pipelineInfo.pMultisampleState = &multisampling; pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = &dynamicState; 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) { + 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[] = { swapChainImageViews[i] }; - VkFramebufferCreateInfo framebufferInfo = {}; + VkFramebufferCreateInfo framebufferInfo{}; framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; framebufferInfo.renderPass = renderPass; framebufferInfo.attachmentCount = 1; @@ -633,7 +631,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!"); } } @@ -642,35 +640,36 @@ class HelloTriangleApplication { void createCommandPool() { QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); - VkCommandPoolCreateInfo poolInfo = {}; + VkCommandPoolCreateInfo poolInfo{}; poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; - poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); - 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 createVertexBuffer() { - VkBufferCreateInfo bufferInfo = {}; + VkBufferCreateInfo bufferInfo{}; bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; bufferInfo.size = sizeof(vertices[0]) * vertices.size(); 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!"); } VkMemoryRequirements memRequirements; vkGetBufferMemoryRequirements(device, vertexBuffer, &memRequirements); - VkMemoryAllocateInfo allocInfo = {}; + 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); - 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,13 +695,9 @@ class HelloTriangleApplication { } void createCommandBuffers() { - if (commandBuffers.size() > 0) { - vkFreeCommandBuffers(device, commandPool, commandBuffers.size(), commandBuffers.data()); - } - - commandBuffers.resize(swapChainFramebuffers.size()); + commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); - VkCommandBufferAllocateInfo allocInfo = {}; + VkCommandBufferAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; allocInfo.commandPool = commandPool; allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; @@ -711,57 +706,84 @@ class HelloTriangleApplication { 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; + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; - vkBeginCommandBuffer(commandBuffers[i], &beginInfo); + if (vkBeginCommandBuffer(commandBuffer, &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; + VkRenderPassBeginInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = renderPass; + renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex]; + renderPassInfo.renderArea.offset = {0, 0}; + renderPassInfo.renderArea.extent = swapChainExtent; - VkClearValue clearColor = {0.0f, 0.0f, 0.0f, 1.0f}; - renderPassInfo.clearValueCount = 1; - renderPassInfo.pClearValues = &clearColor; + VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; + renderPassInfo.clearValueCount = 1; + renderPassInfo.pClearValues = &clearColor; - vkCmdBeginRenderPass(commandBuffers[i], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); - vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); - VkBuffer vertexBuffers[] = {vertexBuffer}; - VkDeviceSize offsets[] = {0}; - vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets); + 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; + vkCmdSetViewport(commandBuffer, 0, 1, &viewport); - vkCmdDraw(commandBuffers[i], vertices.size(), 1, 0, 0); + VkRect2D scissor{}; + scissor.offset = {0, 0}; + scissor.extent = swapChainExtent; + vkCmdSetScissor(commandBuffer, 0, 1, &scissor); - vkCmdEndRenderPass(commandBuffers[i]); + VkBuffer vertexBuffers[] = {vertexBuffer}; + VkDeviceSize offsets[] = {0}; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); - if (vkEndCommandBuffer(commandBuffers[i]) != VK_SUCCESS) { - throw std::runtime_error("failed to record command buffer!"); - } + vkCmdDraw(commandBuffer, static_cast(vertices.size()), 1, 0, 0); + + vkCmdEndRenderPass(commandBuffer); + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); } } - void createSemaphores() { - VkSemaphoreCreateInfo semaphoreInfo = {}; + 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, UINT64_MAX); + uint32_t imageIndex; - VkResult result = vkAcquireNextImageKHR(device, swapChain, std::numeric_limits::max(), imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex); + VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); if (result == VK_ERROR_OUT_OF_DATE_KHR) { recreateSwapChain(); @@ -770,27 +792,32 @@ class HelloTriangleApplication { throw std::runtime_error("failed to acquire swap chain image!"); } - VkSubmitInfo submitInfo = {}; + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffers[currentFrame], 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; submitInfo.pWaitDstStageMask = waitStages; submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &commandBuffers[imageIndex]; + submitInfo.pCommandBuffers = &commandBuffers[currentFrame]; - 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!"); } - VkPresentInfoKHR presentInfo = {}; + VkPresentInfoKHR presentInfo{}; presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; presentInfo.waitSemaphoreCount = 1; @@ -804,35 +831,33 @@ 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) { - VkShaderModuleCreateInfo createInfo = {}; + 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) { - 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) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { return availableFormat; } } @@ -840,18 +865,14 @@ class HelloTriangleApplication { return availableFormats[0]; } - VkPresentModeKHR chooseSwapPresentMode(const std::vector availablePresentModes) { - VkPresentModeKHR bestMode = VK_PRESENT_MODE_FIFO_KHR; - + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { 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; + return VK_PRESENT_MODE_FIFO_KHR; } VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { @@ -859,12 +880,15 @@ 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)); + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); return actualExtent; } @@ -935,14 +959,14 @@ class HelloTriangleApplication { int i = 0; for (const auto& queueFamily : queueFamilies) { - if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { indices.graphicsFamily = i; } VkBool32 presentSupport = false; vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); - if (queueFamily.queueCount > 0 && presentSupport) { + if (presentSupport) { indices.presentFamily = i; } @@ -957,18 +981,14 @@ 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); + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); } return extensions; @@ -1017,8 +1037,8 @@ class HelloTriangleApplication { 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; + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; return VK_FALSE; } @@ -1029,10 +1049,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/20_staging_buffer.cpp similarity index 64% rename from code/staging_buffer.cpp rename to code/20_staging_buffer.cpp index 63ef25d5..ac0f9b20 100644 --- a/code/staging_buffer.cpp +++ b/code/20_staging_buffer.cpp @@ -4,20 +4,25 @@ #include #include -#include -#include #include +#include #include #include #include +#include +#include +#include #include +#include #include -const int WIDTH = 800; -const int HEIGHT = 600; +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const int MAX_FRAMES_IN_FLIGHT = 2; const std::vector validationLayers = { - "VK_LAYER_LUNARG_standard_validation" + "VK_LAYER_KHRONOS_validation" }; const std::vector deviceExtensions = { @@ -30,86 +35,28 @@ const bool enableValidationLayers = false; const bool enableValidationLayers = true; #endif -VkResult CreateDebugReportCallbackEXT(VkInstance instance, const VkDebugReportCallbackCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugReportCallbackEXT* pCallback) { - auto func = (PFN_vkCreateDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT"); +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); if (func != nullptr) { - return func(instance, pCreateInfo, pAllocator, pCallback); + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); } else { return VK_ERROR_EXTENSION_NOT_PRESENT; } } -void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT callback, const VkAllocationCallbacks* pAllocator) { - auto func = (PFN_vkDestroyDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT"); +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); if (func != nullptr) { - func(instance, callback, pAllocator); + func(instance, debugMessenger, pAllocator); } } -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; + std::optional graphicsFamily; + std::optional presentFamily; bool isComplete() { - return graphicsFamily >= 0 && presentFamily >= 0; + return graphicsFamily.has_value() && presentFamily.has_value(); } }; @@ -124,7 +71,7 @@ struct Vertex { glm::vec3 color; static VkVertexInputBindingDescription getBindingDescription() { - VkVertexInputBindingDescription bindingDescription = {}; + VkVertexInputBindingDescription bindingDescription{}; bindingDescription.binding = 0; bindingDescription.stride = sizeof(Vertex); bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; @@ -133,7 +80,7 @@ struct Vertex { } static std::array getAttributeDescriptions() { - std::array attributeDescriptions = {}; + std::array attributeDescriptions{}; attributeDescriptions[0].binding = 0; attributeDescriptions[0].location = 0; @@ -161,41 +108,46 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + 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; + uint32_t currentFrame = 0; + + bool framebufferResized = false; void initWindow() { glfwInit(); @@ -203,14 +155,18 @@ 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() { createInstance(); - setupDebugCallback(); + setupDebugMessenger(); createSurface(); pickPhysicalDevice(); createLogicalDevice(); @@ -222,7 +178,7 @@ class HelloTriangleApplication { createCommandPool(); createVertexBuffer(); createCommandBuffers(); - createSemaphores(); + createSyncObjects(); } void mainLoop() { @@ -232,28 +188,67 @@ class HelloTriangleApplication { } vkDeviceWaitIdle(device); + } - glfwDestroyWindow(window); + void cleanupSwapChain() { + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } - glfwTerminate(); + 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(); + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, 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) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); } void recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + vkDeviceWaitIdle(device); + cleanupSwapChain(); + createSwapChain(); createImageViews(); - createRenderPass(); - createGraphicsPipeline(); createFramebuffers(); - createCommandBuffers(); } void createInstance() { @@ -261,7 +256,7 @@ class HelloTriangleApplication { throw std::runtime_error("validation layers requested, but not available!"); } - VkApplicationInfo appInfo = {}; + VkApplicationInfo appInfo{}; appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; appInfo.pApplicationName = "Hello Triangle"; appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); @@ -269,41 +264,53 @@ class HelloTriangleApplication { appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); appInfo.apiVersion = VK_API_VERSION_1_0; - VkInstanceCreateInfo createInfo = {}; + VkInstanceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; } else { createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } - void setupDebugCallback() { + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { 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; + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { - throw std::runtime_error("failed to set up debug callback!"); + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); } } 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!"); } } @@ -335,11 +342,11 @@ class HelloTriangleApplication { QueueFamilyIndices indices = findQueueFamilies(physicalDevice); std::vector queueCreateInfos; - std::set uniqueQueueFamilies = {indices.graphicsFamily, indices.presentFamily}; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; float queuePriority = 1.0f; - for (int queueFamily : uniqueQueueFamilies) { - VkDeviceQueueCreateInfo queueCreateInfo = {}; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; queueCreateInfo.queueFamilyIndex = queueFamily; queueCreateInfo.queueCount = 1; @@ -347,32 +354,32 @@ class HelloTriangleApplication { queueCreateInfos.push_back(queueCreateInfo); } - VkPhysicalDeviceFeatures deviceFeatures = {}; + VkPhysicalDeviceFeatures deviceFeatures{}; - VkDeviceCreateInfo createInfo = {}; + 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!"); } - vkGetDeviceQueue(device, indices.graphicsFamily, 0, &graphicsQueue); - vkGetDeviceQueue(device, indices.presentFamily, 0, &presentQueue); + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); } void createSwapChain() { @@ -387,7 +394,7 @@ class HelloTriangleApplication { imageCount = swapChainSupport.capabilities.maxImageCount; } - VkSwapchainCreateInfoKHR createInfo = {}; + VkSwapchainCreateInfoKHR createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; createInfo.surface = surface; @@ -399,7 +406,7 @@ class HelloTriangleApplication { createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; QueueFamilyIndices indices = findQueueFamilies(physicalDevice); - uint32_t queueFamilyIndices[] = {(uint32_t) indices.graphicsFamily, (uint32_t) indices.presentFamily}; + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; if (indices.graphicsFamily != indices.presentFamily) { createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; @@ -414,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()); @@ -433,10 +434,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++) { - VkImageViewCreateInfo createInfo = {}; + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; createInfo.image = swapChainImages[i]; createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; @@ -451,14 +452,14 @@ 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 createRenderPass() { - VkAttachmentDescription colorAttachment = {}; + VkAttachmentDescription colorAttachment{}; colorAttachment.format = swapChainImageFormat; colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; @@ -468,24 +469,24 @@ class HelloTriangleApplication { colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - VkAttachmentReference colorAttachmentRef = {}; + VkAttachmentReference colorAttachmentRef{}; colorAttachmentRef.attachment = 0; colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - VkSubpassDescription subpass = {}; + VkSubpassDescription subpass{}; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass.colorAttachmentCount = 1; subpass.pColorAttachments = &colorAttachmentRef; - VkSubpassDependency dependency = {}; + 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; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; - VkRenderPassCreateInfo renderPassInfo = {}; + VkRenderPassCreateInfo renderPassInfo{}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; renderPassInfo.attachmentCount = 1; renderPassInfo.pAttachments = &colorAttachment; @@ -494,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!"); } } @@ -503,18 +504,16 @@ 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 = {}; + 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 = {}; + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; fragShaderStageInfo.module = fragShaderModule; @@ -522,42 +521,28 @@ class HelloTriangleApplication { VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; - VkPipelineVertexInputStateCreateInfo vertexInputInfo = {}; + 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 = attributeDescriptions.size(); + vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()); vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); - VkPipelineInputAssemblyStateCreateInfo inputAssembly = {}; + 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 = {}; + 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 = {}; + VkPipelineRasterizationStateCreateInfo rasterizer{}; rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; rasterizer.depthClampEnable = VK_FALSE; rasterizer.rasterizerDiscardEnable = VK_FALSE; @@ -567,16 +552,16 @@ class HelloTriangleApplication { rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; rasterizer.depthBiasEnable = VK_FALSE; - VkPipelineMultisampleStateCreateInfo multisampling = {}; + VkPipelineMultisampleStateCreateInfo multisampling{}; multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; multisampling.sampleShadingEnable = VK_FALSE; multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; - VkPipelineColorBlendAttachmentState colorBlendAttachment = {}; + 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 = {}; + VkPipelineColorBlendStateCreateInfo colorBlending{}; colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; colorBlending.logicOpEnable = VK_FALSE; colorBlending.logicOp = VK_LOGIC_OP_COPY; @@ -587,16 +572,25 @@ class HelloTriangleApplication { colorBlending.blendConstants[2] = 0.0f; colorBlending.blendConstants[3] = 0.0f; - VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; + std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; 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 = {}; + VkGraphicsPipelineCreateInfo pipelineInfo{}; pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; pipelineInfo.stageCount = 2; pipelineInfo.pStages = shaderStages; @@ -606,25 +600,29 @@ class HelloTriangleApplication { pipelineInfo.pRasterizationState = &rasterizer; pipelineInfo.pMultisampleState = &multisampling; pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = &dynamicState; 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) { + 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[] = { swapChainImageViews[i] }; - VkFramebufferCreateInfo framebufferInfo = {}; + VkFramebufferCreateInfo framebufferInfo{}; framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; framebufferInfo.renderPass = renderPass; framebufferInfo.attachmentCount = 1; @@ -633,7 +631,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!"); } } @@ -642,11 +640,12 @@ class HelloTriangleApplication { void createCommandPool() { QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); - VkCommandPoolCreateInfo poolInfo = {}; + VkCommandPoolCreateInfo poolInfo{}; poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; - poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); - 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 +653,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,28 +665,31 @@ 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) { - VkBufferCreateInfo bufferInfo = {}; + 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!"); } VkMemoryRequirements memRequirements; vkGetBufferMemoryRequirements(device, buffer, &memRequirements); - VkMemoryAllocateInfo allocInfo = {}; + 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.replace()) != VK_SUCCESS) { + if (vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) { throw std::runtime_error("failed to allocate buffer memory!"); } @@ -695,7 +697,7 @@ class HelloTriangleApplication { } void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { - VkCommandBufferAllocateInfo allocInfo = {}; + VkCommandBufferAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; allocInfo.commandPool = commandPool; @@ -704,19 +706,19 @@ class HelloTriangleApplication { VkCommandBuffer commandBuffer; vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer); - VkCommandBufferBeginInfo beginInfo = {}; + VkCommandBufferBeginInfo beginInfo{}; beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; vkBeginCommandBuffer(commandBuffer, &beginInfo); - VkBufferCopy copyRegion = {}; + VkBufferCopy copyRegion{}; copyRegion.size = size; vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); vkEndCommandBuffer(commandBuffer); - VkSubmitInfo submitInfo = {}; + VkSubmitInfo submitInfo{}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &commandBuffer; @@ -741,13 +743,9 @@ class HelloTriangleApplication { } void createCommandBuffers() { - if (commandBuffers.size() > 0) { - vkFreeCommandBuffers(device, commandPool, commandBuffers.size(), commandBuffers.data()); - } - - commandBuffers.resize(swapChainFramebuffers.size()); + commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); - VkCommandBufferAllocateInfo allocInfo = {}; + VkCommandBufferAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; allocInfo.commandPool = commandPool; allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; @@ -756,57 +754,84 @@ class HelloTriangleApplication { 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; + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; - vkBeginCommandBuffer(commandBuffers[i], &beginInfo); + if (vkBeginCommandBuffer(commandBuffer, &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; + VkRenderPassBeginInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = renderPass; + renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex]; + renderPassInfo.renderArea.offset = {0, 0}; + renderPassInfo.renderArea.extent = swapChainExtent; - VkClearValue clearColor = {0.0f, 0.0f, 0.0f, 1.0f}; - renderPassInfo.clearValueCount = 1; - renderPassInfo.pClearValues = &clearColor; + VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; + renderPassInfo.clearValueCount = 1; + renderPassInfo.pClearValues = &clearColor; - vkCmdBeginRenderPass(commandBuffers[i], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); - vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); - VkBuffer vertexBuffers[] = {vertexBuffer}; - VkDeviceSize offsets[] = {0}; - vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets); + 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; + vkCmdSetViewport(commandBuffer, 0, 1, &viewport); - vkCmdDraw(commandBuffers[i], vertices.size(), 1, 0, 0); + VkRect2D scissor{}; + scissor.offset = {0, 0}; + scissor.extent = swapChainExtent; + vkCmdSetScissor(commandBuffer, 0, 1, &scissor); - vkCmdEndRenderPass(commandBuffers[i]); + VkBuffer vertexBuffers[] = {vertexBuffer}; + VkDeviceSize offsets[] = {0}; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); - if (vkEndCommandBuffer(commandBuffers[i]) != VK_SUCCESS) { - throw std::runtime_error("failed to record command buffer!"); - } + vkCmdDraw(commandBuffer, static_cast(vertices.size()), 1, 0, 0); + + vkCmdEndRenderPass(commandBuffer); + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); } } - void createSemaphores() { - VkSemaphoreCreateInfo semaphoreInfo = {}; + 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, UINT64_MAX); + uint32_t imageIndex; - VkResult result = vkAcquireNextImageKHR(device, swapChain, std::numeric_limits::max(), imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex); + VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); if (result == VK_ERROR_OUT_OF_DATE_KHR) { recreateSwapChain(); @@ -815,27 +840,32 @@ class HelloTriangleApplication { throw std::runtime_error("failed to acquire swap chain image!"); } - VkSubmitInfo submitInfo = {}; + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffers[currentFrame], 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; submitInfo.pWaitDstStageMask = waitStages; submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &commandBuffers[imageIndex]; + submitInfo.pCommandBuffers = &commandBuffers[currentFrame]; - 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!"); } - VkPresentInfoKHR presentInfo = {}; + VkPresentInfoKHR presentInfo{}; presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; presentInfo.waitSemaphoreCount = 1; @@ -849,35 +879,33 @@ 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) { - VkShaderModuleCreateInfo createInfo = {}; + 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) { - 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) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { return availableFormat; } } @@ -885,18 +913,14 @@ class HelloTriangleApplication { return availableFormats[0]; } - VkPresentModeKHR chooseSwapPresentMode(const std::vector availablePresentModes) { - VkPresentModeKHR bestMode = VK_PRESENT_MODE_FIFO_KHR; - + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { 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; + return VK_PRESENT_MODE_FIFO_KHR; } VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { @@ -904,12 +928,15 @@ 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)); + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); return actualExtent; } @@ -980,14 +1007,14 @@ class HelloTriangleApplication { int i = 0; for (const auto& queueFamily : queueFamilies) { - if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { indices.graphicsFamily = i; } VkBool32 presentSupport = false; vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); - if (queueFamily.queueCount > 0 && presentSupport) { + if (presentSupport) { indices.presentFamily = i; } @@ -1002,18 +1029,14 @@ 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); + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); } return extensions; @@ -1062,8 +1085,8 @@ class HelloTriangleApplication { 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; + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; return VK_FALSE; } @@ -1074,10 +1097,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/21_index_buffer.cpp similarity index 64% rename from code/index_buffer.cpp rename to code/21_index_buffer.cpp index ad87d999..23fcf264 100644 --- a/code/index_buffer.cpp +++ b/code/21_index_buffer.cpp @@ -4,20 +4,25 @@ #include #include -#include -#include #include +#include #include #include #include +#include +#include +#include #include +#include #include -const int WIDTH = 800; -const int HEIGHT = 600; +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const int MAX_FRAMES_IN_FLIGHT = 2; const std::vector validationLayers = { - "VK_LAYER_LUNARG_standard_validation" + "VK_LAYER_KHRONOS_validation" }; const std::vector deviceExtensions = { @@ -30,86 +35,28 @@ const bool enableValidationLayers = false; const bool enableValidationLayers = true; #endif -VkResult CreateDebugReportCallbackEXT(VkInstance instance, const VkDebugReportCallbackCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugReportCallbackEXT* pCallback) { - auto func = (PFN_vkCreateDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT"); +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); if (func != nullptr) { - return func(instance, pCreateInfo, pAllocator, pCallback); + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); } else { return VK_ERROR_EXTENSION_NOT_PRESENT; } } -void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT callback, const VkAllocationCallbacks* pAllocator) { - auto func = (PFN_vkDestroyDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT"); +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); if (func != nullptr) { - func(instance, callback, pAllocator); + func(instance, debugMessenger, pAllocator); } } -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; + std::optional graphicsFamily; + std::optional presentFamily; bool isComplete() { - return graphicsFamily >= 0 && presentFamily >= 0; + return graphicsFamily.has_value() && presentFamily.has_value(); } }; @@ -124,7 +71,7 @@ struct Vertex { glm::vec3 color; static VkVertexInputBindingDescription getBindingDescription() { - VkVertexInputBindingDescription bindingDescription = {}; + VkVertexInputBindingDescription bindingDescription{}; bindingDescription.binding = 0; bindingDescription.stride = sizeof(Vertex); bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; @@ -133,7 +80,7 @@ struct Vertex { } static std::array getAttributeDescriptions() { - std::array attributeDescriptions = {}; + std::array attributeDescriptions{}; attributeDescriptions[0].binding = 0; attributeDescriptions[0].location = 0; @@ -166,43 +113,48 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + 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; + uint32_t currentFrame = 0; + + bool framebufferResized = false; void initWindow() { glfwInit(); @@ -210,14 +162,18 @@ 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() { createInstance(); - setupDebugCallback(); + setupDebugMessenger(); createSurface(); pickPhysicalDevice(); createLogicalDevice(); @@ -230,7 +186,7 @@ class HelloTriangleApplication { createVertexBuffer(); createIndexBuffer(); createCommandBuffers(); - createSemaphores(); + createSyncObjects(); } void mainLoop() { @@ -240,28 +196,70 @@ class HelloTriangleApplication { } vkDeviceWaitIdle(device); + } - glfwDestroyWindow(window); + void cleanupSwapChain() { + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } - glfwTerminate(); + 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(); + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, 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) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); } void recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + vkDeviceWaitIdle(device); + cleanupSwapChain(); + createSwapChain(); createImageViews(); - createRenderPass(); - createGraphicsPipeline(); createFramebuffers(); - createCommandBuffers(); } void createInstance() { @@ -269,7 +267,7 @@ class HelloTriangleApplication { throw std::runtime_error("validation layers requested, but not available!"); } - VkApplicationInfo appInfo = {}; + VkApplicationInfo appInfo{}; appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; appInfo.pApplicationName = "Hello Triangle"; appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); @@ -277,41 +275,53 @@ class HelloTriangleApplication { appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); appInfo.apiVersion = VK_API_VERSION_1_0; - VkInstanceCreateInfo createInfo = {}; + VkInstanceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; } else { createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } - void setupDebugCallback() { + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { 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; + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { - throw std::runtime_error("failed to set up debug callback!"); + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); } } 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!"); } } @@ -343,11 +353,11 @@ class HelloTriangleApplication { QueueFamilyIndices indices = findQueueFamilies(physicalDevice); std::vector queueCreateInfos; - std::set uniqueQueueFamilies = {indices.graphicsFamily, indices.presentFamily}; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; float queuePriority = 1.0f; - for (int queueFamily : uniqueQueueFamilies) { - VkDeviceQueueCreateInfo queueCreateInfo = {}; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; queueCreateInfo.queueFamilyIndex = queueFamily; queueCreateInfo.queueCount = 1; @@ -355,32 +365,32 @@ class HelloTriangleApplication { queueCreateInfos.push_back(queueCreateInfo); } - VkPhysicalDeviceFeatures deviceFeatures = {}; + VkPhysicalDeviceFeatures deviceFeatures{}; - VkDeviceCreateInfo createInfo = {}; + 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!"); } - vkGetDeviceQueue(device, indices.graphicsFamily, 0, &graphicsQueue); - vkGetDeviceQueue(device, indices.presentFamily, 0, &presentQueue); + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); } void createSwapChain() { @@ -395,7 +405,7 @@ class HelloTriangleApplication { imageCount = swapChainSupport.capabilities.maxImageCount; } - VkSwapchainCreateInfoKHR createInfo = {}; + VkSwapchainCreateInfoKHR createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; createInfo.surface = surface; @@ -407,7 +417,7 @@ class HelloTriangleApplication { createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; QueueFamilyIndices indices = findQueueFamilies(physicalDevice); - uint32_t queueFamilyIndices[] = {(uint32_t) indices.graphicsFamily, (uint32_t) indices.presentFamily}; + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; if (indices.graphicsFamily != indices.presentFamily) { createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; @@ -422,16 +432,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,10 +445,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++) { - VkImageViewCreateInfo createInfo = {}; + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; createInfo.image = swapChainImages[i]; createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; @@ -459,14 +463,14 @@ 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 createRenderPass() { - VkAttachmentDescription colorAttachment = {}; + VkAttachmentDescription colorAttachment{}; colorAttachment.format = swapChainImageFormat; colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; @@ -476,24 +480,24 @@ class HelloTriangleApplication { colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - VkAttachmentReference colorAttachmentRef = {}; + VkAttachmentReference colorAttachmentRef{}; colorAttachmentRef.attachment = 0; colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - VkSubpassDescription subpass = {}; + VkSubpassDescription subpass{}; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass.colorAttachmentCount = 1; subpass.pColorAttachments = &colorAttachmentRef; - VkSubpassDependency dependency = {}; + 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; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; - VkRenderPassCreateInfo renderPassInfo = {}; + VkRenderPassCreateInfo renderPassInfo{}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; renderPassInfo.attachmentCount = 1; renderPassInfo.pAttachments = &colorAttachment; @@ -502,7 +506,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,18 +515,16 @@ 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 = {}; + 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 = {}; + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; fragShaderStageInfo.module = fragShaderModule; @@ -530,42 +532,28 @@ class HelloTriangleApplication { VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; - VkPipelineVertexInputStateCreateInfo vertexInputInfo = {}; + 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 = attributeDescriptions.size(); + vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()); vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); - VkPipelineInputAssemblyStateCreateInfo inputAssembly = {}; + 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 = {}; + 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 = {}; + VkPipelineRasterizationStateCreateInfo rasterizer{}; rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; rasterizer.depthClampEnable = VK_FALSE; rasterizer.rasterizerDiscardEnable = VK_FALSE; @@ -575,16 +563,16 @@ class HelloTriangleApplication { rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; rasterizer.depthBiasEnable = VK_FALSE; - VkPipelineMultisampleStateCreateInfo multisampling = {}; + VkPipelineMultisampleStateCreateInfo multisampling{}; multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; multisampling.sampleShadingEnable = VK_FALSE; multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; - VkPipelineColorBlendAttachmentState colorBlendAttachment = {}; + 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 = {}; + VkPipelineColorBlendStateCreateInfo colorBlending{}; colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; colorBlending.logicOpEnable = VK_FALSE; colorBlending.logicOp = VK_LOGIC_OP_COPY; @@ -595,16 +583,25 @@ class HelloTriangleApplication { colorBlending.blendConstants[2] = 0.0f; colorBlending.blendConstants[3] = 0.0f; - VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; + std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; 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 = {}; + VkGraphicsPipelineCreateInfo pipelineInfo{}; pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; pipelineInfo.stageCount = 2; pipelineInfo.pStages = shaderStages; @@ -614,25 +611,29 @@ class HelloTriangleApplication { pipelineInfo.pRasterizationState = &rasterizer; pipelineInfo.pMultisampleState = &multisampling; pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = &dynamicState; 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) { + 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[] = { swapChainImageViews[i] }; - VkFramebufferCreateInfo framebufferInfo = {}; + VkFramebufferCreateInfo framebufferInfo{}; framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; framebufferInfo.renderPass = renderPass; framebufferInfo.attachmentCount = 1; @@ -641,7 +642,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!"); } } @@ -650,11 +651,12 @@ class HelloTriangleApplication { void createCommandPool() { QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); - VkCommandPoolCreateInfo poolInfo = {}; + VkCommandPoolCreateInfo poolInfo{}; poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; - poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); - 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 +664,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 +676,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,28 +696,31 @@ 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) { - VkBufferCreateInfo bufferInfo = {}; + 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!"); } VkMemoryRequirements memRequirements; vkGetBufferMemoryRequirements(device, buffer, &memRequirements); - VkMemoryAllocateInfo allocInfo = {}; + 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.replace()) != VK_SUCCESS) { + if (vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) { throw std::runtime_error("failed to allocate buffer memory!"); } @@ -720,7 +728,7 @@ class HelloTriangleApplication { } void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { - VkCommandBufferAllocateInfo allocInfo = {}; + VkCommandBufferAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; allocInfo.commandPool = commandPool; @@ -729,19 +737,19 @@ class HelloTriangleApplication { VkCommandBuffer commandBuffer; vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer); - VkCommandBufferBeginInfo beginInfo = {}; + VkCommandBufferBeginInfo beginInfo{}; beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; vkBeginCommandBuffer(commandBuffer, &beginInfo); - VkBufferCopy copyRegion = {}; + VkBufferCopy copyRegion{}; copyRegion.size = size; vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); vkEndCommandBuffer(commandBuffer); - VkSubmitInfo submitInfo = {}; + VkSubmitInfo submitInfo{}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &commandBuffer; @@ -766,13 +774,9 @@ class HelloTriangleApplication { } void createCommandBuffers() { - if (commandBuffers.size() > 0) { - vkFreeCommandBuffers(device, commandPool, commandBuffers.size(), commandBuffers.data()); - } - - commandBuffers.resize(swapChainFramebuffers.size()); + commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); - VkCommandBufferAllocateInfo allocInfo = {}; + VkCommandBufferAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; allocInfo.commandPool = commandPool; allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; @@ -781,59 +785,86 @@ class HelloTriangleApplication { 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; + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; - vkBeginCommandBuffer(commandBuffers[i], &beginInfo); + if (vkBeginCommandBuffer(commandBuffer, &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; + VkRenderPassBeginInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = renderPass; + renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex]; + renderPassInfo.renderArea.offset = {0, 0}; + renderPassInfo.renderArea.extent = swapChainExtent; - VkClearValue clearColor = {0.0f, 0.0f, 0.0f, 1.0f}; - renderPassInfo.clearValueCount = 1; - renderPassInfo.pClearValues = &clearColor; + VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; + renderPassInfo.clearValueCount = 1; + renderPassInfo.pClearValues = &clearColor; - vkCmdBeginRenderPass(commandBuffers[i], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); - vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); - VkBuffer vertexBuffers[] = {vertexBuffer}; - VkDeviceSize offsets[] = {0}; - vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets); + 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; + vkCmdSetViewport(commandBuffer, 0, 1, &viewport); - vkCmdBindIndexBuffer(commandBuffers[i], indexBuffer, 0, VK_INDEX_TYPE_UINT16); + VkRect2D scissor{}; + scissor.offset = {0, 0}; + scissor.extent = swapChainExtent; + vkCmdSetScissor(commandBuffer, 0, 1, &scissor); - vkCmdDrawIndexed(commandBuffers[i], indices.size(), 1, 0, 0, 0); + VkBuffer vertexBuffers[] = {vertexBuffer}; + VkDeviceSize offsets[] = {0}; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); - vkCmdEndRenderPass(commandBuffers[i]); + vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT16); - if (vkEndCommandBuffer(commandBuffers[i]) != VK_SUCCESS) { - throw std::runtime_error("failed to record command buffer!"); - } + vkCmdDrawIndexed(commandBuffer, static_cast(indices.size()), 1, 0, 0, 0); + + vkCmdEndRenderPass(commandBuffer); + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); } } - void createSemaphores() { - VkSemaphoreCreateInfo semaphoreInfo = {}; + 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, UINT64_MAX); + uint32_t imageIndex; - VkResult result = vkAcquireNextImageKHR(device, swapChain, std::numeric_limits::max(), imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex); + VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); if (result == VK_ERROR_OUT_OF_DATE_KHR) { recreateSwapChain(); @@ -842,27 +873,32 @@ class HelloTriangleApplication { throw std::runtime_error("failed to acquire swap chain image!"); } - VkSubmitInfo submitInfo = {}; + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffers[currentFrame], 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; submitInfo.pWaitDstStageMask = waitStages; submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &commandBuffers[imageIndex]; + submitInfo.pCommandBuffers = &commandBuffers[currentFrame]; - 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!"); } - VkPresentInfoKHR presentInfo = {}; + VkPresentInfoKHR presentInfo{}; presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; presentInfo.waitSemaphoreCount = 1; @@ -876,35 +912,33 @@ 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) { - VkShaderModuleCreateInfo createInfo = {}; + 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) { - 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) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { return availableFormat; } } @@ -912,18 +946,14 @@ class HelloTriangleApplication { return availableFormats[0]; } - VkPresentModeKHR chooseSwapPresentMode(const std::vector availablePresentModes) { - VkPresentModeKHR bestMode = VK_PRESENT_MODE_FIFO_KHR; - + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { 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; + return VK_PRESENT_MODE_FIFO_KHR; } VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { @@ -931,12 +961,15 @@ 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)); + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); return actualExtent; } @@ -1007,14 +1040,14 @@ class HelloTriangleApplication { int i = 0; for (const auto& queueFamily : queueFamilies) { - if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { indices.graphicsFamily = i; } VkBool32 presentSupport = false; vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); - if (queueFamily.queueCount > 0 && presentSupport) { + if (presentSupport) { indices.presentFamily = i; } @@ -1029,18 +1062,14 @@ 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); + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); } return extensions; @@ -1089,8 +1118,8 @@ class HelloTriangleApplication { 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; + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; return VK_FALSE; } @@ -1101,10 +1130,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/22_descriptor_layout.cpp similarity index 63% rename from code/descriptor_layout.cpp rename to code/22_descriptor_layout.cpp index 6b40847a..b4b2f4b3 100644 --- a/code/descriptor_layout.cpp +++ b/code/22_descriptor_layout.cpp @@ -6,21 +6,26 @@ #include #include -#include -#include -#include #include +#include #include +#include #include #include +#include +#include +#include #include +#include #include -const int WIDTH = 800; -const int HEIGHT = 600; +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const int MAX_FRAMES_IN_FLIGHT = 2; const std::vector validationLayers = { - "VK_LAYER_LUNARG_standard_validation" + "VK_LAYER_KHRONOS_validation" }; const std::vector deviceExtensions = { @@ -33,86 +38,28 @@ const bool enableValidationLayers = false; const bool enableValidationLayers = true; #endif -VkResult CreateDebugReportCallbackEXT(VkInstance instance, const VkDebugReportCallbackCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugReportCallbackEXT* pCallback) { - auto func = (PFN_vkCreateDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT"); +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); if (func != nullptr) { - return func(instance, pCreateInfo, pAllocator, pCallback); + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); } else { return VK_ERROR_EXTENSION_NOT_PRESENT; } } -void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT callback, const VkAllocationCallbacks* pAllocator) { - auto func = (PFN_vkDestroyDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT"); +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); if (func != nullptr) { - func(instance, callback, pAllocator); + func(instance, debugMessenger, pAllocator); } } -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; + std::optional graphicsFamily; + std::optional presentFamily; bool isComplete() { - return graphicsFamily >= 0 && presentFamily >= 0; + return graphicsFamily.has_value() && presentFamily.has_value(); } }; @@ -127,7 +74,7 @@ struct Vertex { glm::vec3 color; static VkVertexInputBindingDescription getBindingDescription() { - VkVertexInputBindingDescription bindingDescription = {}; + VkVertexInputBindingDescription bindingDescription{}; bindingDescription.binding = 0; bindingDescription.stride = sizeof(Vertex); bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; @@ -136,7 +83,7 @@ struct Vertex { } static std::array getAttributeDescriptions() { - std::array attributeDescriptions = {}; + std::array attributeDescriptions{}; attributeDescriptions[0].binding = 0; attributeDescriptions[0].location = 0; @@ -175,49 +122,52 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + 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; + uint32_t currentFrame = 0; + + bool framebufferResized = false; void initWindow() { glfwInit(); @@ -225,14 +175,18 @@ 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() { createInstance(); - setupDebugCallback(); + setupDebugMessenger(); createSurface(); pickPhysicalDevice(); createLogicalDevice(); @@ -245,42 +199,89 @@ 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(); + 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(); + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); - HelloTriangleApplication* app = reinterpret_cast(glfwGetWindowUserPointer(window)); - app->recreateSwapChain(); + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroyBuffer(device, uniformBuffers[i], nullptr); + vkFreeMemory(device, uniformBuffersMemory[i], nullptr); + } + + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, 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) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); } void recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + vkDeviceWaitIdle(device); + cleanupSwapChain(); + createSwapChain(); createImageViews(); - createRenderPass(); - createGraphicsPipeline(); createFramebuffers(); - createCommandBuffers(); } void createInstance() { @@ -288,7 +289,7 @@ class HelloTriangleApplication { throw std::runtime_error("validation layers requested, but not available!"); } - VkApplicationInfo appInfo = {}; + VkApplicationInfo appInfo{}; appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; appInfo.pApplicationName = "Hello Triangle"; appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); @@ -296,41 +297,53 @@ class HelloTriangleApplication { appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); appInfo.apiVersion = VK_API_VERSION_1_0; - VkInstanceCreateInfo createInfo = {}; + VkInstanceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; } else { createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } - void setupDebugCallback() { + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { 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; + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { - throw std::runtime_error("failed to set up debug callback!"); + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); } } 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!"); } } @@ -362,11 +375,11 @@ class HelloTriangleApplication { QueueFamilyIndices indices = findQueueFamilies(physicalDevice); std::vector queueCreateInfos; - std::set uniqueQueueFamilies = {indices.graphicsFamily, indices.presentFamily}; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; float queuePriority = 1.0f; - for (int queueFamily : uniqueQueueFamilies) { - VkDeviceQueueCreateInfo queueCreateInfo = {}; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; queueCreateInfo.queueFamilyIndex = queueFamily; queueCreateInfo.queueCount = 1; @@ -374,32 +387,32 @@ class HelloTriangleApplication { queueCreateInfos.push_back(queueCreateInfo); } - VkPhysicalDeviceFeatures deviceFeatures = {}; + VkPhysicalDeviceFeatures deviceFeatures{}; - VkDeviceCreateInfo createInfo = {}; + 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!"); } - vkGetDeviceQueue(device, indices.graphicsFamily, 0, &graphicsQueue); - vkGetDeviceQueue(device, indices.presentFamily, 0, &presentQueue); + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); } void createSwapChain() { @@ -414,7 +427,7 @@ class HelloTriangleApplication { imageCount = swapChainSupport.capabilities.maxImageCount; } - VkSwapchainCreateInfoKHR createInfo = {}; + VkSwapchainCreateInfoKHR createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; createInfo.surface = surface; @@ -426,7 +439,7 @@ class HelloTriangleApplication { createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; QueueFamilyIndices indices = findQueueFamilies(physicalDevice); - uint32_t queueFamilyIndices[] = {(uint32_t) indices.graphicsFamily, (uint32_t) indices.presentFamily}; + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; if (indices.graphicsFamily != indices.presentFamily) { createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; @@ -441,16 +454,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,10 +467,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++) { - VkImageViewCreateInfo createInfo = {}; + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; createInfo.image = swapChainImages[i]; createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; @@ -478,14 +485,14 @@ 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 createRenderPass() { - VkAttachmentDescription colorAttachment = {}; + VkAttachmentDescription colorAttachment{}; colorAttachment.format = swapChainImageFormat; colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; @@ -495,24 +502,24 @@ class HelloTriangleApplication { colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - VkAttachmentReference colorAttachmentRef = {}; + VkAttachmentReference colorAttachmentRef{}; colorAttachmentRef.attachment = 0; colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - VkSubpassDescription subpass = {}; + VkSubpassDescription subpass{}; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass.colorAttachmentCount = 1; subpass.pColorAttachments = &colorAttachmentRef; - VkSubpassDependency dependency = {}; + 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; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; - VkRenderPassCreateInfo renderPassInfo = {}; + VkRenderPassCreateInfo renderPassInfo{}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; renderPassInfo.attachmentCount = 1; renderPassInfo.pAttachments = &colorAttachment; @@ -521,25 +528,25 @@ 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!"); } } void createDescriptorSetLayout() { - VkDescriptorSetLayoutBinding uboLayoutBinding = {}; + 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; - VkDescriptorSetLayoutCreateInfo layoutInfo = {}; + VkDescriptorSetLayoutCreateInfo layoutInfo{}; 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!"); } } @@ -548,18 +555,16 @@ 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 = {}; + 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 = {}; + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; fragShaderStageInfo.module = fragShaderModule; @@ -567,42 +572,28 @@ class HelloTriangleApplication { VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; - VkPipelineVertexInputStateCreateInfo vertexInputInfo = {}; + 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 = attributeDescriptions.size(); + vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()); vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); - VkPipelineInputAssemblyStateCreateInfo inputAssembly = {}; + 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 = {}; + 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 = {}; + + VkPipelineRasterizationStateCreateInfo rasterizer{}; rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; rasterizer.depthClampEnable = VK_FALSE; rasterizer.rasterizerDiscardEnable = VK_FALSE; @@ -612,16 +603,16 @@ class HelloTriangleApplication { rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; rasterizer.depthBiasEnable = VK_FALSE; - VkPipelineMultisampleStateCreateInfo multisampling = {}; + VkPipelineMultisampleStateCreateInfo multisampling{}; multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; multisampling.sampleShadingEnable = VK_FALSE; multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; - VkPipelineColorBlendAttachmentState colorBlendAttachment = {}; + 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 = {}; + VkPipelineColorBlendStateCreateInfo colorBlending{}; colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; colorBlending.logicOpEnable = VK_FALSE; colorBlending.logicOp = VK_LOGIC_OP_COPY; @@ -632,17 +623,25 @@ class HelloTriangleApplication { colorBlending.blendConstants[2] = 0.0f; colorBlending.blendConstants[3] = 0.0f; - VkDescriptorSetLayout setLayouts[] = {descriptorSetLayout}; - VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; + std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + 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!"); } - VkGraphicsPipelineCreateInfo pipelineInfo = {}; + VkGraphicsPipelineCreateInfo pipelineInfo{}; pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; pipelineInfo.stageCount = 2; pipelineInfo.pStages = shaderStages; @@ -652,25 +651,29 @@ class HelloTriangleApplication { pipelineInfo.pRasterizationState = &rasterizer; pipelineInfo.pMultisampleState = &multisampling; pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = &dynamicState; 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) { + 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[] = { swapChainImageViews[i] }; - VkFramebufferCreateInfo framebufferInfo = {}; + VkFramebufferCreateInfo framebufferInfo{}; framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; framebufferInfo.renderPass = renderPass; framebufferInfo.attachmentCount = 1; @@ -679,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!"); } } @@ -688,11 +691,12 @@ class HelloTriangleApplication { void createCommandPool() { QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); - VkCommandPoolCreateInfo poolInfo = {}; + VkCommandPoolCreateInfo poolInfo{}; poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; - poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); - 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 +704,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 +716,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,35 +736,42 @@ 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(MAX_FRAMES_IN_FLIGHT); + uniformBuffersMemory.resize(MAX_FRAMES_IN_FLIGHT); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; 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) { - VkBufferCreateInfo bufferInfo = {}; + 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!"); } VkMemoryRequirements memRequirements; vkGetBufferMemoryRequirements(device, buffer, &memRequirements); - VkMemoryAllocateInfo allocInfo = {}; + 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.replace()) != VK_SUCCESS) { + if (vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) { throw std::runtime_error("failed to allocate buffer memory!"); } @@ -765,7 +779,7 @@ class HelloTriangleApplication { } void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { - VkCommandBufferAllocateInfo allocInfo = {}; + VkCommandBufferAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; allocInfo.commandPool = commandPool; @@ -774,19 +788,19 @@ class HelloTriangleApplication { VkCommandBuffer commandBuffer; vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer); - VkCommandBufferBeginInfo beginInfo = {}; + VkCommandBufferBeginInfo beginInfo{}; beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; vkBeginCommandBuffer(commandBuffer, &beginInfo); - VkBufferCopy copyRegion = {}; + VkBufferCopy copyRegion{}; copyRegion.size = size; vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); vkEndCommandBuffer(commandBuffer); - VkSubmitInfo submitInfo = {}; + VkSubmitInfo submitInfo{}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &commandBuffer; @@ -811,13 +825,9 @@ class HelloTriangleApplication { } void createCommandBuffers() { - if (commandBuffers.size() > 0) { - vkFreeCommandBuffers(device, commandPool, commandBuffers.size(), commandBuffers.data()); - } + commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); - commandBuffers.resize(swapChainFramebuffers.size()); - - VkCommandBufferAllocateInfo allocInfo = {}; + VkCommandBufferAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; allocInfo.commandPool = commandPool; allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; @@ -826,79 +836,104 @@ class HelloTriangleApplication { 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; + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; - vkBeginCommandBuffer(commandBuffers[i], &beginInfo); + if (vkBeginCommandBuffer(commandBuffer, &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; + VkRenderPassBeginInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = renderPass; + renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex]; + renderPassInfo.renderArea.offset = {0, 0}; + renderPassInfo.renderArea.extent = swapChainExtent; - VkClearValue clearColor = {0.0f, 0.0f, 0.0f, 1.0f}; - renderPassInfo.clearValueCount = 1; - renderPassInfo.pClearValues = &clearColor; + VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; + renderPassInfo.clearValueCount = 1; + renderPassInfo.pClearValues = &clearColor; - vkCmdBeginRenderPass(commandBuffers[i], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); - vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); - VkBuffer vertexBuffers[] = {vertexBuffer}; - VkDeviceSize offsets[] = {0}; - vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets); + 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; + vkCmdSetViewport(commandBuffer, 0, 1, &viewport); - vkCmdBindIndexBuffer(commandBuffers[i], indexBuffer, 0, VK_INDEX_TYPE_UINT16); + VkRect2D scissor{}; + scissor.offset = {0, 0}; + scissor.extent = swapChainExtent; + vkCmdSetScissor(commandBuffer, 0, 1, &scissor); - vkCmdDrawIndexed(commandBuffers[i], indices.size(), 1, 0, 0, 0); + VkBuffer vertexBuffers[] = {vertexBuffer}; + VkDeviceSize offsets[] = {0}; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); - vkCmdEndRenderPass(commandBuffers[i]); + vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT16); - if (vkEndCommandBuffer(commandBuffers[i]) != VK_SUCCESS) { - throw std::runtime_error("failed to record command buffer!"); - } + vkCmdDrawIndexed(commandBuffer, static_cast(indices.size()), 1, 0, 0, 0); + + vkCmdEndRenderPass(commandBuffer); + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); } } - void createSemaphores() { - VkSemaphoreCreateInfo semaphoreInfo = {}; + 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)); + 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, 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, UINT64_MAX); + uint32_t imageIndex; - VkResult result = vkAcquireNextImageKHR(device, swapChain, std::numeric_limits::max(), imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex); + VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); if (result == VK_ERROR_OUT_OF_DATE_KHR) { recreateSwapChain(); @@ -907,27 +942,34 @@ class HelloTriangleApplication { throw std::runtime_error("failed to acquire swap chain image!"); } - VkSubmitInfo submitInfo = {}; + updateUniformBuffer(currentFrame); + + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffers[currentFrame], 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; submitInfo.pWaitDstStageMask = waitStages; submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &commandBuffers[imageIndex]; + submitInfo.pCommandBuffers = &commandBuffers[currentFrame]; - 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!"); } - VkPresentInfoKHR presentInfo = {}; + VkPresentInfoKHR presentInfo{}; presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; presentInfo.waitSemaphoreCount = 1; @@ -941,35 +983,33 @@ 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) { - VkShaderModuleCreateInfo createInfo = {}; + 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) { - 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) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { return availableFormat; } } @@ -977,18 +1017,14 @@ class HelloTriangleApplication { return availableFormats[0]; } - VkPresentModeKHR chooseSwapPresentMode(const std::vector availablePresentModes) { - VkPresentModeKHR bestMode = VK_PRESENT_MODE_FIFO_KHR; - + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { 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; + return VK_PRESENT_MODE_FIFO_KHR; } VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { @@ -996,12 +1032,15 @@ 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)); + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); return actualExtent; } @@ -1072,14 +1111,14 @@ class HelloTriangleApplication { int i = 0; for (const auto& queueFamily : queueFamilies) { - if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { indices.graphicsFamily = i; } VkBool32 presentSupport = false; vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); - if (queueFamily.queueCount > 0 && presentSupport) { + if (presentSupport) { indices.presentFamily = i; } @@ -1094,18 +1133,14 @@ 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); + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); } return extensions; @@ -1154,8 +1189,8 @@ class HelloTriangleApplication { 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; + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; return VK_FALSE; } @@ -1166,10 +1201,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/22_shader_ubo.frag similarity index 70% rename from code/shader_vertexbuffer.frag rename to code/22_shader_ubo.frag index 14fc5862..7c5b0e74 100644 --- a/code/shader_vertexbuffer.frag +++ b/code/22_shader_ubo.frag @@ -1,10 +1,9 @@ -#version 450 -#extension GL_ARB_separate_shader_objects : enable - -layout(location = 0) in vec3 fragColor; - -layout(location = 0) out vec4 outColor; - -void main() { - outColor = vec4(fragColor, 1.0); -} \ No newline at end of file +#version 450 + +layout(location = 0) in vec3 fragColor; + +layout(location = 0) out vec4 outColor; + +void main() { + outColor = vec4(fragColor, 1.0); +} diff --git a/code/shader_ubo.vert b/code/22_shader_ubo.vert similarity index 75% rename from code/shader_ubo.vert rename to code/22_shader_ubo.vert index 028313d4..5ffbb2de 100644 --- a/code/shader_ubo.vert +++ b/code/22_shader_ubo.vert @@ -1,22 +1,17 @@ -#version 450 -#extension GL_ARB_separate_shader_objects : enable - -layout(binding = 0) uniform UniformBufferObject { - mat4 model; - mat4 view; - mat4 proj; -} ubo; - -layout(location = 0) in vec2 inPosition; -layout(location = 1) in vec3 inColor; - -layout(location = 0) out vec3 fragColor; - -out gl_PerVertex { - vec4 gl_Position; -}; - -void main() { - gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 0.0, 1.0); - fragColor = inColor; -} \ No newline at end of file +#version 450 + +layout(binding = 0) uniform UniformBufferObject { + mat4 model; + mat4 view; + mat4 proj; +} ubo; + +layout(location = 0) in vec2 inPosition; +layout(location = 1) in vec3 inColor; + +layout(location = 0) out vec3 fragColor; + +void main() { + gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 0.0, 1.0); + fragColor = inColor; +} diff --git a/code/descriptor_set.cpp b/code/23_descriptor_sets.cpp similarity index 61% rename from code/descriptor_set.cpp rename to code/23_descriptor_sets.cpp index e66d8d90..66e6d012 100644 --- a/code/descriptor_set.cpp +++ b/code/23_descriptor_sets.cpp @@ -6,21 +6,26 @@ #include #include -#include -#include -#include #include +#include #include +#include #include #include +#include +#include +#include #include +#include #include -const int WIDTH = 800; -const int HEIGHT = 600; +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const int MAX_FRAMES_IN_FLIGHT = 2; const std::vector validationLayers = { - "VK_LAYER_LUNARG_standard_validation" + "VK_LAYER_KHRONOS_validation" }; const std::vector deviceExtensions = { @@ -33,86 +38,28 @@ const bool enableValidationLayers = false; const bool enableValidationLayers = true; #endif -VkResult CreateDebugReportCallbackEXT(VkInstance instance, const VkDebugReportCallbackCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugReportCallbackEXT* pCallback) { - auto func = (PFN_vkCreateDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT"); +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); if (func != nullptr) { - return func(instance, pCreateInfo, pAllocator, pCallback); + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); } else { return VK_ERROR_EXTENSION_NOT_PRESENT; } } -void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT callback, const VkAllocationCallbacks* pAllocator) { - auto func = (PFN_vkDestroyDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT"); +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); if (func != nullptr) { - func(instance, callback, pAllocator); + func(instance, debugMessenger, pAllocator); } } -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; + std::optional graphicsFamily; + std::optional presentFamily; bool isComplete() { - return graphicsFamily >= 0 && presentFamily >= 0; + return graphicsFamily.has_value() && presentFamily.has_value(); } }; @@ -127,7 +74,7 @@ struct Vertex { glm::vec3 color; static VkVertexInputBindingDescription getBindingDescription() { - VkVertexInputBindingDescription bindingDescription = {}; + VkVertexInputBindingDescription bindingDescription{}; bindingDescription.binding = 0; bindingDescription.stride = sizeof(Vertex); bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; @@ -136,7 +83,7 @@ struct Vertex { } static std::array getAttributeDescriptions() { - std::array attributeDescriptions = {}; + std::array attributeDescriptions{}; attributeDescriptions[0].binding = 0; attributeDescriptions[0].location = 0; @@ -153,9 +100,9 @@ struct Vertex { }; struct UniformBufferObject { - glm::mat4 model; - glm::mat4 view; - glm::mat4 proj; + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; }; const std::vector vertices = { @@ -175,52 +122,55 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + 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; + uint32_t currentFrame = 0; + + bool framebufferResized = false; void initWindow() { glfwInit(); @@ -228,14 +178,18 @@ 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() { createInstance(); - setupDebugCallback(); + setupDebugMessenger(); createSurface(); pickPhysicalDevice(); createLogicalDevice(); @@ -248,44 +202,93 @@ 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(); + 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(); + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroyBuffer(device, uniformBuffers[i], nullptr); + vkFreeMemory(device, uniformBuffersMemory[i], nullptr); + } + + vkDestroyDescriptorPool(device, descriptorPool, nullptr); + + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, 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) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); } void recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + vkDeviceWaitIdle(device); + cleanupSwapChain(); + createSwapChain(); createImageViews(); - createRenderPass(); - createGraphicsPipeline(); createFramebuffers(); - createCommandBuffers(); } void createInstance() { @@ -293,7 +296,7 @@ class HelloTriangleApplication { throw std::runtime_error("validation layers requested, but not available!"); } - VkApplicationInfo appInfo = {}; + VkApplicationInfo appInfo{}; appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; appInfo.pApplicationName = "Hello Triangle"; appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); @@ -301,41 +304,53 @@ class HelloTriangleApplication { appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); appInfo.apiVersion = VK_API_VERSION_1_0; - VkInstanceCreateInfo createInfo = {}; + VkInstanceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; } else { createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } - void setupDebugCallback() { + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { 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; + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { - throw std::runtime_error("failed to set up debug callback!"); + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); } } 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!"); } } @@ -367,11 +382,11 @@ class HelloTriangleApplication { QueueFamilyIndices indices = findQueueFamilies(physicalDevice); std::vector queueCreateInfos; - std::set uniqueQueueFamilies = {indices.graphicsFamily, indices.presentFamily}; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; float queuePriority = 1.0f; - for (int queueFamily : uniqueQueueFamilies) { - VkDeviceQueueCreateInfo queueCreateInfo = {}; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; queueCreateInfo.queueFamilyIndex = queueFamily; queueCreateInfo.queueCount = 1; @@ -379,32 +394,32 @@ class HelloTriangleApplication { queueCreateInfos.push_back(queueCreateInfo); } - VkPhysicalDeviceFeatures deviceFeatures = {}; + VkPhysicalDeviceFeatures deviceFeatures{}; - VkDeviceCreateInfo createInfo = {}; + 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!"); } - vkGetDeviceQueue(device, indices.graphicsFamily, 0, &graphicsQueue); - vkGetDeviceQueue(device, indices.presentFamily, 0, &presentQueue); + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); } void createSwapChain() { @@ -419,7 +434,7 @@ class HelloTriangleApplication { imageCount = swapChainSupport.capabilities.maxImageCount; } - VkSwapchainCreateInfoKHR createInfo = {}; + VkSwapchainCreateInfoKHR createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; createInfo.surface = surface; @@ -431,7 +446,7 @@ class HelloTriangleApplication { createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; QueueFamilyIndices indices = findQueueFamilies(physicalDevice); - uint32_t queueFamilyIndices[] = {(uint32_t) indices.graphicsFamily, (uint32_t) indices.presentFamily}; + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; if (indices.graphicsFamily != indices.presentFamily) { createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; @@ -446,16 +461,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,10 +474,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++) { - VkImageViewCreateInfo createInfo = {}; + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; createInfo.image = swapChainImages[i]; createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; @@ -483,14 +492,14 @@ 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 createRenderPass() { - VkAttachmentDescription colorAttachment = {}; + VkAttachmentDescription colorAttachment{}; colorAttachment.format = swapChainImageFormat; colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; @@ -500,24 +509,24 @@ class HelloTriangleApplication { colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - VkAttachmentReference colorAttachmentRef = {}; + VkAttachmentReference colorAttachmentRef{}; colorAttachmentRef.attachment = 0; colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - VkSubpassDescription subpass = {}; + VkSubpassDescription subpass{}; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass.colorAttachmentCount = 1; subpass.pColorAttachments = &colorAttachmentRef; - VkSubpassDependency dependency = {}; + 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; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; - VkRenderPassCreateInfo renderPassInfo = {}; + VkRenderPassCreateInfo renderPassInfo{}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; renderPassInfo.attachmentCount = 1; renderPassInfo.pAttachments = &colorAttachment; @@ -526,25 +535,25 @@ 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!"); } } void createDescriptorSetLayout() { - VkDescriptorSetLayoutBinding uboLayoutBinding = {}; + 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; - VkDescriptorSetLayoutCreateInfo layoutInfo = {}; + VkDescriptorSetLayoutCreateInfo layoutInfo{}; 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!"); } } @@ -553,18 +562,16 @@ 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 = {}; + 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 = {}; + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; fragShaderStageInfo.module = fragShaderModule; @@ -572,42 +579,28 @@ class HelloTriangleApplication { VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; - VkPipelineVertexInputStateCreateInfo vertexInputInfo = {}; + 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 = attributeDescriptions.size(); + vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()); vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); - VkPipelineInputAssemblyStateCreateInfo inputAssembly = {}; + 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 = {}; + 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 = {}; + VkPipelineRasterizationStateCreateInfo rasterizer{}; rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; rasterizer.depthClampEnable = VK_FALSE; rasterizer.rasterizerDiscardEnable = VK_FALSE; @@ -617,16 +610,16 @@ class HelloTriangleApplication { rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; rasterizer.depthBiasEnable = VK_FALSE; - VkPipelineMultisampleStateCreateInfo multisampling = {}; + VkPipelineMultisampleStateCreateInfo multisampling{}; multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; multisampling.sampleShadingEnable = VK_FALSE; multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; - VkPipelineColorBlendAttachmentState colorBlendAttachment = {}; + 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 = {}; + VkPipelineColorBlendStateCreateInfo colorBlending{}; colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; colorBlending.logicOpEnable = VK_FALSE; colorBlending.logicOp = VK_LOGIC_OP_COPY; @@ -637,17 +630,25 @@ class HelloTriangleApplication { colorBlending.blendConstants[2] = 0.0f; colorBlending.blendConstants[3] = 0.0f; - VkDescriptorSetLayout setLayouts[] = {descriptorSetLayout}; - VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; + std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + 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!"); } - VkGraphicsPipelineCreateInfo pipelineInfo = {}; + VkGraphicsPipelineCreateInfo pipelineInfo{}; pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; pipelineInfo.stageCount = 2; pipelineInfo.pStages = shaderStages; @@ -657,25 +658,29 @@ class HelloTriangleApplication { pipelineInfo.pRasterizationState = &rasterizer; pipelineInfo.pMultisampleState = &multisampling; pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = &dynamicState; 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) { + 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[] = { swapChainImageViews[i] }; - VkFramebufferCreateInfo framebufferInfo = {}; + VkFramebufferCreateInfo framebufferInfo{}; framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; framebufferInfo.renderPass = renderPass; framebufferInfo.attachmentCount = 1; @@ -684,7 +689,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!"); } } @@ -693,11 +698,12 @@ class HelloTriangleApplication { void createCommandPool() { QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); - VkCommandPoolCreateInfo poolInfo = {}; + VkCommandPoolCreateInfo poolInfo{}; poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; - poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); - 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 +711,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 +723,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,80 +743,90 @@ 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(MAX_FRAMES_IN_FLIGHT); + uniformBuffersMemory.resize(MAX_FRAMES_IN_FLIGHT); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; 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 = {}; + VkDescriptorPoolSize poolSize{}; poolSize.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - poolSize.descriptorCount = 1; + poolSize.descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT); - VkDescriptorPoolCreateInfo poolInfo = {}; + VkDescriptorPoolCreateInfo poolInfo{}; poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; poolInfo.poolSizeCount = 1; poolInfo.pPoolSizes = &poolSize; - poolInfo.maxSets = 1; + poolInfo.maxSets = static_cast(MAX_FRAMES_IN_FLIGHT); - 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}; - VkDescriptorSetAllocateInfo allocInfo = {}; + void createDescriptorSets() { + std::vector layouts(MAX_FRAMES_IN_FLIGHT, 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(MAX_FRAMES_IN_FLIGHT); + allocInfo.pSetLayouts = layouts.data(); - if (vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet) != VK_SUCCESS) { - throw std::runtime_error("failed to allocate descriptor set!"); + descriptorSets.resize(MAX_FRAMES_IN_FLIGHT); + if (vkAllocateDescriptorSets(device, &allocInfo, descriptorSets.data()) != 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 < MAX_FRAMES_IN_FLIGHT; 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) { - VkBufferCreateInfo bufferInfo = {}; + 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!"); } VkMemoryRequirements memRequirements; vkGetBufferMemoryRequirements(device, buffer, &memRequirements); - VkMemoryAllocateInfo allocInfo = {}; + 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.replace()) != VK_SUCCESS) { + if (vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) { throw std::runtime_error("failed to allocate buffer memory!"); } @@ -815,7 +834,7 @@ class HelloTriangleApplication { } void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { - VkCommandBufferAllocateInfo allocInfo = {}; + VkCommandBufferAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; allocInfo.commandPool = commandPool; @@ -824,19 +843,19 @@ class HelloTriangleApplication { VkCommandBuffer commandBuffer; vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer); - VkCommandBufferBeginInfo beginInfo = {}; + VkCommandBufferBeginInfo beginInfo{}; beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; vkBeginCommandBuffer(commandBuffer, &beginInfo); - VkBufferCopy copyRegion = {}; + VkBufferCopy copyRegion{}; copyRegion.size = size; vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); vkEndCommandBuffer(commandBuffer); - VkSubmitInfo submitInfo = {}; + VkSubmitInfo submitInfo{}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &commandBuffer; @@ -861,13 +880,9 @@ class HelloTriangleApplication { } void createCommandBuffers() { - if (commandBuffers.size() > 0) { - vkFreeCommandBuffers(device, commandPool, commandBuffers.size(), commandBuffers.data()); - } + commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); - commandBuffers.resize(swapChainFramebuffers.size()); - - VkCommandBufferAllocateInfo allocInfo = {}; + VkCommandBufferAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; allocInfo.commandPool = commandPool; allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; @@ -876,81 +891,107 @@ class HelloTriangleApplication { 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; + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; - vkBeginCommandBuffer(commandBuffers[i], &beginInfo); + if (vkBeginCommandBuffer(commandBuffer, &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; + VkRenderPassBeginInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = renderPass; + renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex]; + renderPassInfo.renderArea.offset = {0, 0}; + renderPassInfo.renderArea.extent = swapChainExtent; - VkClearValue clearColor = {0.0f, 0.0f, 0.0f, 1.0f}; - renderPassInfo.clearValueCount = 1; - renderPassInfo.pClearValues = &clearColor; + VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; + renderPassInfo.clearValueCount = 1; + renderPassInfo.pClearValues = &clearColor; - vkCmdBeginRenderPass(commandBuffers[i], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); - vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); - VkBuffer vertexBuffers[] = {vertexBuffer}; - VkDeviceSize offsets[] = {0}; - vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets); + 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; + vkCmdSetViewport(commandBuffer, 0, 1, &viewport); - vkCmdBindIndexBuffer(commandBuffers[i], indexBuffer, 0, VK_INDEX_TYPE_UINT16); + VkRect2D scissor{}; + scissor.offset = {0, 0}; + scissor.extent = swapChainExtent; + vkCmdSetScissor(commandBuffer, 0, 1, &scissor); - vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr); + VkBuffer vertexBuffers[] = {vertexBuffer}; + VkDeviceSize offsets[] = {0}; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); - vkCmdDrawIndexed(commandBuffers[i], indices.size(), 1, 0, 0, 0); + vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT16); - vkCmdEndRenderPass(commandBuffers[i]); + vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets[currentFrame], 0, nullptr); - if (vkEndCommandBuffer(commandBuffers[i]) != VK_SUCCESS) { - throw std::runtime_error("failed to record command buffer!"); - } + vkCmdDrawIndexed(commandBuffer, static_cast(indices.size()), 1, 0, 0, 0); + + vkCmdEndRenderPass(commandBuffer); + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); } + } - void createSemaphores() { - VkSemaphoreCreateInfo semaphoreInfo = {}; + 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)); + 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, 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, UINT64_MAX); + uint32_t imageIndex; - VkResult result = vkAcquireNextImageKHR(device, swapChain, std::numeric_limits::max(), imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex); + VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); if (result == VK_ERROR_OUT_OF_DATE_KHR) { recreateSwapChain(); @@ -959,27 +1000,34 @@ class HelloTriangleApplication { throw std::runtime_error("failed to acquire swap chain image!"); } - VkSubmitInfo submitInfo = {}; + updateUniformBuffer(currentFrame); + + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffers[currentFrame], 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; submitInfo.pWaitDstStageMask = waitStages; submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &commandBuffers[imageIndex]; + submitInfo.pCommandBuffers = &commandBuffers[currentFrame]; - 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!"); } - VkPresentInfoKHR presentInfo = {}; + VkPresentInfoKHR presentInfo{}; presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; presentInfo.waitSemaphoreCount = 1; @@ -993,35 +1041,33 @@ 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) { - VkShaderModuleCreateInfo createInfo = {}; + 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) { - 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) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { return availableFormat; } } @@ -1029,18 +1075,14 @@ class HelloTriangleApplication { return availableFormats[0]; } - VkPresentModeKHR chooseSwapPresentMode(const std::vector availablePresentModes) { - VkPresentModeKHR bestMode = VK_PRESENT_MODE_FIFO_KHR; - + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { 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; + return VK_PRESENT_MODE_FIFO_KHR; } VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { @@ -1048,12 +1090,15 @@ 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)); + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); return actualExtent; } @@ -1124,14 +1169,14 @@ class HelloTriangleApplication { int i = 0; for (const auto& queueFamily : queueFamilies) { - if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { indices.graphicsFamily = i; } VkBool32 presentSupport = false; vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); - if (queueFamily.queueCount > 0 && presentSupport) { + if (presentSupport) { indices.presentFamily = i; } @@ -1146,18 +1191,14 @@ 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); + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); } return extensions; @@ -1206,8 +1247,8 @@ class HelloTriangleApplication { 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; + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; return VK_FALSE; } @@ -1218,10 +1259,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/24_texture_image.cpp similarity index 60% rename from code/texture_image.cpp rename to code/24_texture_image.cpp index e1fdfff8..5bc27a48 100644 --- a/code/texture_image.cpp +++ b/code/24_texture_image.cpp @@ -9,21 +9,26 @@ #include #include -#include -#include -#include #include +#include #include +#include #include #include +#include +#include +#include #include +#include #include -const int WIDTH = 800; -const int HEIGHT = 600; +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const int MAX_FRAMES_IN_FLIGHT = 2; const std::vector validationLayers = { - "VK_LAYER_LUNARG_standard_validation" + "VK_LAYER_KHRONOS_validation" }; const std::vector deviceExtensions = { @@ -36,86 +41,28 @@ const bool enableValidationLayers = false; const bool enableValidationLayers = true; #endif -VkResult CreateDebugReportCallbackEXT(VkInstance instance, const VkDebugReportCallbackCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugReportCallbackEXT* pCallback) { - auto func = (PFN_vkCreateDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT"); +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); if (func != nullptr) { - return func(instance, pCreateInfo, pAllocator, pCallback); + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); } else { return VK_ERROR_EXTENSION_NOT_PRESENT; } } -void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT callback, const VkAllocationCallbacks* pAllocator) { - auto func = (PFN_vkDestroyDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT"); +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); if (func != nullptr) { - func(instance, callback, pAllocator); + func(instance, debugMessenger, pAllocator); } } -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; + std::optional graphicsFamily; + std::optional presentFamily; bool isComplete() { - return graphicsFamily >= 0 && presentFamily >= 0; + return graphicsFamily.has_value() && presentFamily.has_value(); } }; @@ -130,7 +77,7 @@ struct Vertex { glm::vec3 color; static VkVertexInputBindingDescription getBindingDescription() { - VkVertexInputBindingDescription bindingDescription = {}; + VkVertexInputBindingDescription bindingDescription{}; bindingDescription.binding = 0; bindingDescription.stride = sizeof(Vertex); bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; @@ -139,7 +86,7 @@ struct Vertex { } static std::array getAttributeDescriptions() { - std::array attributeDescriptions = {}; + std::array attributeDescriptions{}; attributeDescriptions[0].binding = 0; attributeDescriptions[0].location = 0; @@ -156,9 +103,9 @@ struct Vertex { }; struct UniformBufferObject { - glm::mat4 model; - glm::mat4 view; - glm::mat4 proj; + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; }; const std::vector vertices = { @@ -178,55 +125,58 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + 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; + uint32_t currentFrame = 0; + + bool framebufferResized = false; void initWindow() { glfwInit(); @@ -234,14 +184,18 @@ 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() { createInstance(); - setupDebugCallback(); + setupDebugMessenger(); createSurface(); pickPhysicalDevice(); createLogicalDevice(); @@ -255,44 +209,96 @@ 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(); + 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(); + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroyBuffer(device, uniformBuffers[i], nullptr); + vkFreeMemory(device, uniformBuffersMemory[i], nullptr); + } + + vkDestroyDescriptorPool(device, descriptorPool, nullptr); + + vkDestroyImage(device, textureImage, nullptr); + vkFreeMemory(device, textureImageMemory, nullptr); + + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, 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) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); } void recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + vkDeviceWaitIdle(device); + cleanupSwapChain(); + createSwapChain(); createImageViews(); - createRenderPass(); - createGraphicsPipeline(); createFramebuffers(); - createCommandBuffers(); } void createInstance() { @@ -300,7 +306,7 @@ class HelloTriangleApplication { throw std::runtime_error("validation layers requested, but not available!"); } - VkApplicationInfo appInfo = {}; + VkApplicationInfo appInfo{}; appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; appInfo.pApplicationName = "Hello Triangle"; appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); @@ -308,41 +314,53 @@ class HelloTriangleApplication { appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); appInfo.apiVersion = VK_API_VERSION_1_0; - VkInstanceCreateInfo createInfo = {}; + VkInstanceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; } else { createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } - void setupDebugCallback() { + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { 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; + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { - throw std::runtime_error("failed to set up debug callback!"); + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); } } 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!"); } } @@ -374,11 +392,11 @@ class HelloTriangleApplication { QueueFamilyIndices indices = findQueueFamilies(physicalDevice); std::vector queueCreateInfos; - std::set uniqueQueueFamilies = {indices.graphicsFamily, indices.presentFamily}; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; float queuePriority = 1.0f; - for (int queueFamily : uniqueQueueFamilies) { - VkDeviceQueueCreateInfo queueCreateInfo = {}; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; queueCreateInfo.queueFamilyIndex = queueFamily; queueCreateInfo.queueCount = 1; @@ -386,32 +404,32 @@ class HelloTriangleApplication { queueCreateInfos.push_back(queueCreateInfo); } - VkPhysicalDeviceFeatures deviceFeatures = {}; + VkPhysicalDeviceFeatures deviceFeatures{}; - VkDeviceCreateInfo createInfo = {}; + 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!"); } - vkGetDeviceQueue(device, indices.graphicsFamily, 0, &graphicsQueue); - vkGetDeviceQueue(device, indices.presentFamily, 0, &presentQueue); + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); } void createSwapChain() { @@ -426,7 +444,7 @@ class HelloTriangleApplication { imageCount = swapChainSupport.capabilities.maxImageCount; } - VkSwapchainCreateInfoKHR createInfo = {}; + VkSwapchainCreateInfoKHR createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; createInfo.surface = surface; @@ -438,7 +456,7 @@ class HelloTriangleApplication { createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; QueueFamilyIndices indices = findQueueFamilies(physicalDevice); - uint32_t queueFamilyIndices[] = {(uint32_t) indices.graphicsFamily, (uint32_t) indices.presentFamily}; + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; if (indices.graphicsFamily != indices.presentFamily) { createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; @@ -453,16 +471,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,10 +484,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++) { - VkImageViewCreateInfo createInfo = {}; + for (size_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; createInfo.image = swapChainImages[i]; createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; @@ -490,14 +502,14 @@ 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 createRenderPass() { - VkAttachmentDescription colorAttachment = {}; + VkAttachmentDescription colorAttachment{}; colorAttachment.format = swapChainImageFormat; colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; @@ -507,24 +519,24 @@ class HelloTriangleApplication { colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - VkAttachmentReference colorAttachmentRef = {}; + VkAttachmentReference colorAttachmentRef{}; colorAttachmentRef.attachment = 0; colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - VkSubpassDescription subpass = {}; + VkSubpassDescription subpass{}; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass.colorAttachmentCount = 1; subpass.pColorAttachments = &colorAttachmentRef; - VkSubpassDependency dependency = {}; + 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; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; - VkRenderPassCreateInfo renderPassInfo = {}; + VkRenderPassCreateInfo renderPassInfo{}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; renderPassInfo.attachmentCount = 1; renderPassInfo.pAttachments = &colorAttachment; @@ -533,25 +545,25 @@ 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!"); } } void createDescriptorSetLayout() { - VkDescriptorSetLayoutBinding uboLayoutBinding = {}; + 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; - VkDescriptorSetLayoutCreateInfo layoutInfo = {}; + VkDescriptorSetLayoutCreateInfo layoutInfo{}; 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!"); } } @@ -560,18 +572,16 @@ 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 = {}; + 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 = {}; + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; fragShaderStageInfo.module = fragShaderModule; @@ -579,42 +589,28 @@ class HelloTriangleApplication { VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; - VkPipelineVertexInputStateCreateInfo vertexInputInfo = {}; + 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 = attributeDescriptions.size(); + vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()); vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); - VkPipelineInputAssemblyStateCreateInfo inputAssembly = {}; + 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 = {}; + 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 = {}; + VkPipelineRasterizationStateCreateInfo rasterizer{}; rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; rasterizer.depthClampEnable = VK_FALSE; rasterizer.rasterizerDiscardEnable = VK_FALSE; @@ -624,16 +620,16 @@ class HelloTriangleApplication { rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; rasterizer.depthBiasEnable = VK_FALSE; - VkPipelineMultisampleStateCreateInfo multisampling = {}; + VkPipelineMultisampleStateCreateInfo multisampling{}; multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; multisampling.sampleShadingEnable = VK_FALSE; multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; - VkPipelineColorBlendAttachmentState colorBlendAttachment = {}; + 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 = {}; + VkPipelineColorBlendStateCreateInfo colorBlending{}; colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; colorBlending.logicOpEnable = VK_FALSE; colorBlending.logicOp = VK_LOGIC_OP_COPY; @@ -644,17 +640,25 @@ class HelloTriangleApplication { colorBlending.blendConstants[2] = 0.0f; colorBlending.blendConstants[3] = 0.0f; - VkDescriptorSetLayout setLayouts[] = {descriptorSetLayout}; - VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; + std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + 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!"); } - VkGraphicsPipelineCreateInfo pipelineInfo = {}; + VkGraphicsPipelineCreateInfo pipelineInfo{}; pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; pipelineInfo.stageCount = 2; pipelineInfo.pStages = shaderStages; @@ -664,25 +668,29 @@ class HelloTriangleApplication { pipelineInfo.pRasterizationState = &rasterizer; pipelineInfo.pMultisampleState = &multisampling; pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = &dynamicState; 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) { + 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[] = { swapChainImageViews[i] }; - VkFramebufferCreateInfo framebufferInfo = {}; + VkFramebufferCreateInfo framebufferInfo{}; framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; framebufferInfo.renderPass = renderPass; framebufferInfo.attachmentCount = 1; @@ -691,7 +699,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!"); } } @@ -700,11 +708,12 @@ class HelloTriangleApplication { void createCommandPool() { QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); - VkCommandPoolCreateInfo poolInfo = {}; + VkCommandPoolCreateInfo poolInfo{}; poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; - poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); - 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,46 +727,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); + createImage(texWidth, texHeight, VK_FORMAT_R8G8B8A8_SRGB, 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_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); + transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); - 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) { - VkImageCreateInfo imageInfo = {}; + 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; imageInfo.extent.width = width; @@ -767,24 +759,24 @@ 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!"); } VkMemoryRequirements memRequirements; vkGetImageMemoryRequirements(device, image, &memRequirements); - VkMemoryAllocateInfo allocInfo = {}; + 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.replace()) != VK_SUCCESS) { + if (vkAllocateMemory(device, &allocInfo, nullptr, &imageMemory) != VK_SUCCESS) { throw std::runtime_error("failed to allocate image memory!"); } @@ -794,7 +786,7 @@ class HelloTriangleApplication { void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout) { VkCommandBuffer commandBuffer = beginSingleTimeCommands(); - VkImageMemoryBarrier barrier = {}; + VkImageMemoryBarrier barrier{}; barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; barrier.oldLayout = oldLayout; barrier.newLayout = newLayout; @@ -807,22 +799,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 +830,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 +856,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 +868,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,80 +888,90 @@ 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(MAX_FRAMES_IN_FLIGHT); + uniformBuffersMemory.resize(MAX_FRAMES_IN_FLIGHT); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; 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 = {}; + VkDescriptorPoolSize poolSize{}; poolSize.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - poolSize.descriptorCount = 1; + poolSize.descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT); - VkDescriptorPoolCreateInfo poolInfo = {}; + VkDescriptorPoolCreateInfo poolInfo{}; poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; poolInfo.poolSizeCount = 1; poolInfo.pPoolSizes = &poolSize; - poolInfo.maxSets = 1; + poolInfo.maxSets = static_cast(MAX_FRAMES_IN_FLIGHT); - 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}; - VkDescriptorSetAllocateInfo allocInfo = {}; + void createDescriptorSets() { + std::vector layouts(MAX_FRAMES_IN_FLIGHT, 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(MAX_FRAMES_IN_FLIGHT); + allocInfo.pSetLayouts = layouts.data(); - if (vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet) != VK_SUCCESS) { - throw std::runtime_error("failed to allocate descriptor set!"); + descriptorSets.resize(MAX_FRAMES_IN_FLIGHT); + if (vkAllocateDescriptorSets(device, &allocInfo, descriptorSets.data()) != 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 < MAX_FRAMES_IN_FLIGHT; 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) { - VkBufferCreateInfo bufferInfo = {}; + 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!"); } VkMemoryRequirements memRequirements; vkGetBufferMemoryRequirements(device, buffer, &memRequirements); - VkMemoryAllocateInfo allocInfo = {}; + 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.replace()) != VK_SUCCESS) { + if (vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) { throw std::runtime_error("failed to allocate buffer memory!"); } @@ -973,7 +979,7 @@ class HelloTriangleApplication { } VkCommandBuffer beginSingleTimeCommands() { - VkCommandBufferAllocateInfo allocInfo = {}; + VkCommandBufferAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; allocInfo.commandPool = commandPool; @@ -982,7 +988,7 @@ class HelloTriangleApplication { VkCommandBuffer commandBuffer; vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer); - VkCommandBufferBeginInfo beginInfo = {}; + VkCommandBufferBeginInfo beginInfo{}; beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; @@ -994,7 +1000,7 @@ class HelloTriangleApplication { void endSingleTimeCommands(VkCommandBuffer commandBuffer) { vkEndCommandBuffer(commandBuffer); - VkSubmitInfo submitInfo = {}; + VkSubmitInfo submitInfo{}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &commandBuffer; @@ -1008,7 +1014,7 @@ class HelloTriangleApplication { void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { VkCommandBuffer commandBuffer = beginSingleTimeCommands(); - VkBufferCopy copyRegion = {}; + VkBufferCopy copyRegion{}; copyRegion.size = size; vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); @@ -1029,13 +1035,9 @@ class HelloTriangleApplication { } void createCommandBuffers() { - if (commandBuffers.size() > 0) { - vkFreeCommandBuffers(device, commandPool, commandBuffers.size(), commandBuffers.data()); - } - - commandBuffers.resize(swapChainFramebuffers.size()); + commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); - VkCommandBufferAllocateInfo allocInfo = {}; + VkCommandBufferAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; allocInfo.commandPool = commandPool; allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; @@ -1044,81 +1046,107 @@ class HelloTriangleApplication { 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; + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; - vkBeginCommandBuffer(commandBuffers[i], &beginInfo); + if (vkBeginCommandBuffer(commandBuffer, &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; + VkRenderPassBeginInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = renderPass; + renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex]; + renderPassInfo.renderArea.offset = {0, 0}; + renderPassInfo.renderArea.extent = swapChainExtent; - VkClearValue clearColor = {0.0f, 0.0f, 0.0f, 1.0f}; - renderPassInfo.clearValueCount = 1; - renderPassInfo.pClearValues = &clearColor; + VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; + renderPassInfo.clearValueCount = 1; + renderPassInfo.pClearValues = &clearColor; - vkCmdBeginRenderPass(commandBuffers[i], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); - vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); - VkBuffer vertexBuffers[] = {vertexBuffer}; - VkDeviceSize offsets[] = {0}; - vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets); + 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; + vkCmdSetViewport(commandBuffer, 0, 1, &viewport); - vkCmdBindIndexBuffer(commandBuffers[i], indexBuffer, 0, VK_INDEX_TYPE_UINT16); + VkRect2D scissor{}; + scissor.offset = {0, 0}; + scissor.extent = swapChainExtent; + vkCmdSetScissor(commandBuffer, 0, 1, &scissor); - vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr); + VkBuffer vertexBuffers[] = {vertexBuffer}; + VkDeviceSize offsets[] = {0}; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); - vkCmdDrawIndexed(commandBuffers[i], indices.size(), 1, 0, 0, 0); + vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT16); - vkCmdEndRenderPass(commandBuffers[i]); + vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets[currentFrame], 0, nullptr); - if (vkEndCommandBuffer(commandBuffers[i]) != VK_SUCCESS) { - throw std::runtime_error("failed to record command buffer!"); - } + vkCmdDrawIndexed(commandBuffer, static_cast(indices.size()), 1, 0, 0, 0); + + vkCmdEndRenderPass(commandBuffer); + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); } + } - void createSemaphores() { - VkSemaphoreCreateInfo semaphoreInfo = {}; + 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)); + 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, 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, UINT64_MAX); + uint32_t imageIndex; - VkResult result = vkAcquireNextImageKHR(device, swapChain, std::numeric_limits::max(), imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex); + VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); if (result == VK_ERROR_OUT_OF_DATE_KHR) { recreateSwapChain(); @@ -1127,27 +1155,34 @@ class HelloTriangleApplication { throw std::runtime_error("failed to acquire swap chain image!"); } - VkSubmitInfo submitInfo = {}; + updateUniformBuffer(currentFrame); + + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffers[currentFrame], 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; submitInfo.pWaitDstStageMask = waitStages; submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &commandBuffers[imageIndex]; + submitInfo.pCommandBuffers = &commandBuffers[currentFrame]; - 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!"); } - VkPresentInfoKHR presentInfo = {}; + VkPresentInfoKHR presentInfo{}; presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; presentInfo.waitSemaphoreCount = 1; @@ -1161,35 +1196,33 @@ 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) { - VkShaderModuleCreateInfo createInfo = {}; + 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) { - 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) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { return availableFormat; } } @@ -1197,18 +1230,14 @@ class HelloTriangleApplication { return availableFormats[0]; } - VkPresentModeKHR chooseSwapPresentMode(const std::vector availablePresentModes) { - VkPresentModeKHR bestMode = VK_PRESENT_MODE_FIFO_KHR; - + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { 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; + return VK_PRESENT_MODE_FIFO_KHR; } VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { @@ -1216,12 +1245,15 @@ 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)); + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); return actualExtent; } @@ -1292,14 +1324,14 @@ class HelloTriangleApplication { int i = 0; for (const auto& queueFamily : queueFamilies) { - if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { indices.graphicsFamily = i; } VkBool32 presentSupport = false; vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); - if (queueFamily.queueCount > 0 && presentSupport) { + if (presentSupport) { indices.presentFamily = i; } @@ -1314,18 +1346,14 @@ 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); + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); } return extensions; @@ -1374,8 +1402,8 @@ class HelloTriangleApplication { 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; + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; return VK_FALSE; } @@ -1386,10 +1414,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/25_sampler.cpp similarity index 59% rename from code/sampler.cpp rename to code/25_sampler.cpp index e24a4fa8..12eee4d6 100644 --- a/code/sampler.cpp +++ b/code/25_sampler.cpp @@ -9,21 +9,26 @@ #include #include -#include -#include -#include #include +#include #include +#include #include #include +#include +#include +#include #include +#include #include -const int WIDTH = 800; -const int HEIGHT = 600; +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const int MAX_FRAMES_IN_FLIGHT = 2; const std::vector validationLayers = { - "VK_LAYER_LUNARG_standard_validation" + "VK_LAYER_KHRONOS_validation" }; const std::vector deviceExtensions = { @@ -36,86 +41,28 @@ const bool enableValidationLayers = false; const bool enableValidationLayers = true; #endif -VkResult CreateDebugReportCallbackEXT(VkInstance instance, const VkDebugReportCallbackCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugReportCallbackEXT* pCallback) { - auto func = (PFN_vkCreateDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT"); +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); if (func != nullptr) { - return func(instance, pCreateInfo, pAllocator, pCallback); + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); } else { return VK_ERROR_EXTENSION_NOT_PRESENT; } } -void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT callback, const VkAllocationCallbacks* pAllocator) { - auto func = (PFN_vkDestroyDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT"); +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); if (func != nullptr) { - func(instance, callback, pAllocator); + func(instance, debugMessenger, pAllocator); } } -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; + std::optional graphicsFamily; + std::optional presentFamily; bool isComplete() { - return graphicsFamily >= 0 && presentFamily >= 0; + return graphicsFamily.has_value() && presentFamily.has_value(); } }; @@ -130,7 +77,7 @@ struct Vertex { glm::vec3 color; static VkVertexInputBindingDescription getBindingDescription() { - VkVertexInputBindingDescription bindingDescription = {}; + VkVertexInputBindingDescription bindingDescription{}; bindingDescription.binding = 0; bindingDescription.stride = sizeof(Vertex); bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; @@ -139,7 +86,7 @@ struct Vertex { } static std::array getAttributeDescriptions() { - std::array attributeDescriptions = {}; + std::array attributeDescriptions{}; attributeDescriptions[0].binding = 0; attributeDescriptions[0].location = 0; @@ -156,9 +103,9 @@ struct Vertex { }; struct UniformBufferObject { - glm::mat4 model; - glm::mat4 view; - glm::mat4 proj; + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; }; const std::vector vertices = { @@ -178,57 +125,60 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + 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; + uint32_t currentFrame = 0; + + bool framebufferResized = false; void initWindow() { glfwInit(); @@ -236,14 +186,18 @@ 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() { createInstance(); - setupDebugCallback(); + setupDebugMessenger(); createSurface(); pickPhysicalDevice(); createLogicalDevice(); @@ -259,44 +213,99 @@ 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(); + 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(); + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroyBuffer(device, uniformBuffers[i], nullptr); + vkFreeMemory(device, uniformBuffersMemory[i], nullptr); + } + + vkDestroyDescriptorPool(device, descriptorPool, nullptr); + + vkDestroySampler(device, textureSampler, nullptr); + vkDestroyImageView(device, textureImageView, nullptr); + + vkDestroyImage(device, textureImage, nullptr); + vkFreeMemory(device, textureImageMemory, nullptr); + + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, 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) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); } void recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + vkDeviceWaitIdle(device); + cleanupSwapChain(); + createSwapChain(); createImageViews(); - createRenderPass(); - createGraphicsPipeline(); createFramebuffers(); - createCommandBuffers(); } void createInstance() { @@ -304,7 +313,7 @@ class HelloTriangleApplication { throw std::runtime_error("validation layers requested, but not available!"); } - VkApplicationInfo appInfo = {}; + VkApplicationInfo appInfo{}; appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; appInfo.pApplicationName = "Hello Triangle"; appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); @@ -312,41 +321,53 @@ class HelloTriangleApplication { appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); appInfo.apiVersion = VK_API_VERSION_1_0; - VkInstanceCreateInfo createInfo = {}; + VkInstanceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; } else { createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } - void setupDebugCallback() { + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { 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; + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { - throw std::runtime_error("failed to set up debug callback!"); + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); } } 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!"); } } @@ -378,11 +399,11 @@ class HelloTriangleApplication { QueueFamilyIndices indices = findQueueFamilies(physicalDevice); std::vector queueCreateInfos; - std::set uniqueQueueFamilies = {indices.graphicsFamily, indices.presentFamily}; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; float queuePriority = 1.0f; - for (int queueFamily : uniqueQueueFamilies) { - VkDeviceQueueCreateInfo queueCreateInfo = {}; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; queueCreateInfo.queueFamilyIndex = queueFamily; queueCreateInfo.queueCount = 1; @@ -390,32 +411,33 @@ class HelloTriangleApplication { queueCreateInfos.push_back(queueCreateInfo); } - VkPhysicalDeviceFeatures deviceFeatures = {}; + VkPhysicalDeviceFeatures deviceFeatures{}; + deviceFeatures.samplerAnisotropy = VK_TRUE; - VkDeviceCreateInfo createInfo = {}; + 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!"); } - vkGetDeviceQueue(device, indices.graphicsFamily, 0, &graphicsQueue); - vkGetDeviceQueue(device, indices.presentFamily, 0, &presentQueue); + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); } void createSwapChain() { @@ -430,7 +452,7 @@ class HelloTriangleApplication { imageCount = swapChainSupport.capabilities.maxImageCount; } - VkSwapchainCreateInfoKHR createInfo = {}; + VkSwapchainCreateInfoKHR createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; createInfo.surface = surface; @@ -442,7 +464,7 @@ class HelloTriangleApplication { createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; QueueFamilyIndices indices = findQueueFamilies(physicalDevice); - uint32_t queueFamilyIndices[] = {(uint32_t) indices.graphicsFamily, (uint32_t) indices.presentFamily}; + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; if (indices.graphicsFamily != indices.presentFamily) { createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; @@ -457,16 +479,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,15 +492,15 @@ 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); } } void createRenderPass() { - VkAttachmentDescription colorAttachment = {}; + VkAttachmentDescription colorAttachment{}; colorAttachment.format = swapChainImageFormat; colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; @@ -494,24 +510,24 @@ class HelloTriangleApplication { colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - VkAttachmentReference colorAttachmentRef = {}; + VkAttachmentReference colorAttachmentRef{}; colorAttachmentRef.attachment = 0; colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - VkSubpassDescription subpass = {}; + VkSubpassDescription subpass{}; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass.colorAttachmentCount = 1; subpass.pColorAttachments = &colorAttachmentRef; - VkSubpassDependency dependency = {}; + 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; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; - VkRenderPassCreateInfo renderPassInfo = {}; + VkRenderPassCreateInfo renderPassInfo{}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; renderPassInfo.attachmentCount = 1; renderPassInfo.pAttachments = &colorAttachment; @@ -520,25 +536,25 @@ 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!"); } } void createDescriptorSetLayout() { - VkDescriptorSetLayoutBinding uboLayoutBinding = {}; + 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; - VkDescriptorSetLayoutCreateInfo layoutInfo = {}; + VkDescriptorSetLayoutCreateInfo layoutInfo{}; 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!"); } } @@ -547,18 +563,16 @@ 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 = {}; + 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 = {}; + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; fragShaderStageInfo.module = fragShaderModule; @@ -566,42 +580,28 @@ class HelloTriangleApplication { VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; - VkPipelineVertexInputStateCreateInfo vertexInputInfo = {}; + 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 = attributeDescriptions.size(); + vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()); vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); - VkPipelineInputAssemblyStateCreateInfo inputAssembly = {}; + 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 = {}; + 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 = {}; + VkPipelineRasterizationStateCreateInfo rasterizer{}; rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; rasterizer.depthClampEnable = VK_FALSE; rasterizer.rasterizerDiscardEnable = VK_FALSE; @@ -611,16 +611,16 @@ class HelloTriangleApplication { rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; rasterizer.depthBiasEnable = VK_FALSE; - VkPipelineMultisampleStateCreateInfo multisampling = {}; + VkPipelineMultisampleStateCreateInfo multisampling{}; multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; multisampling.sampleShadingEnable = VK_FALSE; multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; - VkPipelineColorBlendAttachmentState colorBlendAttachment = {}; + 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 = {}; + VkPipelineColorBlendStateCreateInfo colorBlending{}; colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; colorBlending.logicOpEnable = VK_FALSE; colorBlending.logicOp = VK_LOGIC_OP_COPY; @@ -631,17 +631,25 @@ class HelloTriangleApplication { colorBlending.blendConstants[2] = 0.0f; colorBlending.blendConstants[3] = 0.0f; - VkDescriptorSetLayout setLayouts[] = {descriptorSetLayout}; - VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; + std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + 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!"); } - VkGraphicsPipelineCreateInfo pipelineInfo = {}; + VkGraphicsPipelineCreateInfo pipelineInfo{}; pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; pipelineInfo.stageCount = 2; pipelineInfo.pStages = shaderStages; @@ -651,25 +659,29 @@ class HelloTriangleApplication { pipelineInfo.pRasterizationState = &rasterizer; pipelineInfo.pMultisampleState = &multisampling; pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = &dynamicState; 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) { + 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[] = { swapChainImageViews[i] }; - VkFramebufferCreateInfo framebufferInfo = {}; + VkFramebufferCreateInfo framebufferInfo{}; framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; framebufferInfo.renderPass = renderPass; framebufferInfo.attachmentCount = 1; @@ -678,7 +690,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!"); } } @@ -687,11 +699,12 @@ class HelloTriangleApplication { void createCommandPool() { QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); - VkCommandPoolCreateInfo poolInfo = {}; + VkCommandPoolCreateInfo poolInfo{}; poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; - poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); - 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,50 +718,36 @@ 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); + createImage(texWidth, texHeight, VK_FORMAT_R8G8B8A8_SRGB, 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_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); + transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); - 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_SRGB); } void createTextureSampler() { - VkSamplerCreateInfo samplerInfo = {}; + VkPhysicalDeviceProperties properties{}; + vkGetPhysicalDeviceProperties(physicalDevice, &properties); + + VkSamplerCreateInfo samplerInfo{}; samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; samplerInfo.magFilter = VK_FILTER_LINEAR; samplerInfo.minFilter = VK_FILTER_LINEAR; @@ -756,20 +755,20 @@ class HelloTriangleApplication { samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; samplerInfo.anisotropyEnable = VK_TRUE; - samplerInfo.maxAnisotropy = 16; + samplerInfo.maxAnisotropy = properties.limits.maxSamplerAnisotropy; 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; - 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) { - VkImageViewCreateInfo viewInfo = {}; + VkImageView createImageView(VkImage image, VkFormat format) { + VkImageViewCreateInfo viewInfo{}; viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; viewInfo.image = image; viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; @@ -780,13 +779,16 @@ 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) { - VkImageCreateInfo imageInfo = {}; + 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; imageInfo.extent.width = width; @@ -796,24 +798,24 @@ 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!"); } VkMemoryRequirements memRequirements; vkGetImageMemoryRequirements(device, image, &memRequirements); - VkMemoryAllocateInfo allocInfo = {}; + 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.replace()) != VK_SUCCESS) { + if (vkAllocateMemory(device, &allocInfo, nullptr, &imageMemory) != VK_SUCCESS) { throw std::runtime_error("failed to allocate image memory!"); } @@ -823,7 +825,7 @@ class HelloTriangleApplication { void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout) { VkCommandBuffer commandBuffer = beginSingleTimeCommands(); - VkImageMemoryBarrier barrier = {}; + VkImageMemoryBarrier barrier{}; barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; barrier.oldLayout = oldLayout; barrier.newLayout = newLayout; @@ -836,22 +838,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 +869,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 +895,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 +907,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,80 +927,90 @@ 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(MAX_FRAMES_IN_FLIGHT); + uniformBuffersMemory.resize(MAX_FRAMES_IN_FLIGHT); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; 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 = {}; + VkDescriptorPoolSize poolSize{}; poolSize.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - poolSize.descriptorCount = 1; + poolSize.descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT); - VkDescriptorPoolCreateInfo poolInfo = {}; + VkDescriptorPoolCreateInfo poolInfo{}; poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; poolInfo.poolSizeCount = 1; poolInfo.pPoolSizes = &poolSize; - poolInfo.maxSets = 1; + poolInfo.maxSets = static_cast(MAX_FRAMES_IN_FLIGHT); - 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}; - VkDescriptorSetAllocateInfo allocInfo = {}; + void createDescriptorSets() { + std::vector layouts(MAX_FRAMES_IN_FLIGHT, 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(MAX_FRAMES_IN_FLIGHT); + allocInfo.pSetLayouts = layouts.data(); - if (vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet) != VK_SUCCESS) { - throw std::runtime_error("failed to allocate descriptor set!"); + descriptorSets.resize(MAX_FRAMES_IN_FLIGHT); + if (vkAllocateDescriptorSets(device, &allocInfo, descriptorSets.data()) != 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 < MAX_FRAMES_IN_FLIGHT; 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) { - VkBufferCreateInfo bufferInfo = {}; + 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!"); } VkMemoryRequirements memRequirements; vkGetBufferMemoryRequirements(device, buffer, &memRequirements); - VkMemoryAllocateInfo allocInfo = {}; + 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.replace()) != VK_SUCCESS) { + if (vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) { throw std::runtime_error("failed to allocate buffer memory!"); } @@ -1002,7 +1018,7 @@ class HelloTriangleApplication { } VkCommandBuffer beginSingleTimeCommands() { - VkCommandBufferAllocateInfo allocInfo = {}; + VkCommandBufferAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; allocInfo.commandPool = commandPool; @@ -1011,7 +1027,7 @@ class HelloTriangleApplication { VkCommandBuffer commandBuffer; vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer); - VkCommandBufferBeginInfo beginInfo = {}; + VkCommandBufferBeginInfo beginInfo{}; beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; @@ -1023,7 +1039,7 @@ class HelloTriangleApplication { void endSingleTimeCommands(VkCommandBuffer commandBuffer) { vkEndCommandBuffer(commandBuffer); - VkSubmitInfo submitInfo = {}; + VkSubmitInfo submitInfo{}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &commandBuffer; @@ -1037,7 +1053,7 @@ class HelloTriangleApplication { void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { VkCommandBuffer commandBuffer = beginSingleTimeCommands(); - VkBufferCopy copyRegion = {}; + VkBufferCopy copyRegion{}; copyRegion.size = size; vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); @@ -1058,13 +1074,9 @@ class HelloTriangleApplication { } void createCommandBuffers() { - if (commandBuffers.size() > 0) { - vkFreeCommandBuffers(device, commandPool, commandBuffers.size(), commandBuffers.data()); - } + commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); - commandBuffers.resize(swapChainFramebuffers.size()); - - VkCommandBufferAllocateInfo allocInfo = {}; + VkCommandBufferAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; allocInfo.commandPool = commandPool; allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; @@ -1073,81 +1085,106 @@ class HelloTriangleApplication { 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; + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; - vkBeginCommandBuffer(commandBuffers[i], &beginInfo); + if (vkBeginCommandBuffer(commandBuffer, &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; + VkRenderPassBeginInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = renderPass; + renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex]; + renderPassInfo.renderArea.offset = {0, 0}; + renderPassInfo.renderArea.extent = swapChainExtent; - VkClearValue clearColor = {0.0f, 0.0f, 0.0f, 1.0f}; - renderPassInfo.clearValueCount = 1; - renderPassInfo.pClearValues = &clearColor; + VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; + renderPassInfo.clearValueCount = 1; + renderPassInfo.pClearValues = &clearColor; - vkCmdBeginRenderPass(commandBuffers[i], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); - vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); - VkBuffer vertexBuffers[] = {vertexBuffer}; - VkDeviceSize offsets[] = {0}; - vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets); + 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; + vkCmdSetViewport(commandBuffer, 0, 1, &viewport); - vkCmdBindIndexBuffer(commandBuffers[i], indexBuffer, 0, VK_INDEX_TYPE_UINT16); + VkRect2D scissor{}; + scissor.offset = {0, 0}; + scissor.extent = swapChainExtent; + vkCmdSetScissor(commandBuffer, 0, 1, &scissor); - vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr); + VkBuffer vertexBuffers[] = {vertexBuffer}; + VkDeviceSize offsets[] = {0}; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); - vkCmdDrawIndexed(commandBuffers[i], indices.size(), 1, 0, 0, 0); + vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT16); - vkCmdEndRenderPass(commandBuffers[i]); + vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets[currentFrame], 0, nullptr); - if (vkEndCommandBuffer(commandBuffers[i]) != VK_SUCCESS) { - throw std::runtime_error("failed to record command buffer!"); - } + vkCmdDrawIndexed(commandBuffer, static_cast(indices.size()), 1, 0, 0, 0); + + vkCmdEndRenderPass(commandBuffer); + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); } } - void createSemaphores() { - VkSemaphoreCreateInfo semaphoreInfo = {}; + 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)); + 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, 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, UINT64_MAX); + uint32_t imageIndex; - VkResult result = vkAcquireNextImageKHR(device, swapChain, std::numeric_limits::max(), imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex); + VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); if (result == VK_ERROR_OUT_OF_DATE_KHR) { recreateSwapChain(); @@ -1156,27 +1193,34 @@ class HelloTriangleApplication { throw std::runtime_error("failed to acquire swap chain image!"); } - VkSubmitInfo submitInfo = {}; + updateUniformBuffer(currentFrame); + + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffers[currentFrame], 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; submitInfo.pWaitDstStageMask = waitStages; submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &commandBuffers[imageIndex]; + submitInfo.pCommandBuffers = &commandBuffers[currentFrame]; - 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!"); } - VkPresentInfoKHR presentInfo = {}; + VkPresentInfoKHR presentInfo{}; presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; presentInfo.waitSemaphoreCount = 1; @@ -1190,35 +1234,33 @@ 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) { - VkShaderModuleCreateInfo createInfo = {}; + 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) { - 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) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { return availableFormat; } } @@ -1226,18 +1268,14 @@ class HelloTriangleApplication { return availableFormats[0]; } - VkPresentModeKHR chooseSwapPresentMode(const std::vector availablePresentModes) { - VkPresentModeKHR bestMode = VK_PRESENT_MODE_FIFO_KHR; - + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { 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; + return VK_PRESENT_MODE_FIFO_KHR; } VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { @@ -1245,12 +1283,15 @@ 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)); + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); return actualExtent; } @@ -1291,7 +1332,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) { @@ -1321,14 +1365,14 @@ class HelloTriangleApplication { int i = 0; for (const auto& queueFamily : queueFamilies) { - if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { indices.graphicsFamily = i; } VkBool32 presentSupport = false; vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); - if (queueFamily.queueCount > 0 && presentSupport) { + if (presentSupport) { indices.presentFamily = i; } @@ -1343,18 +1387,14 @@ 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); + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); } return extensions; @@ -1403,8 +1443,8 @@ class HelloTriangleApplication { 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; + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; return VK_FALSE; } @@ -1415,10 +1455,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/26_shader_textures.frag similarity index 79% rename from code/shader_depth.frag rename to code/26_shader_textures.frag index 1de4e4cd..873f5410 100644 --- a/code/shader_depth.frag +++ b/code/26_shader_textures.frag @@ -1,13 +1,12 @@ -#version 450 -#extension GL_ARB_separate_shader_objects : enable - -layout(binding = 1) uniform sampler2D texSampler; - -layout(location = 0) in vec3 fragColor; -layout(location = 1) in vec2 fragTexCoord; - -layout(location = 0) out vec4 outColor; - -void main() { - outColor = texture(texSampler, fragTexCoord); -} \ No newline at end of file +#version 450 + +layout(binding = 1) uniform sampler2D texSampler; + +layout(location = 0) in vec3 fragColor; +layout(location = 1) in vec2 fragTexCoord; + +layout(location = 0) out vec4 outColor; + +void main() { + outColor = texture(texSampler, fragTexCoord); +} diff --git a/code/shader_textures.vert b/code/26_shader_textures.vert similarity index 79% rename from code/shader_textures.vert rename to code/26_shader_textures.vert index 903f4f78..5510aa3f 100644 --- a/code/shader_textures.vert +++ b/code/26_shader_textures.vert @@ -1,25 +1,20 @@ -#version 450 -#extension GL_ARB_separate_shader_objects : enable - -layout(binding = 0) uniform UniformBufferObject { - mat4 model; - mat4 view; - mat4 proj; -} ubo; - -layout(location = 0) in vec2 inPosition; -layout(location = 1) in vec3 inColor; -layout(location = 2) in vec2 inTexCoord; - -layout(location = 0) out vec3 fragColor; -layout(location = 1) out vec2 fragTexCoord; - -out gl_PerVertex { - vec4 gl_Position; -}; - -void main() { - gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 0.0, 1.0); - fragColor = inColor; - fragTexCoord = inTexCoord; -} \ No newline at end of file +#version 450 + +layout(binding = 0) uniform UniformBufferObject { + mat4 model; + mat4 view; + mat4 proj; +} ubo; + +layout(location = 0) in vec2 inPosition; +layout(location = 1) in vec3 inColor; +layout(location = 2) in vec2 inTexCoord; + +layout(location = 0) out vec3 fragColor; +layout(location = 1) out vec2 fragTexCoord; + +void main() { + gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 0.0, 1.0); + fragColor = inColor; + fragTexCoord = inTexCoord; +} diff --git a/code/texture_mapping.cpp b/code/26_texture_mapping.cpp similarity index 58% rename from code/texture_mapping.cpp rename to code/26_texture_mapping.cpp index 89ca8d41..fe0ab6a5 100644 --- a/code/texture_mapping.cpp +++ b/code/26_texture_mapping.cpp @@ -9,21 +9,26 @@ #include #include -#include -#include -#include #include +#include #include +#include #include #include +#include +#include +#include #include +#include #include -const int WIDTH = 800; -const int HEIGHT = 600; +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const int MAX_FRAMES_IN_FLIGHT = 2; const std::vector validationLayers = { - "VK_LAYER_LUNARG_standard_validation" + "VK_LAYER_KHRONOS_validation" }; const std::vector deviceExtensions = { @@ -36,86 +41,28 @@ const bool enableValidationLayers = false; const bool enableValidationLayers = true; #endif -VkResult CreateDebugReportCallbackEXT(VkInstance instance, const VkDebugReportCallbackCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugReportCallbackEXT* pCallback) { - auto func = (PFN_vkCreateDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT"); +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); if (func != nullptr) { - return func(instance, pCreateInfo, pAllocator, pCallback); + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); } else { return VK_ERROR_EXTENSION_NOT_PRESENT; } } -void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT callback, const VkAllocationCallbacks* pAllocator) { - auto func = (PFN_vkDestroyDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT"); +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); if (func != nullptr) { - func(instance, callback, pAllocator); + func(instance, debugMessenger, pAllocator); } } -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; + std::optional graphicsFamily; + std::optional presentFamily; bool isComplete() { - return graphicsFamily >= 0 && presentFamily >= 0; + return graphicsFamily.has_value() && presentFamily.has_value(); } }; @@ -131,7 +78,7 @@ struct Vertex { glm::vec2 texCoord; static VkVertexInputBindingDescription getBindingDescription() { - VkVertexInputBindingDescription bindingDescription = {}; + VkVertexInputBindingDescription bindingDescription{}; bindingDescription.binding = 0; bindingDescription.stride = sizeof(Vertex); bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; @@ -140,7 +87,7 @@ struct Vertex { } static std::array getAttributeDescriptions() { - std::array attributeDescriptions = {}; + std::array attributeDescriptions{}; attributeDescriptions[0].binding = 0; attributeDescriptions[0].location = 0; @@ -162,16 +109,16 @@ struct Vertex { }; struct UniformBufferObject { - glm::mat4 model; - glm::mat4 view; - glm::mat4 proj; + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; }; const std::vector vertices = { - {{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}, {0.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 +131,60 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + 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; + uint32_t currentFrame = 0; + + bool framebufferResized = false; void initWindow() { glfwInit(); @@ -242,14 +192,18 @@ 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() { createInstance(); - setupDebugCallback(); + setupDebugMessenger(); createSurface(); pickPhysicalDevice(); createLogicalDevice(); @@ -265,44 +219,99 @@ 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(); + 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(); + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroyBuffer(device, uniformBuffers[i], nullptr); + vkFreeMemory(device, uniformBuffersMemory[i], nullptr); + } + + vkDestroyDescriptorPool(device, descriptorPool, nullptr); + + vkDestroySampler(device, textureSampler, nullptr); + vkDestroyImageView(device, textureImageView, nullptr); + + vkDestroyImage(device, textureImage, nullptr); + vkFreeMemory(device, textureImageMemory, nullptr); + + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, 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) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); } void recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + vkDeviceWaitIdle(device); + cleanupSwapChain(); + createSwapChain(); createImageViews(); - createRenderPass(); - createGraphicsPipeline(); createFramebuffers(); - createCommandBuffers(); } void createInstance() { @@ -310,7 +319,7 @@ class HelloTriangleApplication { throw std::runtime_error("validation layers requested, but not available!"); } - VkApplicationInfo appInfo = {}; + VkApplicationInfo appInfo{}; appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; appInfo.pApplicationName = "Hello Triangle"; appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); @@ -318,41 +327,53 @@ class HelloTriangleApplication { appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); appInfo.apiVersion = VK_API_VERSION_1_0; - VkInstanceCreateInfo createInfo = {}; + VkInstanceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; } else { createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } - void setupDebugCallback() { + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { 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; + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { - throw std::runtime_error("failed to set up debug callback!"); + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); } } 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,11 +405,11 @@ class HelloTriangleApplication { QueueFamilyIndices indices = findQueueFamilies(physicalDevice); std::vector queueCreateInfos; - std::set uniqueQueueFamilies = {indices.graphicsFamily, indices.presentFamily}; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; float queuePriority = 1.0f; - for (int queueFamily : uniqueQueueFamilies) { - VkDeviceQueueCreateInfo queueCreateInfo = {}; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; queueCreateInfo.queueFamilyIndex = queueFamily; queueCreateInfo.queueCount = 1; @@ -396,32 +417,33 @@ class HelloTriangleApplication { queueCreateInfos.push_back(queueCreateInfo); } - VkPhysicalDeviceFeatures deviceFeatures = {}; + VkPhysicalDeviceFeatures deviceFeatures{}; + deviceFeatures.samplerAnisotropy = VK_TRUE; - VkDeviceCreateInfo createInfo = {}; + 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!"); } - vkGetDeviceQueue(device, indices.graphicsFamily, 0, &graphicsQueue); - vkGetDeviceQueue(device, indices.presentFamily, 0, &presentQueue); + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); } void createSwapChain() { @@ -436,7 +458,7 @@ class HelloTriangleApplication { imageCount = swapChainSupport.capabilities.maxImageCount; } - VkSwapchainCreateInfoKHR createInfo = {}; + VkSwapchainCreateInfoKHR createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; createInfo.surface = surface; @@ -448,7 +470,7 @@ class HelloTriangleApplication { createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; QueueFamilyIndices indices = findQueueFamilies(physicalDevice); - uint32_t queueFamilyIndices[] = {(uint32_t) indices.graphicsFamily, (uint32_t) indices.presentFamily}; + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; if (indices.graphicsFamily != indices.presentFamily) { createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; @@ -463,16 +485,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,15 +498,15 @@ 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); } } void createRenderPass() { - VkAttachmentDescription colorAttachment = {}; + VkAttachmentDescription colorAttachment{}; colorAttachment.format = swapChainImageFormat; colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; @@ -500,24 +516,24 @@ class HelloTriangleApplication { colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - VkAttachmentReference colorAttachmentRef = {}; + VkAttachmentReference colorAttachmentRef{}; colorAttachmentRef.attachment = 0; colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - VkSubpassDescription subpass = {}; + VkSubpassDescription subpass{}; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass.colorAttachmentCount = 1; subpass.pColorAttachments = &colorAttachmentRef; - VkSubpassDependency dependency = {}; + 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; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; - VkRenderPassCreateInfo renderPassInfo = {}; + VkRenderPassCreateInfo renderPassInfo{}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; renderPassInfo.attachmentCount = 1; renderPassInfo.pAttachments = &colorAttachment; @@ -526,33 +542,33 @@ 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!"); } } void createDescriptorSetLayout() { - VkDescriptorSetLayoutBinding uboLayoutBinding = {}; + 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 = {}; + 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 = {}; + 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,18 +577,16 @@ 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 = {}; + 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 = {}; + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; fragShaderStageInfo.module = fragShaderModule; @@ -580,42 +594,28 @@ class HelloTriangleApplication { VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; - VkPipelineVertexInputStateCreateInfo vertexInputInfo = {}; + 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 = attributeDescriptions.size(); + vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()); vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); - VkPipelineInputAssemblyStateCreateInfo inputAssembly = {}; + 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 = {}; + 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 = {}; + VkPipelineRasterizationStateCreateInfo rasterizer{}; rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; rasterizer.depthClampEnable = VK_FALSE; rasterizer.rasterizerDiscardEnable = VK_FALSE; @@ -625,16 +625,16 @@ class HelloTriangleApplication { rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; rasterizer.depthBiasEnable = VK_FALSE; - VkPipelineMultisampleStateCreateInfo multisampling = {}; + VkPipelineMultisampleStateCreateInfo multisampling{}; multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; multisampling.sampleShadingEnable = VK_FALSE; multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; - VkPipelineColorBlendAttachmentState colorBlendAttachment = {}; + 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 = {}; + VkPipelineColorBlendStateCreateInfo colorBlending{}; colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; colorBlending.logicOpEnable = VK_FALSE; colorBlending.logicOp = VK_LOGIC_OP_COPY; @@ -645,17 +645,25 @@ class HelloTriangleApplication { colorBlending.blendConstants[2] = 0.0f; colorBlending.blendConstants[3] = 0.0f; - VkDescriptorSetLayout setLayouts[] = {descriptorSetLayout}; - VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; + std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + 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!"); } - VkGraphicsPipelineCreateInfo pipelineInfo = {}; + VkGraphicsPipelineCreateInfo pipelineInfo{}; pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; pipelineInfo.stageCount = 2; pipelineInfo.pStages = shaderStages; @@ -665,25 +673,29 @@ class HelloTriangleApplication { pipelineInfo.pRasterizationState = &rasterizer; pipelineInfo.pMultisampleState = &multisampling; pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = &dynamicState; 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) { + 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[] = { swapChainImageViews[i] }; - VkFramebufferCreateInfo framebufferInfo = {}; + VkFramebufferCreateInfo framebufferInfo{}; framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; framebufferInfo.renderPass = renderPass; framebufferInfo.attachmentCount = 1; @@ -692,7 +704,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!"); } } @@ -701,11 +713,12 @@ class HelloTriangleApplication { void createCommandPool() { QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); - VkCommandPoolCreateInfo poolInfo = {}; + VkCommandPoolCreateInfo poolInfo{}; poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; - poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); - 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,50 +732,36 @@ 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); + createImage(texWidth, texHeight, VK_FORMAT_R8G8B8A8_SRGB, 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_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); + transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); - 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_SRGB); } void createTextureSampler() { - VkSamplerCreateInfo samplerInfo = {}; + VkPhysicalDeviceProperties properties{}; + vkGetPhysicalDeviceProperties(physicalDevice, &properties); + + VkSamplerCreateInfo samplerInfo{}; samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; samplerInfo.magFilter = VK_FILTER_LINEAR; samplerInfo.minFilter = VK_FILTER_LINEAR; @@ -770,20 +769,20 @@ class HelloTriangleApplication { samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; samplerInfo.anisotropyEnable = VK_TRUE; - samplerInfo.maxAnisotropy = 16; + samplerInfo.maxAnisotropy = properties.limits.maxSamplerAnisotropy; 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; - 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) { - VkImageViewCreateInfo viewInfo = {}; + VkImageView createImageView(VkImage image, VkFormat format) { + VkImageViewCreateInfo viewInfo{}; viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; viewInfo.image = image; viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; @@ -794,13 +793,16 @@ 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) { - VkImageCreateInfo imageInfo = {}; + 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; imageInfo.extent.width = width; @@ -810,24 +812,24 @@ 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!"); } VkMemoryRequirements memRequirements; vkGetImageMemoryRequirements(device, image, &memRequirements); - VkMemoryAllocateInfo allocInfo = {}; + 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.replace()) != VK_SUCCESS) { + if (vkAllocateMemory(device, &allocInfo, nullptr, &imageMemory) != VK_SUCCESS) { throw std::runtime_error("failed to allocate image memory!"); } @@ -837,7 +839,7 @@ class HelloTriangleApplication { void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout) { VkCommandBuffer commandBuffer = beginSingleTimeCommands(); - VkImageMemoryBarrier barrier = {}; + VkImageMemoryBarrier barrier{}; barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; barrier.oldLayout = oldLayout; barrier.newLayout = newLayout; @@ -850,22 +852,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 +883,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 +909,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 +921,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,96 +941,106 @@ 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(MAX_FRAMES_IN_FLIGHT); + uniformBuffersMemory.resize(MAX_FRAMES_IN_FLIGHT); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; 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 = {}; + std::array poolSizes{}; poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - poolSizes[0].descriptorCount = 1; + poolSizes[0].descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT); poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - poolSizes[1].descriptorCount = 1; + poolSizes[1].descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT); - VkDescriptorPoolCreateInfo poolInfo = {}; + 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(MAX_FRAMES_IN_FLIGHT); - 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}; - VkDescriptorSetAllocateInfo allocInfo = {}; + void createDescriptorSets() { + std::vector layouts(MAX_FRAMES_IN_FLIGHT, 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(MAX_FRAMES_IN_FLIGHT); + allocInfo.pSetLayouts = layouts.data(); - if (vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet) != VK_SUCCESS) { - throw std::runtime_error("failed to allocate descriptor set!"); + descriptorSets.resize(MAX_FRAMES_IN_FLIGHT); + if (vkAllocateDescriptorSets(device, &allocInfo, descriptorSets.data()) != 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 < MAX_FRAMES_IN_FLIGHT; 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) { - VkBufferCreateInfo bufferInfo = {}; + 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!"); } VkMemoryRequirements memRequirements; vkGetBufferMemoryRequirements(device, buffer, &memRequirements); - VkMemoryAllocateInfo allocInfo = {}; + 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.replace()) != VK_SUCCESS) { + if (vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) { throw std::runtime_error("failed to allocate buffer memory!"); } @@ -1032,7 +1048,7 @@ class HelloTriangleApplication { } VkCommandBuffer beginSingleTimeCommands() { - VkCommandBufferAllocateInfo allocInfo = {}; + VkCommandBufferAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; allocInfo.commandPool = commandPool; @@ -1041,7 +1057,7 @@ class HelloTriangleApplication { VkCommandBuffer commandBuffer; vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer); - VkCommandBufferBeginInfo beginInfo = {}; + VkCommandBufferBeginInfo beginInfo{}; beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; @@ -1053,7 +1069,7 @@ class HelloTriangleApplication { void endSingleTimeCommands(VkCommandBuffer commandBuffer) { vkEndCommandBuffer(commandBuffer); - VkSubmitInfo submitInfo = {}; + VkSubmitInfo submitInfo{}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &commandBuffer; @@ -1067,7 +1083,7 @@ class HelloTriangleApplication { void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { VkCommandBuffer commandBuffer = beginSingleTimeCommands(); - VkBufferCopy copyRegion = {}; + VkBufferCopy copyRegion{}; copyRegion.size = size; vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); @@ -1088,13 +1104,9 @@ class HelloTriangleApplication { } void createCommandBuffers() { - if (commandBuffers.size() > 0) { - vkFreeCommandBuffers(device, commandPool, commandBuffers.size(), commandBuffers.data()); - } + commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); - commandBuffers.resize(swapChainFramebuffers.size()); - - VkCommandBufferAllocateInfo allocInfo = {}; + VkCommandBufferAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; allocInfo.commandPool = commandPool; allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; @@ -1103,81 +1115,106 @@ class HelloTriangleApplication { 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; + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; - vkBeginCommandBuffer(commandBuffers[i], &beginInfo); + if (vkBeginCommandBuffer(commandBuffer, &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; + VkRenderPassBeginInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = renderPass; + renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex]; + renderPassInfo.renderArea.offset = {0, 0}; + renderPassInfo.renderArea.extent = swapChainExtent; - VkClearValue clearColor = {0.0f, 0.0f, 0.0f, 1.0f}; - renderPassInfo.clearValueCount = 1; - renderPassInfo.pClearValues = &clearColor; + VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; + renderPassInfo.clearValueCount = 1; + renderPassInfo.pClearValues = &clearColor; - vkCmdBeginRenderPass(commandBuffers[i], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); - vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); - VkBuffer vertexBuffers[] = {vertexBuffer}; - VkDeviceSize offsets[] = {0}; - vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets); + 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; + vkCmdSetViewport(commandBuffer, 0, 1, &viewport); - vkCmdBindIndexBuffer(commandBuffers[i], indexBuffer, 0, VK_INDEX_TYPE_UINT16); + VkRect2D scissor{}; + scissor.offset = {0, 0}; + scissor.extent = swapChainExtent; + vkCmdSetScissor(commandBuffer, 0, 1, &scissor); - vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr); + VkBuffer vertexBuffers[] = {vertexBuffer}; + VkDeviceSize offsets[] = {0}; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); - vkCmdDrawIndexed(commandBuffers[i], indices.size(), 1, 0, 0, 0); + vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT16); - vkCmdEndRenderPass(commandBuffers[i]); + vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets[currentFrame], 0, nullptr); - if (vkEndCommandBuffer(commandBuffers[i]) != VK_SUCCESS) { - throw std::runtime_error("failed to record command buffer!"); - } + vkCmdDrawIndexed(commandBuffer, static_cast(indices.size()), 1, 0, 0, 0); + + vkCmdEndRenderPass(commandBuffer); + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); } } - void createSemaphores() { - VkSemaphoreCreateInfo semaphoreInfo = {}; + 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)); + 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, 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, UINT64_MAX); + uint32_t imageIndex; - VkResult result = vkAcquireNextImageKHR(device, swapChain, std::numeric_limits::max(), imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex); + VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); if (result == VK_ERROR_OUT_OF_DATE_KHR) { recreateSwapChain(); @@ -1186,27 +1223,34 @@ class HelloTriangleApplication { throw std::runtime_error("failed to acquire swap chain image!"); } - VkSubmitInfo submitInfo = {}; + updateUniformBuffer(currentFrame); + + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffers[currentFrame], 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; submitInfo.pWaitDstStageMask = waitStages; submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &commandBuffers[imageIndex]; + submitInfo.pCommandBuffers = &commandBuffers[currentFrame]; - 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!"); } - VkPresentInfoKHR presentInfo = {}; + VkPresentInfoKHR presentInfo{}; presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; presentInfo.waitSemaphoreCount = 1; @@ -1220,35 +1264,33 @@ 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) { - VkShaderModuleCreateInfo createInfo = {}; + 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) { - 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) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { return availableFormat; } } @@ -1256,18 +1298,14 @@ class HelloTriangleApplication { return availableFormats[0]; } - VkPresentModeKHR chooseSwapPresentMode(const std::vector availablePresentModes) { - VkPresentModeKHR bestMode = VK_PRESENT_MODE_FIFO_KHR; - + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { 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; + return VK_PRESENT_MODE_FIFO_KHR; } VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { @@ -1275,12 +1313,15 @@ 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)); + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); return actualExtent; } @@ -1321,7 +1362,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) { @@ -1351,14 +1395,14 @@ class HelloTriangleApplication { int i = 0; for (const auto& queueFamily : queueFamilies) { - if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { indices.graphicsFamily = i; } VkBool32 presentSupport = false; vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); - if (queueFamily.queueCount > 0 && presentSupport) { + if (presentSupport) { indices.presentFamily = i; } @@ -1373,18 +1417,14 @@ 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); + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); } return extensions; @@ -1433,8 +1473,8 @@ class HelloTriangleApplication { 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; + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; return VK_FALSE; } @@ -1445,10 +1485,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/27_depth_buffering.cpp similarity index 59% rename from code/depth_buffering.cpp rename to code/27_depth_buffering.cpp index 5aa24560..1f10fff4 100644 --- a/code/depth_buffering.cpp +++ b/code/27_depth_buffering.cpp @@ -10,21 +10,26 @@ #include #include -#include -#include -#include #include +#include #include +#include #include #include +#include +#include +#include #include +#include #include -const int WIDTH = 800; -const int HEIGHT = 600; +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const int MAX_FRAMES_IN_FLIGHT = 2; const std::vector validationLayers = { - "VK_LAYER_LUNARG_standard_validation" + "VK_LAYER_KHRONOS_validation" }; const std::vector deviceExtensions = { @@ -37,86 +42,28 @@ const bool enableValidationLayers = false; const bool enableValidationLayers = true; #endif -VkResult CreateDebugReportCallbackEXT(VkInstance instance, const VkDebugReportCallbackCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugReportCallbackEXT* pCallback) { - auto func = (PFN_vkCreateDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT"); +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); if (func != nullptr) { - return func(instance, pCreateInfo, pAllocator, pCallback); + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); } else { return VK_ERROR_EXTENSION_NOT_PRESENT; } } -void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT callback, const VkAllocationCallbacks* pAllocator) { - auto func = (PFN_vkDestroyDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT"); +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); if (func != nullptr) { - func(instance, callback, pAllocator); + func(instance, debugMessenger, pAllocator); } } -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; + std::optional graphicsFamily; + std::optional presentFamily; bool isComplete() { - return graphicsFamily >= 0 && presentFamily >= 0; + return graphicsFamily.has_value() && presentFamily.has_value(); } }; @@ -132,7 +79,7 @@ struct Vertex { glm::vec2 texCoord; static VkVertexInputBindingDescription getBindingDescription() { - VkVertexInputBindingDescription bindingDescription = {}; + VkVertexInputBindingDescription bindingDescription{}; bindingDescription.binding = 0; bindingDescription.stride = sizeof(Vertex); bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; @@ -141,7 +88,7 @@ struct Vertex { } static std::array getAttributeDescriptions() { - std::array attributeDescriptions = {}; + std::array attributeDescriptions{}; attributeDescriptions[0].binding = 0; attributeDescriptions[0].location = 0; @@ -163,21 +110,21 @@ struct Vertex { }; struct UniformBufferObject { - glm::mat4 model; - glm::mat4 view; - glm::mat4 proj; + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; }; 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 +138,64 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + 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; + uint32_t currentFrame = 0; + + bool framebufferResized = false; void initWindow() { glfwInit(); @@ -253,14 +203,18 @@ 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() { createInstance(); - setupDebugCallback(); + setupDebugMessenger(); createSurface(); pickPhysicalDevice(); createLogicalDevice(); @@ -277,45 +231,104 @@ 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); + } + + 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(); + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroyBuffer(device, uniformBuffers[i], nullptr); + vkFreeMemory(device, uniformBuffersMemory[i], nullptr); + } + + vkDestroyDescriptorPool(device, descriptorPool, nullptr); + + vkDestroySampler(device, textureSampler, nullptr); + vkDestroyImageView(device, textureImageView, nullptr); + + vkDestroyImage(device, textureImage, nullptr); + vkFreeMemory(device, textureImageMemory, nullptr); - HelloTriangleApplication* app = reinterpret_cast(glfwGetWindowUserPointer(window)); - app->recreateSwapChain(); + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, 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) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); } void recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + vkDeviceWaitIdle(device); + cleanupSwapChain(); + createSwapChain(); createImageViews(); - createRenderPass(); - createGraphicsPipeline(); createDepthResources(); createFramebuffers(); - createCommandBuffers(); } void createInstance() { @@ -323,7 +336,7 @@ class HelloTriangleApplication { throw std::runtime_error("validation layers requested, but not available!"); } - VkApplicationInfo appInfo = {}; + VkApplicationInfo appInfo{}; appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; appInfo.pApplicationName = "Hello Triangle"; appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); @@ -331,41 +344,53 @@ class HelloTriangleApplication { appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); appInfo.apiVersion = VK_API_VERSION_1_0; - VkInstanceCreateInfo createInfo = {}; + VkInstanceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; } else { createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } - void setupDebugCallback() { + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { 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; + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { - throw std::runtime_error("failed to set up debug callback!"); + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); } } 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,11 +422,11 @@ class HelloTriangleApplication { QueueFamilyIndices indices = findQueueFamilies(physicalDevice); std::vector queueCreateInfos; - std::set uniqueQueueFamilies = {indices.graphicsFamily, indices.presentFamily}; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; float queuePriority = 1.0f; - for (int queueFamily : uniqueQueueFamilies) { - VkDeviceQueueCreateInfo queueCreateInfo = {}; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; queueCreateInfo.queueFamilyIndex = queueFamily; queueCreateInfo.queueCount = 1; @@ -409,32 +434,33 @@ class HelloTriangleApplication { queueCreateInfos.push_back(queueCreateInfo); } - VkPhysicalDeviceFeatures deviceFeatures = {}; + VkPhysicalDeviceFeatures deviceFeatures{}; + deviceFeatures.samplerAnisotropy = VK_TRUE; - VkDeviceCreateInfo createInfo = {}; + 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!"); } - vkGetDeviceQueue(device, indices.graphicsFamily, 0, &graphicsQueue); - vkGetDeviceQueue(device, indices.presentFamily, 0, &presentQueue); + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); } void createSwapChain() { @@ -449,7 +475,7 @@ class HelloTriangleApplication { imageCount = swapChainSupport.capabilities.maxImageCount; } - VkSwapchainCreateInfoKHR createInfo = {}; + VkSwapchainCreateInfoKHR createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; createInfo.surface = surface; @@ -461,7 +487,7 @@ class HelloTriangleApplication { createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; QueueFamilyIndices indices = findQueueFamilies(physicalDevice); - uint32_t queueFamilyIndices[] = {(uint32_t) indices.graphicsFamily, (uint32_t) indices.presentFamily}; + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; if (indices.graphicsFamily != indices.presentFamily) { createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; @@ -476,16 +502,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,15 +515,15 @@ 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); } } void createRenderPass() { - VkAttachmentDescription colorAttachment = {}; + VkAttachmentDescription colorAttachment{}; colorAttachment.format = swapChainImageFormat; colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; @@ -513,7 +533,7 @@ class HelloTriangleApplication { colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - VkAttachmentDescription depthAttachment = {}; + VkAttachmentDescription depthAttachment{}; depthAttachment.format = findDepthFormat(); depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT; depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; @@ -523,52 +543,52 @@ class HelloTriangleApplication { depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - VkAttachmentReference colorAttachmentRef = {}; + VkAttachmentReference colorAttachmentRef{}; colorAttachmentRef.attachment = 0; colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - VkAttachmentReference depthAttachmentRef = {}; + VkAttachmentReference depthAttachmentRef{}; depthAttachmentRef.attachment = 1; depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - VkSubpassDescription subpass = {}; + VkSubpassDescription subpass{}; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass.colorAttachmentCount = 1; subpass.pColorAttachments = &colorAttachmentRef; subpass.pDepthStencilAttachment = &depthAttachmentRef; - VkSubpassDependency dependency = {}; + VkSubpassDependency dependency{}; dependency.srcSubpass = VK_SUBPASS_EXTERNAL; dependency.dstSubpass = 0; - dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_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; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; std::array attachments = {colorAttachment, depthAttachment}; - VkRenderPassCreateInfo renderPassInfo = {}; + 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!"); } } void createDescriptorSetLayout() { - VkDescriptorSetLayoutBinding uboLayoutBinding = {}; + 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 = {}; + VkDescriptorSetLayoutBinding samplerLayoutBinding{}; samplerLayoutBinding.binding = 1; samplerLayoutBinding.descriptorCount = 1; samplerLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; @@ -576,12 +596,12 @@ class HelloTriangleApplication { samplerLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; std::array bindings = {uboLayoutBinding, samplerLayoutBinding}; - VkDescriptorSetLayoutCreateInfo layoutInfo = {}; + 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,18 +610,16 @@ 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 = {}; + 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 = {}; + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; fragShaderStageInfo.module = fragShaderModule; @@ -609,42 +627,28 @@ class HelloTriangleApplication { VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; - VkPipelineVertexInputStateCreateInfo vertexInputInfo = {}; + 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 = attributeDescriptions.size(); + vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()); vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); - VkPipelineInputAssemblyStateCreateInfo inputAssembly = {}; + 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 = {}; + 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 = {}; + VkPipelineRasterizationStateCreateInfo rasterizer{}; rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; rasterizer.depthClampEnable = VK_FALSE; rasterizer.rasterizerDiscardEnable = VK_FALSE; @@ -654,12 +658,12 @@ class HelloTriangleApplication { rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; rasterizer.depthBiasEnable = VK_FALSE; - VkPipelineMultisampleStateCreateInfo multisampling = {}; + 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 = {}; + VkPipelineDepthStencilStateCreateInfo depthStencil{}; depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; depthStencil.depthTestEnable = VK_TRUE; depthStencil.depthWriteEnable = VK_TRUE; @@ -667,11 +671,11 @@ class HelloTriangleApplication { depthStencil.depthBoundsTestEnable = VK_FALSE; depthStencil.stencilTestEnable = VK_FALSE; - VkPipelineColorBlendAttachmentState colorBlendAttachment = {}; + 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 = {}; + VkPipelineColorBlendStateCreateInfo colorBlending{}; colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; colorBlending.logicOpEnable = VK_FALSE; colorBlending.logicOp = VK_LOGIC_OP_COPY; @@ -682,17 +686,25 @@ class HelloTriangleApplication { colorBlending.blendConstants[2] = 0.0f; colorBlending.blendConstants[3] = 0.0f; - VkDescriptorSetLayout setLayouts[] = {descriptorSetLayout}; - VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; + std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + 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!"); } - VkGraphicsPipelineCreateInfo pipelineInfo = {}; + VkGraphicsPipelineCreateInfo pipelineInfo{}; pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; pipelineInfo.stageCount = 2; pipelineInfo.pStages = shaderStages; @@ -703,18 +715,22 @@ class HelloTriangleApplication { pipelineInfo.pMultisampleState = &multisampling; pipelineInfo.pDepthStencilState = &depthStencil; pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = &dynamicState; 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) { + 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 = { @@ -722,16 +738,16 @@ class HelloTriangleApplication { depthImageView }; - VkFramebufferCreateInfo framebufferInfo = {}; + 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!"); } } @@ -740,11 +756,12 @@ class HelloTriangleApplication { void createCommandPool() { QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); - VkCommandPoolCreateInfo poolInfo = {}; + VkCommandPoolCreateInfo poolInfo{}; poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; - poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); - 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,9 +770,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); - - transitionImageLayout(depthImage, depthFormat, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); + depthImageView = createImageView(depthImage, depthFormat, VK_IMAGE_ASPECT_DEPTH_BIT); } VkFormat findSupportedFormat(const std::vector& candidates, VkImageTiling tiling, VkFormatFeatureFlags features) { @@ -794,50 +809,36 @@ 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); + createImage(texWidth, texHeight, VK_FORMAT_R8G8B8A8_SRGB, 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_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); + transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); - 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_SRGB, VK_IMAGE_ASPECT_COLOR_BIT); } void createTextureSampler() { - VkSamplerCreateInfo samplerInfo = {}; + VkPhysicalDeviceProperties properties{}; + vkGetPhysicalDeviceProperties(physicalDevice, &properties); + + VkSamplerCreateInfo samplerInfo{}; samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; samplerInfo.magFilter = VK_FILTER_LINEAR; samplerInfo.minFilter = VK_FILTER_LINEAR; @@ -845,20 +846,20 @@ class HelloTriangleApplication { samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; samplerInfo.anisotropyEnable = VK_TRUE; - samplerInfo.maxAnisotropy = 16; + samplerInfo.maxAnisotropy = properties.limits.maxSamplerAnisotropy; 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; - 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) { - VkImageViewCreateInfo viewInfo = {}; + VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags) { + VkImageViewCreateInfo viewInfo{}; viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; viewInfo.image = image; viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; @@ -869,13 +870,16 @@ 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) { - VkImageCreateInfo imageInfo = {}; + 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; imageInfo.extent.width = width; @@ -885,24 +889,24 @@ 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!"); } VkMemoryRequirements memRequirements; vkGetImageMemoryRequirements(device, image, &memRequirements); - VkMemoryAllocateInfo allocInfo = {}; + 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.replace()) != VK_SUCCESS) { + if (vkAllocateMemory(device, &allocInfo, nullptr, &imageMemory) != VK_SUCCESS) { throw std::runtime_error("failed to allocate image memory!"); } @@ -912,48 +916,41 @@ class HelloTriangleApplication { void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout) { VkCommandBuffer commandBuffer = beginSingleTimeCommands(); - VkImageMemoryBarrier barrier = {}; + 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.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; barrier.subresourceRange.baseMipLevel = 0; barrier.subresourceRange.levelCount = 1; 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; - } 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_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, @@ -963,30 +960,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 +986,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 +998,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,96 +1018,106 @@ 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(MAX_FRAMES_IN_FLIGHT); + uniformBuffersMemory.resize(MAX_FRAMES_IN_FLIGHT); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; 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 = {}; + std::array poolSizes{}; poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - poolSizes[0].descriptorCount = 1; + poolSizes[0].descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT); poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - poolSizes[1].descriptorCount = 1; + poolSizes[1].descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT); - VkDescriptorPoolCreateInfo poolInfo = {}; + 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(MAX_FRAMES_IN_FLIGHT); - 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}; - VkDescriptorSetAllocateInfo allocInfo = {}; + void createDescriptorSets() { + std::vector layouts(MAX_FRAMES_IN_FLIGHT, 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(MAX_FRAMES_IN_FLIGHT); + allocInfo.pSetLayouts = layouts.data(); - if (vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet) != VK_SUCCESS) { - throw std::runtime_error("failed to allocate descriptor set!"); + descriptorSets.resize(MAX_FRAMES_IN_FLIGHT); + if (vkAllocateDescriptorSets(device, &allocInfo, descriptorSets.data()) != 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 < MAX_FRAMES_IN_FLIGHT; 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) { - VkBufferCreateInfo bufferInfo = {}; + 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!"); } VkMemoryRequirements memRequirements; vkGetBufferMemoryRequirements(device, buffer, &memRequirements); - VkMemoryAllocateInfo allocInfo = {}; + 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.replace()) != VK_SUCCESS) { + if (vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) { throw std::runtime_error("failed to allocate buffer memory!"); } @@ -1120,7 +1125,7 @@ class HelloTriangleApplication { } VkCommandBuffer beginSingleTimeCommands() { - VkCommandBufferAllocateInfo allocInfo = {}; + VkCommandBufferAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; allocInfo.commandPool = commandPool; @@ -1129,7 +1134,7 @@ class HelloTriangleApplication { VkCommandBuffer commandBuffer; vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer); - VkCommandBufferBeginInfo beginInfo = {}; + VkCommandBufferBeginInfo beginInfo{}; beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; @@ -1141,7 +1146,7 @@ class HelloTriangleApplication { void endSingleTimeCommands(VkCommandBuffer commandBuffer) { vkEndCommandBuffer(commandBuffer); - VkSubmitInfo submitInfo = {}; + VkSubmitInfo submitInfo{}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &commandBuffer; @@ -1155,7 +1160,7 @@ class HelloTriangleApplication { void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { VkCommandBuffer commandBuffer = beginSingleTimeCommands(); - VkBufferCopy copyRegion = {}; + VkBufferCopy copyRegion{}; copyRegion.size = size; vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); @@ -1176,13 +1181,9 @@ class HelloTriangleApplication { } void createCommandBuffers() { - if (commandBuffers.size() > 0) { - vkFreeCommandBuffers(device, commandPool, commandBuffers.size(), commandBuffers.data()); - } - - commandBuffers.resize(swapChainFramebuffers.size()); + commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); - VkCommandBufferAllocateInfo allocInfo = {}; + VkCommandBufferAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; allocInfo.commandPool = commandPool; allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; @@ -1191,84 +1192,109 @@ class HelloTriangleApplication { 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; + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; - vkBeginCommandBuffer(commandBuffers[i], &beginInfo); + if (vkBeginCommandBuffer(commandBuffer, &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; + VkRenderPassBeginInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = renderPass; + renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex]; + 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}; + 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.pClearValues = clearValues.data(); + renderPassInfo.clearValueCount = static_cast(clearValues.size()); + renderPassInfo.pClearValues = clearValues.data(); - vkCmdBeginRenderPass(commandBuffers[i], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); - vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); - VkBuffer vertexBuffers[] = {vertexBuffer}; - VkDeviceSize offsets[] = {0}; - vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets); + 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; + vkCmdSetViewport(commandBuffer, 0, 1, &viewport); - vkCmdBindIndexBuffer(commandBuffers[i], indexBuffer, 0, VK_INDEX_TYPE_UINT16); + VkRect2D scissor{}; + scissor.offset = {0, 0}; + scissor.extent = swapChainExtent; + vkCmdSetScissor(commandBuffer, 0, 1, &scissor); - vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr); + VkBuffer vertexBuffers[] = {vertexBuffer}; + VkDeviceSize offsets[] = {0}; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); - vkCmdDrawIndexed(commandBuffers[i], indices.size(), 1, 0, 0, 0); + vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT16); - vkCmdEndRenderPass(commandBuffers[i]); + vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets[currentFrame], 0, nullptr); - if (vkEndCommandBuffer(commandBuffers[i]) != VK_SUCCESS) { - throw std::runtime_error("failed to record command buffer!"); - } + vkCmdDrawIndexed(commandBuffer, static_cast(indices.size()), 1, 0, 0, 0); + + vkCmdEndRenderPass(commandBuffer); + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); } } - void createSemaphores() { - VkSemaphoreCreateInfo semaphoreInfo = {}; + 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)); + 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, 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, UINT64_MAX); + uint32_t imageIndex; - VkResult result = vkAcquireNextImageKHR(device, swapChain, std::numeric_limits::max(), imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex); + VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); if (result == VK_ERROR_OUT_OF_DATE_KHR) { recreateSwapChain(); @@ -1277,27 +1303,34 @@ class HelloTriangleApplication { throw std::runtime_error("failed to acquire swap chain image!"); } - VkSubmitInfo submitInfo = {}; + updateUniformBuffer(currentFrame); + + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffers[currentFrame], 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; submitInfo.pWaitDstStageMask = waitStages; submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &commandBuffers[imageIndex]; + submitInfo.pCommandBuffers = &commandBuffers[currentFrame]; - 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!"); } - VkPresentInfoKHR presentInfo = {}; + VkPresentInfoKHR presentInfo{}; presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; presentInfo.waitSemaphoreCount = 1; @@ -1311,35 +1344,33 @@ 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) { - VkShaderModuleCreateInfo createInfo = {}; + 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) { - 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) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { return availableFormat; } } @@ -1347,18 +1378,14 @@ class HelloTriangleApplication { return availableFormats[0]; } - VkPresentModeKHR chooseSwapPresentMode(const std::vector availablePresentModes) { - VkPresentModeKHR bestMode = VK_PRESENT_MODE_FIFO_KHR; - + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { 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; + return VK_PRESENT_MODE_FIFO_KHR; } VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { @@ -1366,12 +1393,15 @@ 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)); + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); return actualExtent; } @@ -1412,7 +1442,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) { @@ -1442,14 +1475,14 @@ class HelloTriangleApplication { int i = 0; for (const auto& queueFamily : queueFamilies) { - if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { indices.graphicsFamily = i; } VkBool32 presentSupport = false; vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); - if (queueFamily.queueCount > 0 && presentSupport) { + if (presentSupport) { indices.presentFamily = i; } @@ -1464,18 +1497,14 @@ 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); + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); } return extensions; @@ -1524,8 +1553,8 @@ class HelloTriangleApplication { 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; + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; return VK_FALSE; } @@ -1536,7 +1565,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/27_shader_depth.frag similarity index 79% rename from code/shader_textures.frag rename to code/27_shader_depth.frag index 1de4e4cd..873f5410 100644 --- a/code/shader_textures.frag +++ b/code/27_shader_depth.frag @@ -1,13 +1,12 @@ -#version 450 -#extension GL_ARB_separate_shader_objects : enable - -layout(binding = 1) uniform sampler2D texSampler; - -layout(location = 0) in vec3 fragColor; -layout(location = 1) in vec2 fragTexCoord; - -layout(location = 0) out vec4 outColor; - -void main() { - outColor = texture(texSampler, fragTexCoord); -} \ No newline at end of file +#version 450 + +layout(binding = 1) uniform sampler2D texSampler; + +layout(location = 0) in vec3 fragColor; +layout(location = 1) in vec2 fragTexCoord; + +layout(location = 0) out vec4 outColor; + +void main() { + outColor = texture(texSampler, fragTexCoord); +} diff --git a/code/shader_depth.vert b/code/27_shader_depth.vert similarity index 79% rename from code/shader_depth.vert rename to code/27_shader_depth.vert index 03aa6eb6..840711c3 100644 --- a/code/shader_depth.vert +++ b/code/27_shader_depth.vert @@ -1,25 +1,20 @@ -#version 450 -#extension GL_ARB_separate_shader_objects : enable - -layout(binding = 0) uniform UniformBufferObject { - mat4 model; - mat4 view; - mat4 proj; -} ubo; - -layout(location = 0) in vec3 inPosition; -layout(location = 1) in vec3 inColor; -layout(location = 2) in vec2 inTexCoord; - -layout(location = 0) out vec3 fragColor; -layout(location = 1) out vec2 fragTexCoord; - -out gl_PerVertex { - vec4 gl_Position; -}; - -void main() { - gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 1.0); - fragColor = inColor; - fragTexCoord = inTexCoord; -} \ No newline at end of file +#version 450 + +layout(binding = 0) uniform UniformBufferObject { + mat4 model; + mat4 view; + mat4 proj; +} ubo; + +layout(location = 0) in vec3 inPosition; +layout(location = 1) in vec3 inColor; +layout(location = 2) in vec2 inTexCoord; + +layout(location = 0) out vec3 fragColor; +layout(location = 1) out vec2 fragTexCoord; + +void main() { + gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 1.0); + fragColor = inColor; + fragTexCoord = inTexCoord; +} diff --git a/code/model_loading.cpp b/code/28_model_loading.cpp similarity index 60% rename from code/model_loading.cpp rename to code/28_model_loading.cpp index 67d625e5..87b66d3d 100644 --- a/code/model_loading.cpp +++ b/code/28_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,25 +15,30 @@ #include #include -#include -#include -#include #include +#include #include +#include #include #include +#include +#include +#include #include +#include #include #include -const int WIDTH = 800; -const int HEIGHT = 600; +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const std::string MODEL_PATH = "models/viking_room.obj"; +const std::string TEXTURE_PATH = "textures/viking_room.png"; -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" + "VK_LAYER_KHRONOS_validation" }; const std::vector deviceExtensions = { @@ -45,86 +51,28 @@ const bool enableValidationLayers = false; const bool enableValidationLayers = true; #endif -VkResult CreateDebugReportCallbackEXT(VkInstance instance, const VkDebugReportCallbackCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugReportCallbackEXT* pCallback) { - auto func = (PFN_vkCreateDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT"); +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); if (func != nullptr) { - return func(instance, pCreateInfo, pAllocator, pCallback); + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); } else { return VK_ERROR_EXTENSION_NOT_PRESENT; } } -void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT callback, const VkAllocationCallbacks* pAllocator) { - auto func = (PFN_vkDestroyDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT"); +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); if (func != nullptr) { - func(instance, callback, pAllocator); + func(instance, debugMessenger, pAllocator); } } -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; + std::optional graphicsFamily; + std::optional presentFamily; bool isComplete() { - return graphicsFamily >= 0 && presentFamily >= 0; + return graphicsFamily.has_value() && presentFamily.has_value(); } }; @@ -140,7 +88,7 @@ struct Vertex { glm::vec2 texCoord; static VkVertexInputBindingDescription getBindingDescription() { - VkVertexInputBindingDescription bindingDescription = {}; + VkVertexInputBindingDescription bindingDescription{}; bindingDescription.binding = 0; bindingDescription.stride = sizeof(Vertex); bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; @@ -149,7 +97,7 @@ struct Vertex { } static std::array getAttributeDescriptions() { - std::array attributeDescriptions = {}; + std::array attributeDescriptions{}; attributeDescriptions[0].binding = 0; attributeDescriptions[0].location = 0; @@ -183,9 +131,9 @@ namespace std { } struct UniformBufferObject { - glm::mat4 model; - glm::mat4 view; - glm::mat4 proj; + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; }; class HelloTriangleApplication { @@ -194,63 +142,66 @@ class HelloTriangleApplication { initWindow(); initVulkan(); mainLoop(); + cleanup(); } private: GLFWwindow* window; - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - VDeleter surface{instance, vkDestroySurfaceKHR}; + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + 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; + uint32_t currentFrame = 0; + + bool framebufferResized = false; void initWindow() { glfwInit(); @@ -258,14 +209,18 @@ 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() { createInstance(); - setupDebugCallback(); + setupDebugMessenger(); createSurface(); pickPhysicalDevice(); createLogicalDevice(); @@ -283,45 +238,104 @@ 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); + } + + 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(); + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroyBuffer(device, uniformBuffers[i], nullptr); + vkFreeMemory(device, uniformBuffersMemory[i], nullptr); + } + + vkDestroyDescriptorPool(device, descriptorPool, nullptr); + + vkDestroySampler(device, textureSampler, nullptr); + vkDestroyImageView(device, textureImageView, nullptr); + + vkDestroyImage(device, textureImage, nullptr); + vkFreeMemory(device, textureImageMemory, nullptr); - HelloTriangleApplication* app = reinterpret_cast(glfwGetWindowUserPointer(window)); - app->recreateSwapChain(); + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, 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) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); } void recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + vkDeviceWaitIdle(device); + cleanupSwapChain(); + createSwapChain(); createImageViews(); - createRenderPass(); - createGraphicsPipeline(); createDepthResources(); createFramebuffers(); - createCommandBuffers(); } void createInstance() { @@ -329,7 +343,7 @@ class HelloTriangleApplication { throw std::runtime_error("validation layers requested, but not available!"); } - VkApplicationInfo appInfo = {}; + VkApplicationInfo appInfo{}; appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; appInfo.pApplicationName = "Hello Triangle"; appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); @@ -337,41 +351,53 @@ class HelloTriangleApplication { appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); appInfo.apiVersion = VK_API_VERSION_1_0; - VkInstanceCreateInfo createInfo = {}; + VkInstanceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); - createInfo.enabledExtensionCount = extensions.size(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; } else { createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; } - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) { + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } - void setupDebugCallback() { + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { 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; + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); - if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, callback.replace()) != VK_SUCCESS) { - throw std::runtime_error("failed to set up debug callback!"); + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); } } 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!"); } } @@ -403,11 +429,11 @@ class HelloTriangleApplication { QueueFamilyIndices indices = findQueueFamilies(physicalDevice); std::vector queueCreateInfos; - std::set uniqueQueueFamilies = {indices.graphicsFamily, indices.presentFamily}; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; float queuePriority = 1.0f; - for (int queueFamily : uniqueQueueFamilies) { - VkDeviceQueueCreateInfo queueCreateInfo = {}; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; queueCreateInfo.queueFamilyIndex = queueFamily; queueCreateInfo.queueCount = 1; @@ -415,32 +441,33 @@ class HelloTriangleApplication { queueCreateInfos.push_back(queueCreateInfo); } - VkPhysicalDeviceFeatures deviceFeatures = {}; + VkPhysicalDeviceFeatures deviceFeatures{}; + deviceFeatures.samplerAnisotropy = VK_TRUE; - VkDeviceCreateInfo createInfo = {}; + 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!"); } - vkGetDeviceQueue(device, indices.graphicsFamily, 0, &graphicsQueue); - vkGetDeviceQueue(device, indices.presentFamily, 0, &presentQueue); + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); } void createSwapChain() { @@ -455,7 +482,7 @@ class HelloTriangleApplication { imageCount = swapChainSupport.capabilities.maxImageCount; } - VkSwapchainCreateInfoKHR createInfo = {}; + VkSwapchainCreateInfoKHR createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; createInfo.surface = surface; @@ -467,7 +494,7 @@ class HelloTriangleApplication { createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; QueueFamilyIndices indices = findQueueFamilies(physicalDevice); - uint32_t queueFamilyIndices[] = {(uint32_t) indices.graphicsFamily, (uint32_t) indices.presentFamily}; + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; if (indices.graphicsFamily != indices.presentFamily) { createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; @@ -482,16 +509,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,15 +522,15 @@ 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); } } void createRenderPass() { - VkAttachmentDescription colorAttachment = {}; + VkAttachmentDescription colorAttachment{}; colorAttachment.format = swapChainImageFormat; colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; @@ -519,7 +540,7 @@ class HelloTriangleApplication { colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - VkAttachmentDescription depthAttachment = {}; + VkAttachmentDescription depthAttachment{}; depthAttachment.format = findDepthFormat(); depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT; depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; @@ -529,52 +550,52 @@ class HelloTriangleApplication { depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - VkAttachmentReference colorAttachmentRef = {}; + VkAttachmentReference colorAttachmentRef{}; colorAttachmentRef.attachment = 0; colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - VkAttachmentReference depthAttachmentRef = {}; + VkAttachmentReference depthAttachmentRef{}; depthAttachmentRef.attachment = 1; depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - VkSubpassDescription subpass = {}; + VkSubpassDescription subpass{}; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass.colorAttachmentCount = 1; subpass.pColorAttachments = &colorAttachmentRef; subpass.pDepthStencilAttachment = &depthAttachmentRef; - VkSubpassDependency dependency = {}; + VkSubpassDependency dependency{}; dependency.srcSubpass = VK_SUBPASS_EXTERNAL; dependency.dstSubpass = 0; - dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_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; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; std::array attachments = {colorAttachment, depthAttachment}; - VkRenderPassCreateInfo renderPassInfo = {}; + 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!"); } } void createDescriptorSetLayout() { - VkDescriptorSetLayoutBinding uboLayoutBinding = {}; + 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 = {}; + VkDescriptorSetLayoutBinding samplerLayoutBinding{}; samplerLayoutBinding.binding = 1; samplerLayoutBinding.descriptorCount = 1; samplerLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; @@ -582,12 +603,12 @@ class HelloTriangleApplication { samplerLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; std::array bindings = {uboLayoutBinding, samplerLayoutBinding}; - VkDescriptorSetLayoutCreateInfo layoutInfo = {}; + 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,18 +617,16 @@ 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 = {}; + 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 = {}; + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; fragShaderStageInfo.module = fragShaderModule; @@ -615,42 +634,28 @@ class HelloTriangleApplication { VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; - VkPipelineVertexInputStateCreateInfo vertexInputInfo = {}; + 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 = attributeDescriptions.size(); + vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()); vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); - VkPipelineInputAssemblyStateCreateInfo inputAssembly = {}; + 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 = {}; + 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 = {}; + VkPipelineRasterizationStateCreateInfo rasterizer{}; rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; rasterizer.depthClampEnable = VK_FALSE; rasterizer.rasterizerDiscardEnable = VK_FALSE; @@ -660,12 +665,12 @@ class HelloTriangleApplication { rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; rasterizer.depthBiasEnable = VK_FALSE; - VkPipelineMultisampleStateCreateInfo multisampling = {}; + 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 = {}; + VkPipelineDepthStencilStateCreateInfo depthStencil{}; depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; depthStencil.depthTestEnable = VK_TRUE; depthStencil.depthWriteEnable = VK_TRUE; @@ -673,11 +678,11 @@ class HelloTriangleApplication { depthStencil.depthBoundsTestEnable = VK_FALSE; depthStencil.stencilTestEnable = VK_FALSE; - VkPipelineColorBlendAttachmentState colorBlendAttachment = {}; + 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 = {}; + VkPipelineColorBlendStateCreateInfo colorBlending{}; colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; colorBlending.logicOpEnable = VK_FALSE; colorBlending.logicOp = VK_LOGIC_OP_COPY; @@ -688,17 +693,25 @@ class HelloTriangleApplication { colorBlending.blendConstants[2] = 0.0f; colorBlending.blendConstants[3] = 0.0f; - VkDescriptorSetLayout setLayouts[] = {descriptorSetLayout}; - VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; + std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + 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!"); } - VkGraphicsPipelineCreateInfo pipelineInfo = {}; + VkGraphicsPipelineCreateInfo pipelineInfo{}; pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; pipelineInfo.stageCount = 2; pipelineInfo.pStages = shaderStages; @@ -709,18 +722,22 @@ class HelloTriangleApplication { pipelineInfo.pMultisampleState = &multisampling; pipelineInfo.pDepthStencilState = &depthStencil; pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = &dynamicState; 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) { + 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 = { @@ -728,16 +745,16 @@ class HelloTriangleApplication { depthImageView }; - VkFramebufferCreateInfo framebufferInfo = {}; + 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!"); } } @@ -746,11 +763,12 @@ class HelloTriangleApplication { void createCommandPool() { QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); - VkCommandPoolCreateInfo poolInfo = {}; + VkCommandPoolCreateInfo poolInfo{}; poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; - poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); - 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,9 +777,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); - - transitionImageLayout(depthImage, depthFormat, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); + depthImageView = createImageView(depthImage, depthFormat, VK_IMAGE_ASPECT_DEPTH_BIT); } VkFormat findSupportedFormat(const std::vector& candidates, VkImageTiling tiling, VkFormatFeatureFlags features) { @@ -781,7 +797,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,50 +816,36 @@ 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); + createImage(texWidth, texHeight, VK_FORMAT_R8G8B8A8_SRGB, 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_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); + transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); - 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_SRGB, VK_IMAGE_ASPECT_COLOR_BIT); } void createTextureSampler() { - VkSamplerCreateInfo samplerInfo = {}; + VkPhysicalDeviceProperties properties{}; + vkGetPhysicalDeviceProperties(physicalDevice, &properties); + + VkSamplerCreateInfo samplerInfo{}; samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; samplerInfo.magFilter = VK_FILTER_LINEAR; samplerInfo.minFilter = VK_FILTER_LINEAR; @@ -851,20 +853,20 @@ class HelloTriangleApplication { samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; samplerInfo.anisotropyEnable = VK_TRUE; - samplerInfo.maxAnisotropy = 16; + samplerInfo.maxAnisotropy = properties.limits.maxSamplerAnisotropy; 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; - 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) { - VkImageViewCreateInfo viewInfo = {}; + VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags) { + VkImageViewCreateInfo viewInfo{}; viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; viewInfo.image = image; viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; @@ -875,13 +877,16 @@ 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) { - VkImageCreateInfo imageInfo = {}; + 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; imageInfo.extent.width = width; @@ -891,24 +896,24 @@ 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!"); } VkMemoryRequirements memRequirements; vkGetImageMemoryRequirements(device, image, &memRequirements); - VkMemoryAllocateInfo allocInfo = {}; + 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.replace()) != VK_SUCCESS) { + if (vkAllocateMemory(device, &allocInfo, nullptr, &imageMemory) != VK_SUCCESS) { throw std::runtime_error("failed to allocate image memory!"); } @@ -918,48 +923,41 @@ class HelloTriangleApplication { void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout) { VkCommandBuffer commandBuffer = beginSingleTimeCommands(); - VkImageMemoryBarrier barrier = {}; + 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.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; barrier.subresourceRange.baseMipLevel = 0; barrier.subresourceRange.levelCount = 1; 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; - } 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_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, @@ -969,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); } @@ -1001,17 +994,17 @@ class HelloTriangleApplication { tinyobj::attrib_t attrib; std::vector shapes; std::vector materials; - std::string err; + std::string warn, err; - if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &err, MODEL_PATH.c_str())) { - throw std::runtime_error(err); + if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, MODEL_PATH.c_str())) { + throw std::runtime_error(warn + err); } - std::unordered_map uniqueVertices = {}; + std::unordered_map uniqueVertices{}; for (const auto& shape : shapes) { for (const auto& index : shape.mesh.indices) { - Vertex vertex = {}; + Vertex vertex{}; vertex.pos = { attrib.vertices[3 * index.vertex_index + 0], @@ -1027,7 +1020,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 +1032,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 +1044,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,96 +1064,106 @@ 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(MAX_FRAMES_IN_FLIGHT); + uniformBuffersMemory.resize(MAX_FRAMES_IN_FLIGHT); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; 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 = {}; + std::array poolSizes{}; poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - poolSizes[0].descriptorCount = 1; + poolSizes[0].descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT); poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - poolSizes[1].descriptorCount = 1; + poolSizes[1].descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT); - VkDescriptorPoolCreateInfo poolInfo = {}; + 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(MAX_FRAMES_IN_FLIGHT); - 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}; - VkDescriptorSetAllocateInfo allocInfo = {}; + void createDescriptorSets() { + std::vector layouts(MAX_FRAMES_IN_FLIGHT, 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(MAX_FRAMES_IN_FLIGHT); + allocInfo.pSetLayouts = layouts.data(); - if (vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet) != VK_SUCCESS) { - throw std::runtime_error("failed to allocate descriptor set!"); + descriptorSets.resize(MAX_FRAMES_IN_FLIGHT); + if (vkAllocateDescriptorSets(device, &allocInfo, descriptorSets.data()) != 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 < MAX_FRAMES_IN_FLIGHT; 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) { - VkBufferCreateInfo bufferInfo = {}; + 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!"); } VkMemoryRequirements memRequirements; vkGetBufferMemoryRequirements(device, buffer, &memRequirements); - VkMemoryAllocateInfo allocInfo = {}; + 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.replace()) != VK_SUCCESS) { + if (vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) { throw std::runtime_error("failed to allocate buffer memory!"); } @@ -1165,7 +1171,7 @@ class HelloTriangleApplication { } VkCommandBuffer beginSingleTimeCommands() { - VkCommandBufferAllocateInfo allocInfo = {}; + VkCommandBufferAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; allocInfo.commandPool = commandPool; @@ -1174,7 +1180,7 @@ class HelloTriangleApplication { VkCommandBuffer commandBuffer; vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer); - VkCommandBufferBeginInfo beginInfo = {}; + VkCommandBufferBeginInfo beginInfo{}; beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; @@ -1186,7 +1192,7 @@ class HelloTriangleApplication { void endSingleTimeCommands(VkCommandBuffer commandBuffer) { vkEndCommandBuffer(commandBuffer); - VkSubmitInfo submitInfo = {}; + VkSubmitInfo submitInfo{}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &commandBuffer; @@ -1200,7 +1206,7 @@ class HelloTriangleApplication { void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { VkCommandBuffer commandBuffer = beginSingleTimeCommands(); - VkBufferCopy copyRegion = {}; + VkBufferCopy copyRegion{}; copyRegion.size = size; vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); @@ -1221,13 +1227,9 @@ class HelloTriangleApplication { } void createCommandBuffers() { - if (commandBuffers.size() > 0) { - vkFreeCommandBuffers(device, commandPool, commandBuffers.size(), commandBuffers.data()); - } - - commandBuffers.resize(swapChainFramebuffers.size()); + commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); - VkCommandBufferAllocateInfo allocInfo = {}; + VkCommandBufferAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; allocInfo.commandPool = commandPool; allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; @@ -1236,84 +1238,109 @@ class HelloTriangleApplication { 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; + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; - vkBeginCommandBuffer(commandBuffers[i], &beginInfo); + if (vkBeginCommandBuffer(commandBuffer, &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; + VkRenderPassBeginInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = renderPass; + renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex]; + 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}; + 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.pClearValues = clearValues.data(); + renderPassInfo.clearValueCount = static_cast(clearValues.size()); + renderPassInfo.pClearValues = clearValues.data(); - vkCmdBeginRenderPass(commandBuffers[i], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); - vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); - VkBuffer vertexBuffers[] = {vertexBuffer}; - VkDeviceSize offsets[] = {0}; - vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets); + 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; + vkCmdSetViewport(commandBuffer, 0, 1, &viewport); - vkCmdBindIndexBuffer(commandBuffers[i], indexBuffer, 0, VK_INDEX_TYPE_UINT32); + VkRect2D scissor{}; + scissor.offset = {0, 0}; + scissor.extent = swapChainExtent; + vkCmdSetScissor(commandBuffer, 0, 1, &scissor); - vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr); + VkBuffer vertexBuffers[] = {vertexBuffer}; + VkDeviceSize offsets[] = {0}; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); - vkCmdDrawIndexed(commandBuffers[i], indices.size(), 1, 0, 0, 0); + vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT32); - vkCmdEndRenderPass(commandBuffers[i]); + vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets[currentFrame], 0, nullptr); - if (vkEndCommandBuffer(commandBuffers[i]) != VK_SUCCESS) { - throw std::runtime_error("failed to record command buffer!"); - } + vkCmdDrawIndexed(commandBuffer, static_cast(indices.size()), 1, 0, 0, 0); + + vkCmdEndRenderPass(commandBuffer); + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record command buffer!"); } } - void createSemaphores() { - VkSemaphoreCreateInfo semaphoreInfo = {}; + 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)); + 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, 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, UINT64_MAX); + uint32_t imageIndex; - VkResult result = vkAcquireNextImageKHR(device, swapChain, std::numeric_limits::max(), imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex); + VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); if (result == VK_ERROR_OUT_OF_DATE_KHR) { recreateSwapChain(); @@ -1322,27 +1349,34 @@ class HelloTriangleApplication { throw std::runtime_error("failed to acquire swap chain image!"); } - VkSubmitInfo submitInfo = {}; + updateUniformBuffer(currentFrame); + + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffers[currentFrame], 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; submitInfo.pWaitDstStageMask = waitStages; submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &commandBuffers[imageIndex]; + submitInfo.pCommandBuffers = &commandBuffers[currentFrame]; - 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!"); } - VkPresentInfoKHR presentInfo = {}; + VkPresentInfoKHR presentInfo{}; presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; presentInfo.waitSemaphoreCount = 1; @@ -1356,35 +1390,33 @@ 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) { - VkShaderModuleCreateInfo createInfo = {}; + 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) { - 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) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { return availableFormat; } } @@ -1392,18 +1424,14 @@ class HelloTriangleApplication { return availableFormats[0]; } - VkPresentModeKHR chooseSwapPresentMode(const std::vector availablePresentModes) { - VkPresentModeKHR bestMode = VK_PRESENT_MODE_FIFO_KHR; - + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { 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; + return VK_PRESENT_MODE_FIFO_KHR; } VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { @@ -1411,12 +1439,15 @@ 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)); + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); return actualExtent; } @@ -1457,7 +1488,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) { @@ -1487,14 +1521,14 @@ class HelloTriangleApplication { int i = 0; for (const auto& queueFamily : queueFamilies) { - if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { indices.graphicsFamily = i; } VkBool32 presentSupport = false; vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); - if (queueFamily.queueCount > 0 && presentSupport) { + if (presentSupport) { indices.presentFamily = i; } @@ -1509,18 +1543,14 @@ 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); + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); } return extensions; @@ -1569,8 +1599,8 @@ class HelloTriangleApplication { 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; + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; return VK_FALSE; } @@ -1581,7 +1611,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/29_mipmapping.cpp b/code/29_mipmapping.cpp new file mode 100644 index 00000000..8dd78180 --- /dev/null +++ b/code/29_mipmapping.cpp @@ -0,0 +1,1714 @@ +#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 +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const std::string MODEL_PATH = "models/viking_room.obj"; +const std::string TEXTURE_PATH = "textures/viking_room.png"; + +const int MAX_FRAMES_IN_FLIGHT = 2; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +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 { + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; +}; + +class HelloTriangleApplication { +public: + void run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + 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; + uint32_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(); + setupDebugMessenger(); + 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); + } + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + } + + void cleanup() { + cleanupSwapChain(); + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroyBuffer(device, uniformBuffers[i], nullptr); + vkFreeMemory(device, uniformBuffersMemory[i], nullptr); + } + + vkDestroyDescriptorPool(device, descriptorPool, nullptr); + + vkDestroySampler(device, textureSampler, nullptr); + vkDestroyImageView(device, textureImageView, nullptr); + + vkDestroyImage(device, textureImage, nullptr); + vkFreeMemory(device, textureImageMemory, nullptr); + + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, 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) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + vkDeviceWaitIdle(device); + + cleanupSwapChain(); + + createSwapChain(); + createImageViews(); + createDepthResources(); + createFramebuffers(); + } + + 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(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; + } else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + 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.value(), indices.presentFamily.value()}; + + float queuePriority = 1.0f; + for (uint32_t 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.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 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[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + 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 | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + dependency.srcAccessMask = 0; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_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; + + VkPipelineViewportStateCreateInfo viewportState{}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.scissorCount = 1; + + 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; + + std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + 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.pDynamicState = &dynamicState; + 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.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); + + 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); + } + + 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_SRGB, 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_SRGB, 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_SRGB, 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_SRGB, VK_IMAGE_ASPECT_COLOR_BIT, mipLevels); + } + + void createTextureSampler() { + VkPhysicalDeviceProperties properties{}; + vkGetPhysicalDeviceProperties(physicalDevice, &properties); + + 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 = properties.limits.maxSamplerAnisotropy; + 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.0f; + samplerInfo.maxLod = static_cast(mipLevels); + samplerInfo.mipLodBias = 0.0f; + + 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; + 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 { + 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 warn, err; + + if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, MODEL_PATH.c_str())) { + throw std::runtime_error(warn + err); + } + + std::unordered_map uniqueVertices{}; + + for (const auto& shape : shapes) { + for (const auto& index : shape.mesh.indices) { + Vertex vertex{}; + + vertex.pos = { + attrib.vertices[3 * index.vertex_index + 0], + attrib.vertices[3 * index.vertex_index + 1], + attrib.vertices[3 * index.vertex_index + 2] + }; + + vertex.texCoord = { + attrib.texcoords[2 * index.texcoord_index + 0], + 1.0f - attrib.texcoords[2 * index.texcoord_index + 1] + }; + + vertex.color = {1.0f, 1.0f, 1.0f}; + + if (uniqueVertices.count(vertex) == 0) { + uniqueVertices[vertex] = static_cast(vertices.size()); + vertices.push_back(vertex); + } + + indices.push_back(uniqueVertices[vertex]); + } + } + } + + void 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(MAX_FRAMES_IN_FLIGHT); + uniformBuffersMemory.resize(MAX_FRAMES_IN_FLIGHT); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; 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(MAX_FRAMES_IN_FLIGHT); + poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + poolSizes[1].descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT); + + 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(MAX_FRAMES_IN_FLIGHT); + + if (vkCreateDescriptorPool(device, &poolInfo, nullptr, &descriptorPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create descriptor pool!"); + } + } + + void createDescriptorSets() { + std::vector layouts(MAX_FRAMES_IN_FLIGHT, descriptorSetLayout); + VkDescriptorSetAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = descriptorPool; + allocInfo.descriptorSetCount = static_cast(MAX_FRAMES_IN_FLIGHT); + allocInfo.pSetLayouts = layouts.data(); + + descriptorSets.resize(MAX_FRAMES_IN_FLIGHT); + if (vkAllocateDescriptorSets(device, &allocInfo, descriptorSets.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate descriptor sets!"); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; 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(MAX_FRAMES_IN_FLIGHT); + + 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!"); + } + } + + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + + if (vkBeginCommandBuffer(commandBuffer, &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[imageIndex]; + 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(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); + + 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; + vkCmdSetViewport(commandBuffer, 0, 1, &viewport); + + VkRect2D scissor{}; + scissor.offset = {0, 0}; + scissor.extent = swapChainExtent; + vkCmdSetScissor(commandBuffer, 0, 1, &scissor); + + VkBuffer vertexBuffers[] = {vertexBuffer}; + VkDeviceSize offsets[] = {0}; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); + + vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT32); + + vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets[currentFrame], 0, nullptr); + + vkCmdDrawIndexed(commandBuffer, static_cast(indices.size()), 1, 0, 0, 0); + + vkCmdEndRenderPass(commandBuffer); + + if (vkEndCommandBuffer(commandBuffer) != 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, UINT64_MAX); + + uint32_t imageIndex; + VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_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(currentFrame); + + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffers[currentFrame], 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[currentFrame]; + + VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[currentFrame]}; + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = signalSemaphores; + + 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) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; + } + + 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::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.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.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (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_UTILS_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(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << 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/30_multisampling.cpp b/code/30_multisampling.cpp new file mode 100644 index 00000000..82c7fdef --- /dev/null +++ b/code/30_multisampling.cpp @@ -0,0 +1,1764 @@ +#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 +#include +#include +#include + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const std::string MODEL_PATH = "models/viking_room.obj"; +const std::string TEXTURE_PATH = "textures/viking_room.png"; + +const int MAX_FRAMES_IN_FLIGHT = 2; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +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 { + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; +}; + +class HelloTriangleApplication { +public: + void run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + GLFWwindow* window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debugMessenger; + 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; + + VkImage colorImage; + VkDeviceMemory colorImageMemory; + VkImageView colorImageView; + + 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; + uint32_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(); + setupDebugMessenger(); + 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() { + vkDestroyImageView(device, depthImageView, nullptr); + vkDestroyImage(device, depthImage, nullptr); + vkFreeMemory(device, depthImageMemory, nullptr); + + vkDestroyImageView(device, colorImageView, nullptr); + vkDestroyImage(device, colorImage, nullptr); + vkFreeMemory(device, colorImageMemory, nullptr); + + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } + + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); + } + + void cleanup() { + cleanupSwapChain(); + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroyBuffer(device, uniformBuffers[i], nullptr); + vkFreeMemory(device, uniformBuffersMemory[i], nullptr); + } + + vkDestroyDescriptorPool(device, descriptorPool, nullptr); + + vkDestroySampler(device, textureSampler, nullptr); + vkDestroyImageView(device, textureImageView, nullptr); + + vkDestroyImage(device, textureImage, nullptr); + vkFreeMemory(device, textureImageMemory, nullptr); + + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, 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) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } + + void recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + vkDeviceWaitIdle(device); + + cleanupSwapChain(); + + createSwapChain(); + createImageViews(); + createColorResources(); + createDepthResources(); + createFramebuffers(); + } + + 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(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; + } else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } + } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + 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.value(), indices.presentFamily.value()}; + + float queuePriority = 1.0f; + for (uint32_t 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.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 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[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + 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 | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + dependency.srcAccessMask = 0; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_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; + + VkPipelineViewportStateCreateInfo viewportState{}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.scissorCount = 1; + + 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; + + std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + 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.pDynamicState = &dynamicState; + 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 = { + colorImageView, + depthImageView, + 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.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); + + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create graphics command pool!"); + } + } + + void createColorResources() { + VkFormat colorFormat = swapChainImageFormat; + + 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, colorImage, colorImageMemory); + colorImageView = createImageView(colorImage, colorFormat, VK_IMAGE_ASPECT_COLOR_BIT, 1); + } + + void createDepthResources() { + VkFormat depthFormat = findDepthFormat(); + + 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, depthImage, depthImageMemory); + depthImageView = createImageView(depthImage, depthFormat, VK_IMAGE_ASPECT_DEPTH_BIT, 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_SRGB, 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_SRGB, 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_SRGB, 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 = 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_SRGB, VK_IMAGE_ASPECT_COLOR_BIT, mipLevels); + } + + void createTextureSampler() { + VkPhysicalDeviceProperties properties{}; + vkGetPhysicalDeviceProperties(physicalDevice, &properties); + + 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 = properties.limits.maxSamplerAnisotropy; + 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.0f; + samplerInfo.maxLod = static_cast(mipLevels); + samplerInfo.mipLodBias = 0.0f; + + 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; + 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 { + 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 warn, err; + + if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, MODEL_PATH.c_str())) { + throw std::runtime_error(warn + err); + } + + std::unordered_map uniqueVertices{}; + + for (const auto& shape : shapes) { + for (const auto& index : shape.mesh.indices) { + Vertex vertex{}; + + vertex.pos = { + attrib.vertices[3 * index.vertex_index + 0], + attrib.vertices[3 * index.vertex_index + 1], + attrib.vertices[3 * index.vertex_index + 2] + }; + + vertex.texCoord = { + attrib.texcoords[2 * index.texcoord_index + 0], + 1.0f - attrib.texcoords[2 * index.texcoord_index + 1] + }; + + vertex.color = {1.0f, 1.0f, 1.0f}; + + if (uniqueVertices.count(vertex) == 0) { + uniqueVertices[vertex] = static_cast(vertices.size()); + vertices.push_back(vertex); + } + + indices.push_back(uniqueVertices[vertex]); + } + } + } + + void 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(MAX_FRAMES_IN_FLIGHT); + uniformBuffersMemory.resize(MAX_FRAMES_IN_FLIGHT); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; 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(MAX_FRAMES_IN_FLIGHT); + poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + poolSizes[1].descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT); + + 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(MAX_FRAMES_IN_FLIGHT); + + if (vkCreateDescriptorPool(device, &poolInfo, nullptr, &descriptorPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create descriptor pool!"); + } + } + + void createDescriptorSets() { + std::vector layouts(MAX_FRAMES_IN_FLIGHT, descriptorSetLayout); + VkDescriptorSetAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = descriptorPool; + allocInfo.descriptorSetCount = static_cast(MAX_FRAMES_IN_FLIGHT); + allocInfo.pSetLayouts = layouts.data(); + + descriptorSets.resize(MAX_FRAMES_IN_FLIGHT); + if (vkAllocateDescriptorSets(device, &allocInfo, descriptorSets.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate descriptor sets!"); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; 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(MAX_FRAMES_IN_FLIGHT); + + 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!"); + } + } + + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + + if (vkBeginCommandBuffer(commandBuffer, &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[imageIndex]; + 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(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); + + 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; + vkCmdSetViewport(commandBuffer, 0, 1, &viewport); + + VkRect2D scissor{}; + scissor.offset = {0, 0}; + scissor.extent = swapChainExtent; + vkCmdSetScissor(commandBuffer, 0, 1, &scissor); + + VkBuffer vertexBuffers[] = {vertexBuffer}; + VkDeviceSize offsets[] = {0}; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); + + vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT32); + + vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets[currentFrame], 0, nullptr); + + vkCmdDrawIndexed(commandBuffer, static_cast(indices.size()), 1, 0, 0, 0); + + vkCmdEndRenderPass(commandBuffer); + + if (vkEndCommandBuffer(commandBuffer) != 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, UINT64_MAX); + + uint32_t imageIndex; + VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_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(currentFrame); + + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffers[currentFrame], 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[currentFrame]; + + VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[currentFrame]}; + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = signalSemaphores; + + 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) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; + } + + VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; + } + + 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::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.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.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (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_UTILS_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(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + std::cerr << "validation layer: " << pCallbackData->pMessage << 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/CMakeLists.txt b/code/CMakeLists.txt new file mode 100644 index 00000000..764ea838 --- /dev/null +++ b/code/CMakeLists.txt @@ -0,0 +1,197 @@ +cmake_minimum_required (VERSION 3.8) + +project (VulkanTutorial) + +########################################################################### +# glfw3 + +find_package (glfw3 REQUIRED) + +########################################################################### +# glm + +find_package (glm REQUIRED) + +########################################################################### +# Vulkan + +find_package (Vulkan REQUIRED) +add_executable (glslang::validator IMPORTED) +find_program (GLSLANG_VALIDATOR "glslangValidator" HINTS $ENV{VULKAN_SDK}/bin REQUIRED) +set_property (TARGET glslang::validator PROPERTY IMPORTED_LOCATION "${GLSLANG_VALIDATOR}") + +########################################################################### +# stb + +if(NOT IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/ext/stb/.github") + message(FATAL_ERROR "The stb submodule directory is missing! " + "You probably did not clone the project with --recursive. It is possible to recover " + "by running \"git submodule update --init --recursive\"") +endif() + +set (STB_INCLUDEDIR ${CMAKE_CURRENT_SOURCE_DIR}/ext/stb) + +########################################################################### +# tinyobjloader + +if(NOT IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/ext/tinyobjloader/.github") + message(FATAL_ERROR "The tinyobjloader submodule directory is missing! " + "You probably did not clone the project with --recursive. It is possible to recover " + "by running \"git submodule update --init --recursive\"") +endif() + +add_subdirectory (${CMAKE_CURRENT_SOURCE_DIR}/ext/tinyobjloader) +add_library (tinyobjloader::tinyobjloader ALIAS tinyobjloader) + +function (add_shaders_target TARGET) + cmake_parse_arguments ("SHADER" "" "CHAPTER_NAME" "SOURCES" ${ARGN}) + set (SHADERS_DIR ${SHADER_CHAPTER_NAME}/shaders) + add_custom_command ( + OUTPUT ${SHADERS_DIR} + COMMAND ${CMAKE_COMMAND} -E make_directory ${SHADERS_DIR} + ) + add_custom_command ( + OUTPUT ${SHADERS_DIR}/frag.spv ${SHADERS_DIR}/vert.spv + COMMAND glslang::validator + ARGS --target-env vulkan1.0 ${SHADER_SOURCES} --quiet + WORKING_DIRECTORY ${SHADERS_DIR} + DEPENDS ${SHADERS_DIR} ${SHADER_SOURCES} + COMMENT "Compiling Shaders" + VERBATIM + ) + add_custom_target (${TARGET} DEPENDS ${SHADERS_DIR}/frag.spv ${SHADERS_DIR}/vert.spv) +endfunction () + +function (add_chapter CHAPTER_NAME) + cmake_parse_arguments (CHAPTER "" "SHADER" "LIBS;TEXTURES;MODELS" ${ARGN}) + + add_executable (${CHAPTER_NAME} ${CHAPTER_NAME}.cpp) + set_target_properties (${CHAPTER_NAME} PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CHAPTER_NAME}) + set_target_properties (${CHAPTER_NAME} PROPERTIES CXX_STANDARD 17) + target_link_libraries (${CHAPTER_NAME} Vulkan::Vulkan glfw) + target_include_directories (${CHAPTER_NAME} PRIVATE ${STB_INCLUDEDIR}) + + if (DEFINED CHAPTER_SHADER) + set (CHAPTER_SHADER_TARGET ${CHAPTER_NAME}_shader) + file (GLOB SHADER_SOURCES ${CHAPTER_SHADER}.frag ${CHAPTER_SHADER}.vert) + add_shaders_target (${CHAPTER_SHADER_TARGET} CHAPTER_NAME ${CHAPTER_NAME} SOURCES ${SHADER_SOURCES}) + add_dependencies (${CHAPTER_NAME} ${CHAPTER_SHADER_TARGET}) + endif () + if (DEFINED CHAPTER_LIBS) + target_link_libraries (${CHAPTER_NAME} ${CHAPTER_LIBS}) + endif () + if (DEFINED CHAPTER_MODELS) + file (COPY ${CHAPTER_MODELS} DESTINATION ${CMAKE_BINARY_DIR}/${CHAPTER_NAME}/models) + endif () + if (DEFINED CHAPTER_TEXTURES) + file (COPY ${CHAPTER_TEXTURES} DESTINATION ${CMAKE_BINARY_DIR}/${CHAPTER_NAME}/textures) + endif () +endfunction () + +add_chapter (00_base_code) + +add_chapter (01_instance_creation) + +add_chapter (02_validation_layers) + +add_chapter (03_physical_device_selection) + +add_chapter (04_logical_device) + +add_chapter (05_window_surface) + +add_chapter (06_swap_chain_creation) + +add_chapter (07_image_views) + +add_chapter (08_graphics_pipeline) + +add_chapter (09_shader_modules + SHADER 09_shader_base) + +add_chapter (10_fixed_functions + SHADER 09_shader_base) + +add_chapter (11_render_passes + SHADER 09_shader_base) + +add_chapter (12_graphics_pipeline_complete + SHADER 09_shader_base) + +add_chapter (13_framebuffers + SHADER 09_shader_base) + +add_chapter (14_command_buffers + SHADER 09_shader_base) + +add_chapter (15_hello_triangle + SHADER 09_shader_base) + +add_chapter (16_frames_in_flight + SHADER 09_shader_base) + +add_chapter (17_swap_chain_recreation + SHADER 09_shader_base) + +add_chapter (18_vertex_input + SHADER 18_shader_vertexbuffer + LIBS glm::glm) + +add_chapter (19_vertex_buffer + SHADER 18_shader_vertexbuffer + LIBS glm::glm) + +add_chapter (20_staging_buffer + SHADER 18_shader_vertexbuffer + LIBS glm::glm) + +add_chapter (21_index_buffer + SHADER 18_shader_vertexbuffer + LIBS glm::glm) + +add_chapter (22_descriptor_layout + SHADER 22_shader_ubo + LIBS glm::glm) + +add_chapter (23_descriptor_sets + SHADER 22_shader_ubo + LIBS glm::glm) + +add_chapter (24_texture_image + SHADER 22_shader_ubo + TEXTURES ../images/texture.jpg + LIBS glm::glm) + +add_chapter (25_sampler + SHADER 22_shader_ubo + TEXTURES ../images/texture.jpg + LIBS glm::glm) + +add_chapter (26_texture_mapping + SHADER 26_shader_textures + TEXTURES ../images/texture.jpg + LIBS glm::glm) + +add_chapter (27_depth_buffering + SHADER 27_shader_depth + TEXTURES ../images/texture.jpg + LIBS glm::glm) + +add_chapter (28_model_loading + SHADER 27_shader_depth + MODELS ../resources/viking_room.obj + TEXTURES ../resources/viking_room.png + LIBS glm::glm tinyobjloader::tinyobjloader) + +add_chapter (29_mipmapping + SHADER 27_shader_depth + MODELS ../resources/viking_room.obj + TEXTURES ../resources/viking_room.png + LIBS glm::glm tinyobjloader::tinyobjloader) + +add_chapter (30_multisampling + SHADER 27_shader_depth + MODELS ../resources/viking_room.obj + TEXTURES ../resources/viking_room.png + LIBS glm::glm tinyobjloader::tinyobjloader) 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/code/ext/stb b/code/ext/stb new file mode 160000 index 00000000..af1a5bc3 --- /dev/null +++ b/code/ext/stb @@ -0,0 +1 @@ +Subproject commit af1a5bc352164740c1cc1354942b1c6b72eacb8a diff --git a/code/ext/tinyobjloader b/code/ext/tinyobjloader new file mode 160000 index 00000000..a1e8bad3 --- /dev/null +++ b/code/ext/tinyobjloader @@ -0,0 +1 @@ +Subproject commit a1e8bad32e1ccd26a7936c5354ecf856aec2cf59 diff --git a/code/validation_layers.cpp b/code/validation_layers.cpp deleted file mode 100644 index 0dd637ec..00000000 --- a/code/validation_layers.cpp +++ /dev/null @@ -1,242 +0,0 @@ -#define GLFW_INCLUDE_VULKAN -#include - -#include -#include -#include -#include -#include - -const int WIDTH = 800; -const int HEIGHT = 600; - -const std::vector validationLayers = { - "VK_LAYER_LUNARG_standard_validation" -}; - -#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); - } -} - -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; - - VDeleter instance{vkDestroyInstance}; - VDeleter callback{instance, DestroyDebugReportCallbackEXT}; - - void initWindow() { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); - - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - } - - void initVulkan() { - createInstance(); - setupDebugCallback(); - } - - void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - } - - glfwDestroyWindow(window); - - glfwTerminate(); - } - - 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 = extensions.size(); - createInfo.ppEnabledExtensionNames = extensions.data(); - - if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); - createInfo.ppEnabledLayerNames = validationLayers.data(); - } else { - createInfo.enabledLayerCount = 0; - } - - if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != 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.replace()) != VK_SUCCESS) { - throw std::runtime_error("failed to set up debug callback!"); - } - } - - std::vector getRequiredExtensions() { - std::vector extensions; - - unsigned int glfwExtensionCount = 0; - const char** glfwExtensions; - glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - for (unsigned int i = 0; i < glfwExtensionCount; i++) { - extensions.push_back(glfwExtensions[i]); - } - - 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 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::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..600a2b38 100644 --- a/config.json +++ b/config.json @@ -7,7 +7,7 @@ "clean_urls": true }, "html": { - "theme": "daux-blue", + "theme": "vulkan-vulkan", "auto_landing": false, "breadcrumbs": true, "breadcrumb_separator": "Chevrons", @@ -16,17 +16,23 @@ "float": false, "auto_toc": true, - "google_analytics": "UA-60335079-1", "links": { "GitHub Repository": "https://github.com/Overv/VulkanTutorial", - "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", + "Support the website": "https://www.paypal.me/AOvervoorde", + "Vulkan Specification": "https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/", "LunarG Vulkan SDK": "https://lunarg.com/vulkan-sdk/", - "Vulkan Hardware Database": "http://vulkan.gpuinfo.org/" + "Vulkan Guide": "https://github.com/KhronosGroup/Vulkan-Guide", + "Vulkan Hardware Database": "https://vulkan.gpuinfo.org/", + "Tutorial for Rust": "https://github.com/bwasty/vulkan-tutorial-rs", + "Tutorial for Java": "https://github.com/Naitsirc98/Vulkan-Tutorial-Java", + "Visual Studio 2019 samples": "https://github.com/jjYBdx4IL/VulkanTutorial-VisualStudioProjectFiles" } }, "ignore": { - "files": ["README.md"] + "files": ["README.md", "build_ebook.py","daux.patch",".gitignore"], + "folders": ["ebook"] }, + "languages": {"en": "English", "fr": "Français"}, + "language": "en", "processor": "VulkanLinkProcessor" } diff --git a/daux.patch b/daux.patch deleted file mode 100644 index 7571a6d3..00000000 --- a/daux.patch +++ /dev/null @@ -1,1412 +0,0 @@ -From 40008c88788c97fd4b81e1117a5f029d9a457acd Mon Sep 17 00:00:00 2001 -From: Alexander Overvoorde -Date: Mon, 17 Apr 2017 20:02:04 +0200 -Subject: [PATCH] Adjust theme for Vulkan tutorial - ---- - daux/VulkanLinkProcessor.php | 69 ++++ - templates/content.php | 26 +- - templates/layout/00_layout.php | 2 +- - templates/layout/05_page.php | 17 +- - 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/js/daux.js | 12 + - themes/daux/js/highlight.pack.js | 576 +++++++++++++++++++++++++++++++- - themes/daux/less/components.less | 69 +++- - themes/daux/less/highlight.less | 241 +++++++------ - 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(-) - create mode 100644 daux/VulkanLinkProcessor.php - -diff --git a/daux/VulkanLinkProcessor.php b/daux/VulkanLinkProcessor.php -new file mode 100644 -index 0000000..4e4d456 ---- /dev/null -+++ b/daux/VulkanLinkProcessor.php -@@ -0,0 +1,69 @@ -+getCursor(); -+ -+ // Ensure that 'v' is the first character of this word -+ $previousChar = $cursor->peek(-1); -+ if ($previousChar !== null && $previousChar !== "\n" && $previousChar !== ' ' && $previousChar !== '(') { -+ return false; -+ } -+ -+ $functionName = $cursor->match('/^[vV]k[A-Z][A-Za-z0-9_]+/'); -+ -+ if (empty($functionName)) { -+ return false; -+ } -+ -+ $inlineContext->getContainer()->appendChild(new Code($functionName)); -+ -+ return true; -+ } -+ } -+ -+ class VulkanLinkRenderer implements InlineRendererInterface { -+ public function render(AbstractInline $inline, ElementRendererInterface $htmlRenderer) { -+ if (!($inline instanceof Code)) { -+ throw new \InvalidArgumentException('Incompatible inline type: ' . get_class($inline)); -+ } -+ -+ if (preg_match("/^[vV]k[A-Z][A-Za-z0-9_]+$/", $inline->getContent()) && strpos($inline->getContent(), "KHR") === false && strpos($inline->getContent(), "EXT") === false) { -+ $attrs = []; -+ $attrs['href'] = "https://www.khronos.org/registry/vulkan/specs/1.0/man/html/" . $inline->getContent() . ".html"; -+ -+ return new HtmlElement('a', $attrs, new HtmlElement('code', [], $inline->getContent())); -+ } else { -+ $attrs = []; -+ foreach ($inline->getData('attributes', []) as $key => $value) { -+ $attrs[$key] = $htmlRenderer->escape($value, true); -+ } -+ -+ return new HtmlElement('code', $attrs, $htmlRenderer->escape($inline->getContent())); -+ } -+ } -+ } -+ -+ class VulkanLinkProcessor extends \Todaymade\Daux\Processor { -+ public function extendCommonMarkEnvironment(\League\CommonMark\Environment $environment) { -+ // Turn Vulkan functions referenced in the text into code blocks -+ $environment->addInlineParser(new VulkanLinkParser()); -+ -+ // Turn code blocks consisting of Vulkan functions into links to the specification -+ $environment->addInlineRenderer('League\CommonMark\Inline\Element\Code', new VulkanLinkRenderer()); -+ } -+ } -+?> -diff --git a/templates/content.php b/templates/content.php -index 2febe38..03017b7 100644 ---- a/templates/content.php -+++ b/templates/content.php -@@ -2,17 +2,14 @@ -
- - - - - - -@@ -26,5 +23,24 @@ - - - -+ -+
-+ -+ -
- -diff --git a/templates/layout/00_layout.php b/templates/layout/00_layout.php -index 868dd05..774a8a5 100755 ---- a/templates/layout/00_layout.php -+++ b/templates/layout/00_layout.php -@@ -8,7 +8,7 @@ - - - -- -+ - - - -diff --git a/templates/layout/05_page.php b/templates/layout/05_page.php -index 1bd8752..b2c0ae7 100755 ---- a/templates/layout/05_page.php -+++ b/templates/layout/05_page.php -@@ -4,11 +4,6 @@ - Fork me on GitHub - -
-- -
-
- -@@ -20,7 +15,12 @@ - -
- - -+
-+ This site is not affiliated with or endorsed by the Khronos Group. Vulkan™ and the Vulkan logo are trademarks of the Khronos Group Inc. -
-
-
-diff --git a/themes/daux/css/theme-blue.min.css b/themes/daux/css/theme-blue.min.css -index 49435a8..9bb3952 100644 ---- a/themes/daux/css/theme-blue.min.css -+++ b/themes/daux/css/theme-blue.min.css -@@ -2,4 +2,4 @@ - * DAUX.IO - * http://daux.io/ - * MIT License -- */.roboto-slab.light{font-weight:100}.roboto-slab.book,.roboto-slab.light{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif}.roboto-slab.book{font-weight:300}.roboto-slab.regular{font-weight:400}.roboto-slab.bold,.roboto-slab.regular{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif}.roboto-slab.bold{font-weight:700}h1,h2,h3,h4,h5,h6{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:300}h1 i{font-size:26px}pre{padding:0}.homepage-hero{padding-top:60px!important;background-color:#82becd;box-shadow:none;border-radius:0;border:none;color:#3f4657;overflow:hidden;padding-bottom:0;margin-bottom:0}.homepage-hero .text-center{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:700;margin:10px 0}.homepage-hero h2{margin:20px 0}.hero-buttons.container-fluid{padding:20px 0;background-color:#c5c5cb}.hero-buttons.container-fluid .btn-hero.btn{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:700;padding:20px 30px;background-image:none;-webkit-filter:none;filter:none;box-shadow:none;border-radius:0;text-shadow:none;border:none;opacity:.8;filter:alpha(opacity=80);margin:0 10px;text-transform:uppercase;border:5px solid #3f4657}@media (max-width:768px){.hero-buttons.container-fluid .btn-hero.btn{display:block;margin-bottom:10px}}.hero-buttons.container-fluid .btn-hero.btn:hover{opacity:1;filter:alpha(opacity=100)}.hero-buttons.container-fluid .btn-hero.btn.btn-secondary{background-color:#c5c5cb;color:#3f4657}.hero-buttons.container-fluid .btn-hero.btn.btn-primary{background-color:#3f4657;color:#f7f7f7}.homepage-content.container-fluid{background-color:#fff;padding:40px 0}.homepage-content.container-fluid .lead{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:400}.homepage-content.container-fluid ol,.homepage-content.container-fluid ul{padding:20px 0;margin:0 0 10px}.homepage-content.container-fluid ol li,.homepage-content.container-fluid ul li{list-style:none;padding-bottom:5px}.homepage-content.container-fluid ol li:before,.homepage-content.container-fluid ul li:before{content:'';width:0;height:0;border:3px solid transparent;border-left:3px solid #82becd;float:left;display:block;margin:6px}@media (max-width:768px){.homepage-content.container-fluid{padding:40px 20px}}.homepage-footer.container-fluid{background-color:#3f4657;box-shadow:none;border-radius:0;color:light;border:none}@media (max-width:768px){.homepage-footer.container-fluid{padding:0 20px}}.homepage-footer.container-fluid .footer-nav{margin:40px 0}.homepage-footer.container-fluid .footer-nav li a{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:700;font-size:16px;line-height:32px}.homepage-footer.container-fluid .footer-nav li a:hover{color:#82becd;text-decoration:underline}.homepage-footer.container-fluid .twitter{margin-top:20px}.homepage-footer.container-fluid .twitter:first-child{margin-top:40px}body,html{height:100%;background-color:#fff;color:#2d2d2d}.columns .left-column{background-color:#f7f7f7}.columns .right-column .content-page{padding:10px;background-color:#fff}.container-fluid .navbar-static-top{margin-left:-15px;margin-right:-15px}.responsive-collapse{padding:10px 15px;display:block;background-color:#e7e7e9;border-bottom:1px solid #e7e7e9}.sub-nav-collapse{display:none}.article-tree,.content-area{padding:0}@media screen and (min-width:768px){body{background-color:#82becd}.navbar-static-top{position:fixed;z-index:1030;width:100%}.responsive-collapse{display:none}.sub-nav-collapse{display:block!important}.container-fluid.fluid-height{height:100%}.article-tree,.content-area{overflow:auto;height:100%}.columns{height:100%;padding-top:50px}.columns .left-column{border-right:1px solid #e7e7e9;overflow-x:hidden}.columns .right-column .content-page{padding:20px;min-height:100%}}@media only screen and (max-width:800px){table,tbody,td,th,thead,tr{display:block;border:none}thead tr{position:absolute;top:-9999px;left:-9999px}tr{margin-bottom:10px;border-bottom:2px solid #ccc}tr td,tr th{border:1px solid #ccc;border-bottom:none}td{border:none;border-bottom:1px solid #eee;position:relative;padding-left:50%!important;white-space:normal}td,td:before{text-align:left}td:before{position:absolute;top:6px;left:6px;width:45%;padding-right:10px;white-space:nowrap;font-weight:700;content:attr(data-title)}}@media print{.content-area{width:100%!important}h1 a[href]:after{font-size:50%}}a{color:#82becd}.btn{display:inline-block}.btn.btn-sidebar{padding:7px 10px;background-image:none;-webkit-filter:none;filter:none;box-shadow:none;background-color:#c5c5cb;border:none}.btn.btn-sidebar .icon-bar{display:block;width:18px;height:2px;margin-top:2px;margin-bottom:3px}.btn.btn-sidebar .icon-bar,.btn.btn-sidebar:hover{background-color:#3f4657;box-shadow:none}.btn.btn-sidebar:hover .icon-bar{background-color:#82becd;box-shadow:none}code{color:#82becd}.navbar{box-shadow:0 1px 5px rgba(0,0,0,.25);background-color:#3f4657;margin-bottom:0}.navbar .container,.navbar .container-fluid{background-image:none;-webkit-filter:none;filter:none;border-bottom:none;padding:0 20px}.navbar .container-fluid .brand,.navbar .container .brand{color:#82becd;text-shadow:none;font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:700}.navbar .container-fluid .navbar-text,.navbar .container-fluid .navbar-text a,.navbar .container .navbar-text,.navbar .container .navbar-text a{color:#82becd}.code-buttons-text{font-size:12px;line-height:1.5;padding:6px 10px 6px 0;display:inline-block;vertical-align:middle}.nav.nav-list{padding-left:0;padding-right:0}.nav.nav-list li a{margin:0;padding:6px 15px 6px 20px;font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:400;color:#3f4657;font-size:15px;text-shadow:none;border-color:#e7e7e9}.nav.nav-list li a .arrow{display:inline-block;position:relative;width:16px;margin-left:-16px}.nav.nav-list li a .arrow:before{position:absolute;display:block;content:"";margin:-.25em 0 0 -.4em;left:50%;top:50%;width:.5em;height:.5em;border-right:.15em solid #3f4657;border-top:.15em solid #3f4657;-webkit-transform:rotate(45deg);transform:rotate(45deg);-webkit-transition-duration:.3s;transition-duration:.3s}.nav.nav-list li a:hover{color:#3f4657;text-shadow:none;background-color:#c5c5cb}.nav.nav-list li.active a{background-color:#c5c5cb}.nav.nav-list li.open>ul{display:block}.nav.nav-list li.open>a,.nav.nav-list li.open>a:focus,.nav.nav-list li.open>a:hover{background-color:transparent}.nav.nav-list li.open>a>.arrow:before{margin-left:-.25em;-webkit-transform:rotate(135deg);transform:rotate(135deg)}.nav.nav-list li ul{display:none;margin-left:15px}.nav.nav-list li ul li a{font-weight:400;font-size:14px;font-family:Helvetica Neue,Helvetica,Arial,sans-serif;line-height:20px;margin:0;margin-left:-15px;padding:3px 30px;border:none;color:#2d2d2d;opacity:.7;filter:alpha(opacity=70)}.nav.nav-list li ul li a:hover{opacity:1;filter:alpha(opacity=100);background-color:transparent}.nav.nav-list li ul li.active a{color:#3f4657}.page-header{margin:10px 0;padding:0}.page-header h1{margin-top:0}.page-header sub-heading{padding:0,0,20px}pre{border:none;background-color:#82becd;border-radius:0;padding:10px;margin-left:-20px;padding-left:30px;margin-right:-20px;padding-right:30px}pre code{background:transparent;border:none}@media (min-width:1150px){.float-view .content-page{height:100%;overflow:auto;padding:0!important;background-color:transparent!important;position:relative}.float-view .content-page article{width:100%;min-height:100%;overflow:auto;position:relative;z-index:1}.float-view .content-page article:before{content:"";width:50%;min-height:100%;overflow:auto;background-color:#fff;display:block;margin:0;position:absolute;z-index:-1}.float-view .content-page table{float:left;clear:left;width:47%;margin-left:1.5%;margin-right:1.5%;background-color:#fff;white-space:normal}.float-view .content-page table code,.float-view .content-page table pre{white-space:normal}.float-view .content-page .page-header{padding:0}.float-view .content-page .page-header,.float-view .content-page blockquote,.float-view .content-page dl,.float-view .content-page h2,.float-view .content-page h3,.float-view .content-page h4,.float-view .content-page h5,.float-view .content-page h6,.float-view .content-page hr,.float-view .content-page ol,.float-view .content-page p,.float-view .content-page ul{float:left;clear:left;width:47%;margin-left:1.5%;margin-right:1.5%;background-color:#fff}.float-view .content-page .page-header:before,.float-view .content-page blockquote:before,.float-view .content-page dl:before,.float-view .content-page h2:before,.float-view .content-page h3:before,.float-view .content-page h4:before,.float-view .content-page h5:before,.float-view .content-page h6:before,.float-view .content-page hr:before,.float-view .content-page ol:before,.float-view .content-page p:before,.float-view .content-page ul:before{width:100%;height:10px;display:block;clear:both}.float-view .content-page .page-header dl,.float-view .content-page .page-header h2,.float-view .content-page .page-header h3,.float-view .content-page .page-header h4,.float-view .content-page .page-header h5,.float-view .content-page .page-header h6,.float-view .content-page .page-header hr,.float-view .content-page .page-header ol,.float-view .content-page .page-header p,.float-view .content-page .page-header pre,.float-view .content-page .page-header ul,.float-view .content-page blockquote dl,.float-view .content-page blockquote h2,.float-view .content-page blockquote h3,.float-view .content-page blockquote h4,.float-view .content-page blockquote h5,.float-view .content-page blockquote h6,.float-view .content-page blockquote hr,.float-view .content-page blockquote ol,.float-view .content-page blockquote p,.float-view .content-page blockquote pre,.float-view .content-page blockquote ul,.float-view .content-page dl dl,.float-view .content-page dl h2,.float-view .content-page dl h3,.float-view .content-page dl h4,.float-view .content-page dl h5,.float-view .content-page dl h6,.float-view .content-page dl hr,.float-view .content-page dl ol,.float-view .content-page dl p,.float-view .content-page dl pre,.float-view .content-page dl ul,.float-view .content-page h2 dl,.float-view .content-page h2 h2,.float-view .content-page h2 h3,.float-view .content-page h2 h4,.float-view .content-page h2 h5,.float-view .content-page h2 h6,.float-view .content-page h2 hr,.float-view .content-page h2 ol,.float-view .content-page h2 p,.float-view .content-page h2 pre,.float-view .content-page h2 ul,.float-view .content-page h3 dl,.float-view .content-page h3 h2,.float-view .content-page h3 h3,.float-view .content-page h3 h4,.float-view .content-page h3 h5,.float-view .content-page h3 h6,.float-view .content-page h3 hr,.float-view .content-page h3 ol,.float-view .content-page h3 p,.float-view .content-page h3 pre,.float-view .content-page h3 ul,.float-view .content-page h4 dl,.float-view .content-page h4 h2,.float-view .content-page h4 h3,.float-view .content-page h4 h4,.float-view .content-page h4 h5,.float-view .content-page h4 h6,.float-view .content-page h4 hr,.float-view .content-page h4 ol,.float-view .content-page h4 p,.float-view .content-page h4 pre,.float-view .content-page h4 ul,.float-view .content-page h5 dl,.float-view .content-page h5 h2,.float-view .content-page h5 h3,.float-view .content-page h5 h4,.float-view .content-page h5 h5,.float-view .content-page h5 h6,.float-view .content-page h5 hr,.float-view .content-page h5 ol,.float-view .content-page h5 p,.float-view .content-page h5 pre,.float-view .content-page h5 ul,.float-view .content-page h6 dl,.float-view .content-page h6 h2,.float-view .content-page h6 h3,.float-view .content-page h6 h4,.float-view .content-page h6 h5,.float-view .content-page h6 h6,.float-view .content-page h6 hr,.float-view .content-page h6 ol,.float-view .content-page h6 p,.float-view .content-page h6 pre,.float-view .content-page h6 ul,.float-view .content-page hr dl,.float-view .content-page hr h2,.float-view .content-page hr h3,.float-view .content-page hr h4,.float-view .content-page hr h5,.float-view .content-page hr h6,.float-view .content-page hr hr,.float-view .content-page hr ol,.float-view .content-page hr p,.float-view .content-page hr pre,.float-view .content-page hr ul,.float-view .content-page ol dl,.float-view .content-page ol h2,.float-view .content-page ol h3,.float-view .content-page ol h4,.float-view .content-page ol h5,.float-view .content-page ol h6,.float-view .content-page ol hr,.float-view .content-page ol ol,.float-view .content-page ol p,.float-view .content-page ol pre,.float-view .content-page ol ul,.float-view .content-page p dl,.float-view .content-page p h2,.float-view .content-page p h3,.float-view .content-page p h4,.float-view .content-page p h5,.float-view .content-page p h6,.float-view .content-page p hr,.float-view .content-page p ol,.float-view .content-page p p,.float-view .content-page p pre,.float-view .content-page p ul,.float-view .content-page ul dl,.float-view .content-page ul h2,.float-view .content-page ul h3,.float-view .content-page ul h4,.float-view .content-page ul h5,.float-view .content-page ul h6,.float-view .content-page ul hr,.float-view .content-page ul ol,.float-view .content-page ul p,.float-view .content-page ul pre,.float-view .content-page ul ul{float:none;display:block}.float-view .content-page hr{border-color:#ddd}.float-view .content-page blockquote p,.float-view .content-page blockquote pre,.float-view .content-page li p,.float-view .content-page li pre{width:100%}.float-view .content-page ol li,.float-view .content-page ul li{margin-left:30px}.float-view .content-page pre{float:left;clear:right;width:47%;border:none;border-left:10px solid #fff;margin:0 0 10px;padding:0 0 0 10px}}table{width:100%;border-bottom:1px solid #e7e7e9;margin-bottom:10px}table tr td,table tr th{padding:8px;line-height:20px;vertical-align:top;border-top:1px solid #e7e7e9;border-left:1px solid #e7e7e9;border-color:#e7e7e9!important}table tr td:last-child,table tr th:last-child{border-right:1px solid #e7e7e9}.footer{position:fixed;bottom:0;left:0;padding:15px}#github-ribbon{position:absolute;top:50px;right:0;z-index:200}.sidebar-links{padding:20px}.sidebar-links a{font-size:13px;font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:400;color:#82becd;line-height:28px}.sidebar-links .twitter hr{border-bottom:none;margin-left:-20px;margin-right:-20px}.search{position:relative}.search__field{padding-right:30px}.search__icon{position:absolute;right:12px;top:10px}.TableOfContents{font-size:16px;padding-left:30px;border-left:6px solid #efefef}.TableOfContents p{margin-bottom:0}.TableOfContents .TableOfContents{border-left-width:0;padding-left:20px}.hljs{display:block;padding:.5em}.hljs,.hljs-clojure .hljs-built_in,.hljs-lisp .hljs-title,.hljs-nginx .hljs-title,.hljs-subst,.hljs-tag .hljs-title{color:#3f4657}.hljs-addition,.hljs-aggregate,.hljs-apache .hljs-cbracket,.hljs-apache .hljs-tag,.hljs-bash .hljs-variable,.hljs-constant,.hljs-django .hljs-variable,.hljs-erlang_repl .hljs-function_or_atom,.hljs-flow,.hljs-markdown .hljs-header,.hljs-parent,.hljs-preprocessor,.hljs-ruby .hljs-symbol,.hljs-ruby .hljs-symbol .hljs-string,.hljs-rules .hljs-value,.hljs-rules .hljs-value .hljs-number,.hljs-smalltalk .hljs-class,.hljs-stream,.hljs-string,.hljs-tag .hljs-value,.hljs-template_tag,.hljs-tex .hljs-command,.hljs-tex .hljs-special,.hljs-title{color:#022e99}.hljs-annotation,.hljs-chunk,.hljs-comment,.hljs-diff .hljs-header,.hljs-markdown .hljs-blockquote,.hljs-template_comment{color:#84989b}.hljs-change,.hljs-date,.hljs-go .hljs-constant,.hljs-literal,.hljs-markdown .hljs-bullet,.hljs-markdown .hljs-link_url,.hljs-number,.hljs-regexp,.hljs-smalltalk .hljs-char,.hljs-smalltalk .hljs-symbol{color:#2f9b92}.hljs-apache .hljs-sqbracket,.hljs-array,.hljs-attr_selector,.hljs-clojure .hljs-attribute,.hljs-coffeescript .hljs-property,.hljs-decorator,.hljs-deletion,.hljs-doctype,.hljs-envvar,.hljs-erlang_repl .hljs-reserved,.hljs-filter .hljs-argument,.hljs-important,.hljs-javadoc,.hljs-label,.hljs-localvars,.hljs-markdown .hljs-link_label,.hljs-nginx .hljs-built_in,.hljs-pi,.hljs-prompt,.hljs-pseudo,.hljs-ruby .hljs-string,.hljs-shebang,.hljs-tex .hljs-formula,.hljs-vhdl .hljs-attribute{color:#840d7a}.hljs-aggregate,.hljs-apache .hljs-tag,.hljs-bash .hljs-variable,.hljs-built_in,.hljs-css .hljs-tag,.hljs-go .hljs-typename,.hljs-id,.hljs-javadoctag,.hljs-keyword,.hljs-markdown .hljs-strong,.hljs-phpdoc,.hljs-request,.hljs-smalltalk .hljs-class,.hljs-status,.hljs-tex .hljs-command,.hljs-title,.hljs-winutils,.hljs-yardoctag{font-weight:700}.hljs-markdown .hljs-emphasis{font-style:italic}.hljs-nginx .hljs-built_in{font-weight:400}.hljs-coffeescript .hljs-javascript,.hljs-javascript .hljs-xml,.hljs-tex .hljs-formula,.hljs-xml .hljs-cdata,.hljs-xml .hljs-css,.hljs-xml .hljs-javascript,.hljs-xml .hljs-vbscript{opacity:.5} -\ No newline at end of file -+ */.roboto-slab.light{font-weight:100}.roboto-slab.book,.roboto-slab.light{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif}.roboto-slab.book{font-weight:300}.roboto-slab.regular{font-weight:400}.roboto-slab.bold,.roboto-slab.regular{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif}.roboto-slab.bold{font-weight:700}h1,h2,h3,h4,h5,h6{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:300}h1 i{font-size:26px}pre{padding:0}.homepage-hero{padding-top:60px!important;background-color:#a41e22;box-shadow:none;border-radius:0;border:none;color:#b3b3b3;overflow:hidden;padding-bottom:0;margin-bottom:0}.homepage-hero .text-center{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:700;margin:10px 0}.homepage-hero h2{margin:20px 0}.hero-buttons.container-fluid{padding:20px 0;background-color:#4e4a4a}.hero-buttons.container-fluid .btn-hero.btn{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:700;padding:20px 30px;background-image:none;-webkit-filter:none;filter:none;box-shadow:none;border-radius:0;text-shadow:none;border:none;opacity:.8;filter:alpha(opacity=80);margin:0 10px;text-transform:uppercase;border:5px solid #b3b3b3}@media (max-width:768px){.hero-buttons.container-fluid .btn-hero.btn{display:block;margin-bottom:10px}}.hero-buttons.container-fluid .btn-hero.btn:hover{opacity:1;filter:alpha(opacity=100)}.hero-buttons.container-fluid .btn-hero.btn.btn-secondary{background-color:#4e4a4a;color:#b3b3b3}.hero-buttons.container-fluid .btn-hero.btn.btn-primary{background-color:#b3b3b3;color:#343131}.homepage-content.container-fluid{background-color:#fff;padding:40px 0}.homepage-content.container-fluid .lead{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:400}.homepage-content.container-fluid ol,.homepage-content.container-fluid ul{padding:20px 0;margin:0 0 10px}.homepage-content.container-fluid ol li,.homepage-content.container-fluid ul li{list-style:none;padding-bottom:5px}.homepage-content.container-fluid ol li:before,.homepage-content.container-fluid ul li:before{content:'';width:0;height:0;border:3px solid transparent;border-left:3px solid #a41e22;float:left;display:block;margin:6px}@media (max-width:768px){.homepage-content.container-fluid{padding:40px 20px}}.homepage-footer.container-fluid{background-color:#b3b3b3;box-shadow:none;border-radius:0;color:light;border:none}@media (max-width:768px){.homepage-footer.container-fluid{padding:0 20px}}.homepage-footer.container-fluid .footer-nav{margin:40px 0}.homepage-footer.container-fluid .footer-nav li a{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:700;font-size:16px;line-height:32px}.homepage-footer.container-fluid .footer-nav li a:hover{color:#a41e22;border-bottom-style:solid;border-bottom-color:#a41e22;text-decoration:none}.homepage-footer.container-fluid .twitter{margin-top:20px}.homepage-footer.container-fluid .twitter:first-child{margin-top:40px}body,html{height:100%;background-color:#fff;color:#2d2d2d}.content{background:#f0f0f0}.columns .left-column{background-color:#343131}.columns .right-column .content-page{padding:10px;background-color:#fff}.container-fluid .navbar-static-top{margin-left:-15px;margin-right:-15px}.responsive-collapse{padding:10px 15px;display:block;background-color:#606060;border-bottom:1px solid #606060}.sub-nav-collapse{display:none}.article-tree,.content-area{padding:0}@media screen and (min-width:768px){body{background-color:#fff}.navbar-static-top{position:fixed;z-index:1030;width:100%}.responsive-collapse{display:none}.sub-nav-collapse{display:block!important}.container-fluid.fluid-height{height:100%}.article-tree,.content-area{overflow:auto;height:100%}.columns{height:100%;padding-top:0}.columns .left-column{max-width:400px;border-right:1px solid #606060;overflow-x:hidden}.columns .right-column .content-page{margin:auto;position:relative;max-width:800px;padding:20px;min-height:100%}}@media screen and (min-width:1200px){.columns .right-column{width:calc(100% - 400px)}}@media only screen and (max-width:800px){table,tbody,td,th,thead,tr{display:block;border:none}thead tr{position:absolute;top:-9999px;left:-9999px}tr{margin-bottom:10px;border-bottom:2px solid #ccc}tr td,tr th{border:1px solid #ccc;border-bottom:none}td{border:none;border-bottom:1px solid #eee;position:relative;padding-left:50%!important;white-space:normal}td,td:before{text-align:left}td:before{position:absolute;top:6px;left:6px;width:45%;padding-right:10px;white-space:nowrap;font-weight:700;content:attr(data-title)}}@media print{.content-area{width:100%!important}h1 a[href]:after{font-size:50%}}body{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif}a{color:#a41e22;border-bottom:1px dotted #a41e22}a:hover{border-bottom-style:solid;border-bottom-color:#b3b3b3;text-decoration:none}a>code{border-bottom:1px dotted #a41e22}a>code:hover{border-bottom-style:solid}a.folder{border-bottom:none}.btn{display:inline-block}.btn.btn-sidebar{padding:7px 10px;background-image:none;-webkit-filter:none;filter:none;box-shadow:none;background-color:#4e4a4a;border:none}.btn.btn-sidebar .icon-bar{display:block;width:18px;height:2px;margin-top:2px;margin-bottom:3px}.btn.btn-sidebar .icon-bar,.btn.btn-sidebar:hover{background-color:#b3b3b3;box-shadow:none}.btn.btn-sidebar:hover .icon-bar{background-color:#a41e22;box-shadow:none}code{color:#666;border:1px solid #ddd}a code{color:#a41e22}.nav-logo{background:#a41e22;padding:20px;color:#fff;font-size:40px}.navbar{box-shadow:0 1px 5px rgba(0,0,0,.25);background-color:#b3b3b3;margin-bottom:0}.navbar .container,.navbar .container-fluid{background-image:none;-webkit-filter:none;filter:none;border-bottom:none;padding:0 20px}.navbar .container-fluid .brand,.navbar .container .brand{color:#a41e22;text-shadow:none;font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:700;border-bottom:none}.navbar .container-fluid .navbar-text,.navbar .container-fluid .navbar-text a,.navbar .container .navbar-text,.navbar .container .navbar-text a{color:#a41e22}.code-buttons-text{font-size:12px;line-height:1.5;padding:6px 10px 6px 0;display:inline-block;vertical-align:middle}.nav{background:#272525}.nav.nav-list{padding-left:0;padding-right:0}.nav.nav-list li a{margin:0;padding:6px 15px 6px 20px;font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:400;color:#b3b3b3;font-size:15px;text-shadow:none;border-color:#606060;border-bottom:none}.nav.nav-list li a .arrow{display:inline-block;position:relative;width:16px;margin-left:-16px}.nav.nav-list li a .arrow:before{position:absolute;display:block;content:"";margin:-.25em 0 0 -.4em;left:50%;top:50%;width:.5em;height:.5em;border-right:.15em solid #b3b3b3;border-top:.15em solid #b3b3b3;-webkit-transform:rotate(45deg);transform:rotate(45deg);-webkit-transition-duration:.3s;transition-duration:.3s}.nav.nav-list li a:hover{color:#b3b3b3;text-shadow:none;background-color:#4e4a4a;border-bottom:none}.nav.nav-list li.active a{background-color:#4e4a4a}.nav.nav-list li.open>ul{display:block}.nav.nav-list li.open>a,.nav.nav-list li.open>a:focus,.nav.nav-list li.open>a:hover{background-color:transparent}.nav.nav-list li.open>a>.arrow:before{margin-left:-.25em;-webkit-transform:rotate(135deg);transform:rotate(135deg)}.nav.nav-list li ul{display:none;margin-left:15px}.nav.nav-list li ul li a{font-weight:400;font-size:14px;font-family:Helvetica Neue,Helvetica,Arial,sans-serif;line-height:20px;margin:0;margin-left:-15px;padding:3px 30px;border:none;color:#b3b3b3;opacity:.7;filter:alpha(opacity=70)}.nav.nav-list li ul li a:hover{opacity:1;filter:alpha(opacity=100);background-color:transparent}.nav.nav-list li ul li.active a{color:#b3b3b3}.page-header{margin:10px 0;padding:0}.page-header h1{margin-top:0}.page-header h1 a{border-bottom:none}.page-header sub-heading{padding:0,0,20px}pre{border:none;background-color:#343131;border-radius:0;padding:10px;margin-left:-20px;padding-left:30px;margin-right:-20px;padding-right:30px}pre code{background:transparent;border:none;color:#fff}@media (min-width:1150px){.float-view .content-page{height:100%;overflow:auto;padding:0!important;background-color:transparent!important;position:relative}.float-view .content-page article{width:100%;min-height:100%;overflow:auto;position:relative;z-index:1}.float-view .content-page article:before{content:"";width:50%;min-height:100%;overflow:auto;background-color:#fff;display:block;margin:0;position:absolute;z-index:-1}.float-view .content-page table{float:left;clear:left;width:47%;margin-left:1.5%;margin-right:1.5%;background-color:#fff;white-space:normal}.float-view .content-page table code,.float-view .content-page table pre{white-space:normal}.float-view .content-page .page-header{padding:0}.float-view .content-page .page-header,.float-view .content-page blockquote,.float-view .content-page dl,.float-view .content-page h2,.float-view .content-page h3,.float-view .content-page h4,.float-view .content-page h5,.float-view .content-page h6,.float-view .content-page hr,.float-view .content-page ol,.float-view .content-page p,.float-view .content-page ul{float:left;clear:left;width:47%;margin-left:1.5%;margin-right:1.5%;background-color:#fff}.float-view .content-page .page-header:before,.float-view .content-page blockquote:before,.float-view .content-page dl:before,.float-view .content-page h2:before,.float-view .content-page h3:before,.float-view .content-page h4:before,.float-view .content-page h5:before,.float-view .content-page h6:before,.float-view .content-page hr:before,.float-view .content-page ol:before,.float-view .content-page p:before,.float-view .content-page ul:before{width:100%;height:10px;display:block;clear:both}.float-view .content-page .page-header dl,.float-view .content-page .page-header h2,.float-view .content-page .page-header h3,.float-view .content-page .page-header h4,.float-view .content-page .page-header h5,.float-view .content-page .page-header h6,.float-view .content-page .page-header hr,.float-view .content-page .page-header ol,.float-view .content-page .page-header p,.float-view .content-page .page-header pre,.float-view .content-page .page-header ul,.float-view .content-page blockquote dl,.float-view .content-page blockquote h2,.float-view .content-page blockquote h3,.float-view .content-page blockquote h4,.float-view .content-page blockquote h5,.float-view .content-page blockquote h6,.float-view .content-page blockquote hr,.float-view .content-page blockquote ol,.float-view .content-page blockquote p,.float-view .content-page blockquote pre,.float-view .content-page blockquote ul,.float-view .content-page dl dl,.float-view .content-page dl h2,.float-view .content-page dl h3,.float-view .content-page dl h4,.float-view .content-page dl h5,.float-view .content-page dl h6,.float-view .content-page dl hr,.float-view .content-page dl ol,.float-view .content-page dl p,.float-view .content-page dl pre,.float-view .content-page dl ul,.float-view .content-page h2 dl,.float-view .content-page h2 h2,.float-view .content-page h2 h3,.float-view .content-page h2 h4,.float-view .content-page h2 h5,.float-view .content-page h2 h6,.float-view .content-page h2 hr,.float-view .content-page h2 ol,.float-view .content-page h2 p,.float-view .content-page h2 pre,.float-view .content-page h2 ul,.float-view .content-page h3 dl,.float-view .content-page h3 h2,.float-view .content-page h3 h3,.float-view .content-page h3 h4,.float-view .content-page h3 h5,.float-view .content-page h3 h6,.float-view .content-page h3 hr,.float-view .content-page h3 ol,.float-view .content-page h3 p,.float-view .content-page h3 pre,.float-view .content-page h3 ul,.float-view .content-page h4 dl,.float-view .content-page h4 h2,.float-view .content-page h4 h3,.float-view .content-page h4 h4,.float-view .content-page h4 h5,.float-view .content-page h4 h6,.float-view .content-page h4 hr,.float-view .content-page h4 ol,.float-view .content-page h4 p,.float-view .content-page h4 pre,.float-view .content-page h4 ul,.float-view .content-page h5 dl,.float-view .content-page h5 h2,.float-view .content-page h5 h3,.float-view .content-page h5 h4,.float-view .content-page h5 h5,.float-view .content-page h5 h6,.float-view .content-page h5 hr,.float-view .content-page h5 ol,.float-view .content-page h5 p,.float-view .content-page h5 pre,.float-view .content-page h5 ul,.float-view .content-page h6 dl,.float-view .content-page h6 h2,.float-view .content-page h6 h3,.float-view .content-page h6 h4,.float-view .content-page h6 h5,.float-view .content-page h6 h6,.float-view .content-page h6 hr,.float-view .content-page h6 ol,.float-view .content-page h6 p,.float-view .content-page h6 pre,.float-view .content-page h6 ul,.float-view .content-page hr dl,.float-view .content-page hr h2,.float-view .content-page hr h3,.float-view .content-page hr h4,.float-view .content-page hr h5,.float-view .content-page hr h6,.float-view .content-page hr hr,.float-view .content-page hr ol,.float-view .content-page hr p,.float-view .content-page hr pre,.float-view .content-page hr ul,.float-view .content-page ol dl,.float-view .content-page ol h2,.float-view .content-page ol h3,.float-view .content-page ol h4,.float-view .content-page ol h5,.float-view .content-page ol h6,.float-view .content-page ol hr,.float-view .content-page ol ol,.float-view .content-page ol p,.float-view .content-page ol pre,.float-view .content-page ol ul,.float-view .content-page p dl,.float-view .content-page p h2,.float-view .content-page p h3,.float-view .content-page p h4,.float-view .content-page p h5,.float-view .content-page p h6,.float-view .content-page p hr,.float-view .content-page p ol,.float-view .content-page p p,.float-view .content-page p pre,.float-view .content-page p ul,.float-view .content-page ul dl,.float-view .content-page ul h2,.float-view .content-page ul h3,.float-view .content-page ul h4,.float-view .content-page ul h5,.float-view .content-page ul h6,.float-view .content-page ul hr,.float-view .content-page ul ol,.float-view .content-page ul p,.float-view .content-page ul pre,.float-view .content-page ul ul{float:none;display:block}.float-view .content-page hr{border-color:#ddd}.float-view .content-page blockquote p,.float-view .content-page blockquote pre,.float-view .content-page li p,.float-view .content-page li pre{width:100%}.float-view .content-page ol li,.float-view .content-page ul li{margin-left:30px}.float-view .content-page pre{float:left;clear:right;width:47%;border:none;border-left:10px solid #fff;margin:0 0 10px;padding:0 0 0 10px}}table{width:100%;border-bottom:1px solid #606060;margin-bottom:10px}table tr td,table tr th{padding:8px;line-height:20px;vertical-align:top;border-top:1px solid #606060;border-left:1px solid #606060;border-color:#606060!important}table tr td:last-child,table tr th:last-child{border-right:1px solid #606060}.footer{position:fixed;bottom:0;left:0;padding:15px}#github-ribbon{position:absolute;top:50px;right:0;z-index:200}.sidebar-links{padding:20px}.sidebar-links a{font-size:13px;font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:400;color:#fff;line-height:28px}.sidebar-links a,.sidebar-links a:hover{border-bottom-color:#fff}.sidebar-links .twitter hr{border-bottom:none;margin-left:-20px;margin-right:-20px}.search{position:relative}.search__field{padding-right:30px}.search__icon{position:absolute;right:12px;top:10px}.TableOfContents{font-size:16px;padding-left:30px;border-left:6px solid #efefef}.TableOfContents p{margin-bottom:0}.TableOfContents .TableOfContents{border-left-width:0;padding-left:20px}.hljs{display:block;padding:.5em}.hljs,.hljs-clojure .hljs-built_in,.hljs-lisp .hljs-title,.hljs-nginx .hljs-title,.hljs-subst,.hljs-tag .hljs-title{color:#f8f8f2}.hljs-meta{color:#75715e}.hljs-keyword{color:#f92672}.hljs-function{color:#89e229}.hljs-function .hljs-params{color:#fff}.hljs-literal,.hljs-number{color:#ae81ff}.hljs-string{color:#e6db74}.hljs-comment{color:#75715e}.hljs-type{color:#66d8ee} -\ No newline at end of file -diff --git a/themes/daux/css/theme-green.min.css b/themes/daux/css/theme-green.min.css -index 2e7376a..82279dc 100644 ---- a/themes/daux/css/theme-green.min.css -+++ b/themes/daux/css/theme-green.min.css -@@ -2,4 +2,4 @@ - * DAUX.IO - * http://daux.io/ - * MIT License -- */.roboto-slab.light{font-weight:100}.roboto-slab.book,.roboto-slab.light{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif}.roboto-slab.book{font-weight:300}.roboto-slab.regular{font-weight:400}.roboto-slab.bold,.roboto-slab.regular{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif}.roboto-slab.bold{font-weight:700}h1,h2,h3,h4,h5,h6{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:300}h1 i{font-size:26px}pre{padding:0}.homepage-hero{padding-top:60px!important;background-color:#8acc37;box-shadow:none;border-radius:0;border:none;color:#000;overflow:hidden;padding-bottom:0;margin-bottom:0}.homepage-hero .text-center{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:700;margin:10px 0}.homepage-hero h2{margin:20px 0}.hero-buttons.container-fluid{padding:20px 0;background-color:#a0d55d}.hero-buttons.container-fluid .btn-hero.btn{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:700;padding:20px 30px;background-image:none;-webkit-filter:none;filter:none;box-shadow:none;border-radius:0;text-shadow:none;border:none;opacity:.8;filter:alpha(opacity=80);margin:0 10px;text-transform:uppercase;border:5px solid #000}@media (max-width:768px){.hero-buttons.container-fluid .btn-hero.btn{display:block;margin-bottom:10px}}.hero-buttons.container-fluid .btn-hero.btn:hover{opacity:1;filter:alpha(opacity=100)}.hero-buttons.container-fluid .btn-hero.btn.btn-secondary{background-color:#a0d55d;color:#000}.hero-buttons.container-fluid .btn-hero.btn.btn-primary{background-color:#000;color:#f5f5f6}.homepage-content.container-fluid{background-color:#fff;padding:40px 0}.homepage-content.container-fluid .lead{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:400}.homepage-content.container-fluid ol,.homepage-content.container-fluid ul{padding:20px 0;margin:0 0 10px}.homepage-content.container-fluid ol li,.homepage-content.container-fluid ul li{list-style:none;padding-bottom:5px}.homepage-content.container-fluid ol li:before,.homepage-content.container-fluid ul li:before{content:'';width:0;height:0;border:3px solid transparent;border-left:3px solid #8acc37;float:left;display:block;margin:6px}@media (max-width:768px){.homepage-content.container-fluid{padding:40px 20px}}.homepage-footer.container-fluid{background-color:#000;box-shadow:none;border-radius:0;color:light;border:none}@media (max-width:768px){.homepage-footer.container-fluid{padding:0 20px}}.homepage-footer.container-fluid .footer-nav{margin:40px 0}.homepage-footer.container-fluid .footer-nav li a{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:700;font-size:16px;line-height:32px}.homepage-footer.container-fluid .footer-nav li a:hover{color:#8acc37;text-decoration:underline}.homepage-footer.container-fluid .twitter{margin-top:20px}.homepage-footer.container-fluid .twitter:first-child{margin-top:40px}body,html{height:100%;background-color:#fff;color:#2d2d2d}.columns .left-column{background-color:#f5f5f6}.columns .right-column .content-page{padding:10px;background-color:#fff}.container-fluid .navbar-static-top{margin-left:-15px;margin-right:-15px}.responsive-collapse{padding:10px 15px;display:block;background-color:#e7e7e9;border-bottom:1px solid #e7e7e9}.sub-nav-collapse{display:none}.article-tree,.content-area{padding:0}@media screen and (min-width:768px){body{background-color:#8acc37}.navbar-static-top{position:fixed;z-index:1030;width:100%}.responsive-collapse{display:none}.sub-nav-collapse{display:block!important}.container-fluid.fluid-height{height:100%}.article-tree,.content-area{overflow:auto;height:100%}.columns{height:100%;padding-top:50px}.columns .left-column{border-right:1px solid #e7e7e9;overflow-x:hidden}.columns .right-column .content-page{padding:20px;min-height:100%}}@media only screen and (max-width:800px){table,tbody,td,th,thead,tr{display:block;border:none}thead tr{position:absolute;top:-9999px;left:-9999px}tr{margin-bottom:10px;border-bottom:2px solid #ccc}tr td,tr th{border:1px solid #ccc;border-bottom:none}td{border:none;border-bottom:1px solid #eee;position:relative;padding-left:50%!important;white-space:normal}td,td:before{text-align:left}td:before{position:absolute;top:6px;left:6px;width:45%;padding-right:10px;white-space:nowrap;font-weight:700;content:attr(data-title)}}@media print{.content-area{width:100%!important}h1 a[href]:after{font-size:50%}}a{color:#8acc37}.btn{display:inline-block}.btn.btn-sidebar{padding:7px 10px;background-image:none;-webkit-filter:none;filter:none;box-shadow:none;background-color:#a0d55d;border:none}.btn.btn-sidebar .icon-bar{display:block;width:18px;height:2px;margin-top:2px;margin-bottom:3px}.btn.btn-sidebar .icon-bar,.btn.btn-sidebar:hover{background-color:#000;box-shadow:none}.btn.btn-sidebar:hover .icon-bar{background-color:#8acc37;box-shadow:none}code{color:#8acc37}.navbar{box-shadow:0 1px 5px rgba(0,0,0,.25);background-color:#000;margin-bottom:0}.navbar .container,.navbar .container-fluid{background-image:none;-webkit-filter:none;filter:none;border-bottom:none;padding:0 20px}.navbar .container-fluid .brand,.navbar .container .brand{color:#8acc37;text-shadow:none;font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:700}.navbar .container-fluid .navbar-text,.navbar .container-fluid .navbar-text a,.navbar .container .navbar-text,.navbar .container .navbar-text a{color:#8acc37}.code-buttons-text{font-size:12px;line-height:1.5;padding:6px 10px 6px 0;display:inline-block;vertical-align:middle}.nav.nav-list{padding-left:0;padding-right:0}.nav.nav-list li a{margin:0;padding:6px 15px 6px 20px;font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:400;color:#000;font-size:15px;text-shadow:none;border-color:#e7e7e9}.nav.nav-list li a .arrow{display:inline-block;position:relative;width:16px;margin-left:-16px}.nav.nav-list li a .arrow:before{position:absolute;display:block;content:"";margin:-.25em 0 0 -.4em;left:50%;top:50%;width:.5em;height:.5em;border-right:.15em solid #000;border-top:.15em solid #000;-webkit-transform:rotate(45deg);transform:rotate(45deg);-webkit-transition-duration:.3s;transition-duration:.3s}.nav.nav-list li a:hover{color:#000;text-shadow:none;background-color:#a0d55d}.nav.nav-list li.active a{background-color:#a0d55d}.nav.nav-list li.open>ul{display:block}.nav.nav-list li.open>a,.nav.nav-list li.open>a:focus,.nav.nav-list li.open>a:hover{background-color:transparent}.nav.nav-list li.open>a>.arrow:before{margin-left:-.25em;-webkit-transform:rotate(135deg);transform:rotate(135deg)}.nav.nav-list li ul{display:none;margin-left:15px}.nav.nav-list li ul li a{font-weight:400;font-size:14px;font-family:Helvetica Neue,Helvetica,Arial,sans-serif;line-height:20px;margin:0;margin-left:-15px;padding:3px 30px;border:none;color:#2d2d2d;opacity:.7;filter:alpha(opacity=70)}.nav.nav-list li ul li a:hover{opacity:1;filter:alpha(opacity=100);background-color:transparent}.nav.nav-list li ul li.active a{color:#000}.page-header{margin:10px 0;padding:0}.page-header h1{margin-top:0}.page-header sub-heading{padding:0,0,20px}pre{border:none;background-color:#8acc37;border-radius:0;padding:10px;margin-left:-20px;padding-left:30px;margin-right:-20px;padding-right:30px}pre code{background:transparent;border:none}@media (min-width:1150px){.float-view .content-page{height:100%;overflow:auto;padding:0!important;background-color:transparent!important;position:relative}.float-view .content-page article{width:100%;min-height:100%;overflow:auto;position:relative;z-index:1}.float-view .content-page article:before{content:"";width:50%;min-height:100%;overflow:auto;background-color:#fff;display:block;margin:0;position:absolute;z-index:-1}.float-view .content-page table{float:left;clear:left;width:47%;margin-left:1.5%;margin-right:1.5%;background-color:#fff;white-space:normal}.float-view .content-page table code,.float-view .content-page table pre{white-space:normal}.float-view .content-page .page-header{padding:0}.float-view .content-page .page-header,.float-view .content-page blockquote,.float-view .content-page dl,.float-view .content-page h2,.float-view .content-page h3,.float-view .content-page h4,.float-view .content-page h5,.float-view .content-page h6,.float-view .content-page hr,.float-view .content-page ol,.float-view .content-page p,.float-view .content-page ul{float:left;clear:left;width:47%;margin-left:1.5%;margin-right:1.5%;background-color:#fff}.float-view .content-page .page-header:before,.float-view .content-page blockquote:before,.float-view .content-page dl:before,.float-view .content-page h2:before,.float-view .content-page h3:before,.float-view .content-page h4:before,.float-view .content-page h5:before,.float-view .content-page h6:before,.float-view .content-page hr:before,.float-view .content-page ol:before,.float-view .content-page p:before,.float-view .content-page ul:before{width:100%;height:10px;display:block;clear:both}.float-view .content-page .page-header dl,.float-view .content-page .page-header h2,.float-view .content-page .page-header h3,.float-view .content-page .page-header h4,.float-view .content-page .page-header h5,.float-view .content-page .page-header h6,.float-view .content-page .page-header hr,.float-view .content-page .page-header ol,.float-view .content-page .page-header p,.float-view .content-page .page-header pre,.float-view .content-page .page-header ul,.float-view .content-page blockquote dl,.float-view .content-page blockquote h2,.float-view .content-page blockquote h3,.float-view .content-page blockquote h4,.float-view .content-page blockquote h5,.float-view .content-page blockquote h6,.float-view .content-page blockquote hr,.float-view .content-page blockquote ol,.float-view .content-page blockquote p,.float-view .content-page blockquote pre,.float-view .content-page blockquote ul,.float-view .content-page dl dl,.float-view .content-page dl h2,.float-view .content-page dl h3,.float-view .content-page dl h4,.float-view .content-page dl h5,.float-view .content-page dl h6,.float-view .content-page dl hr,.float-view .content-page dl ol,.float-view .content-page dl p,.float-view .content-page dl pre,.float-view .content-page dl ul,.float-view .content-page h2 dl,.float-view .content-page h2 h2,.float-view .content-page h2 h3,.float-view .content-page h2 h4,.float-view .content-page h2 h5,.float-view .content-page h2 h6,.float-view .content-page h2 hr,.float-view .content-page h2 ol,.float-view .content-page h2 p,.float-view .content-page h2 pre,.float-view .content-page h2 ul,.float-view .content-page h3 dl,.float-view .content-page h3 h2,.float-view .content-page h3 h3,.float-view .content-page h3 h4,.float-view .content-page h3 h5,.float-view .content-page h3 h6,.float-view .content-page h3 hr,.float-view .content-page h3 ol,.float-view .content-page h3 p,.float-view .content-page h3 pre,.float-view .content-page h3 ul,.float-view .content-page h4 dl,.float-view .content-page h4 h2,.float-view .content-page h4 h3,.float-view .content-page h4 h4,.float-view .content-page h4 h5,.float-view .content-page h4 h6,.float-view .content-page h4 hr,.float-view .content-page h4 ol,.float-view .content-page h4 p,.float-view .content-page h4 pre,.float-view .content-page h4 ul,.float-view .content-page h5 dl,.float-view .content-page h5 h2,.float-view .content-page h5 h3,.float-view .content-page h5 h4,.float-view .content-page h5 h5,.float-view .content-page h5 h6,.float-view .content-page h5 hr,.float-view .content-page h5 ol,.float-view .content-page h5 p,.float-view .content-page h5 pre,.float-view .content-page h5 ul,.float-view .content-page h6 dl,.float-view .content-page h6 h2,.float-view .content-page h6 h3,.float-view .content-page h6 h4,.float-view .content-page h6 h5,.float-view .content-page h6 h6,.float-view .content-page h6 hr,.float-view .content-page h6 ol,.float-view .content-page h6 p,.float-view .content-page h6 pre,.float-view .content-page h6 ul,.float-view .content-page hr dl,.float-view .content-page hr h2,.float-view .content-page hr h3,.float-view .content-page hr h4,.float-view .content-page hr h5,.float-view .content-page hr h6,.float-view .content-page hr hr,.float-view .content-page hr ol,.float-view .content-page hr p,.float-view .content-page hr pre,.float-view .content-page hr ul,.float-view .content-page ol dl,.float-view .content-page ol h2,.float-view .content-page ol h3,.float-view .content-page ol h4,.float-view .content-page ol h5,.float-view .content-page ol h6,.float-view .content-page ol hr,.float-view .content-page ol ol,.float-view .content-page ol p,.float-view .content-page ol pre,.float-view .content-page ol ul,.float-view .content-page p dl,.float-view .content-page p h2,.float-view .content-page p h3,.float-view .content-page p h4,.float-view .content-page p h5,.float-view .content-page p h6,.float-view .content-page p hr,.float-view .content-page p ol,.float-view .content-page p p,.float-view .content-page p pre,.float-view .content-page p ul,.float-view .content-page ul dl,.float-view .content-page ul h2,.float-view .content-page ul h3,.float-view .content-page ul h4,.float-view .content-page ul h5,.float-view .content-page ul h6,.float-view .content-page ul hr,.float-view .content-page ul ol,.float-view .content-page ul p,.float-view .content-page ul pre,.float-view .content-page ul ul{float:none;display:block}.float-view .content-page hr{border-color:#ddd}.float-view .content-page blockquote p,.float-view .content-page blockquote pre,.float-view .content-page li p,.float-view .content-page li pre{width:100%}.float-view .content-page ol li,.float-view .content-page ul li{margin-left:30px}.float-view .content-page pre{float:left;clear:right;width:47%;border:none;border-left:10px solid #fff;margin:0 0 10px;padding:0 0 0 10px}}table{width:100%;border-bottom:1px solid #e7e7e9;margin-bottom:10px}table tr td,table tr th{padding:8px;line-height:20px;vertical-align:top;border-top:1px solid #e7e7e9;border-left:1px solid #e7e7e9;border-color:#e7e7e9!important}table tr td:last-child,table tr th:last-child{border-right:1px solid #e7e7e9}.footer{position:fixed;bottom:0;left:0;padding:15px}#github-ribbon{position:absolute;top:50px;right:0;z-index:200}.sidebar-links{padding:20px}.sidebar-links a{font-size:13px;font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:400;color:#8acc37;line-height:28px}.sidebar-links .twitter hr{border-bottom:none;margin-left:-20px;margin-right:-20px}.search{position:relative}.search__field{padding-right:30px}.search__icon{position:absolute;right:12px;top:10px}.TableOfContents{font-size:16px;padding-left:30px;border-left:6px solid #efefef}.TableOfContents p{margin-bottom:0}.TableOfContents .TableOfContents{border-left-width:0;padding-left:20px}.hljs{display:block;padding:.5em}.hljs,.hljs-clojure .hljs-built_in,.hljs-lisp .hljs-title,.hljs-nginx .hljs-title,.hljs-subst,.hljs-tag .hljs-title{color:#000}.hljs-addition,.hljs-aggregate,.hljs-apache .hljs-cbracket,.hljs-apache .hljs-tag,.hljs-bash .hljs-variable,.hljs-constant,.hljs-django .hljs-variable,.hljs-erlang_repl .hljs-function_or_atom,.hljs-flow,.hljs-markdown .hljs-header,.hljs-parent,.hljs-preprocessor,.hljs-ruby .hljs-symbol,.hljs-ruby .hljs-symbol .hljs-string,.hljs-rules .hljs-value,.hljs-rules .hljs-value .hljs-number,.hljs-smalltalk .hljs-class,.hljs-stream,.hljs-string,.hljs-tag .hljs-value,.hljs-template_tag,.hljs-tex .hljs-command,.hljs-tex .hljs-special,.hljs-title{color:#e0ff00}.hljs-annotation,.hljs-chunk,.hljs-comment,.hljs-diff .hljs-header,.hljs-markdown .hljs-blockquote,.hljs-template_comment{color:#c4e598}.hljs-change,.hljs-date,.hljs-go .hljs-constant,.hljs-literal,.hljs-markdown .hljs-bullet,.hljs-markdown .hljs-link_url,.hljs-number,.hljs-regexp,.hljs-smalltalk .hljs-char,.hljs-smalltalk .hljs-symbol{color:#097c4e}.hljs-apache .hljs-sqbracket,.hljs-array,.hljs-attr_selector,.hljs-clojure .hljs-attribute,.hljs-coffeescript .hljs-property,.hljs-decorator,.hljs-deletion,.hljs-doctype,.hljs-envvar,.hljs-erlang_repl .hljs-reserved,.hljs-filter .hljs-argument,.hljs-important,.hljs-javadoc,.hljs-label,.hljs-localvars,.hljs-markdown .hljs-link_label,.hljs-nginx .hljs-built_in,.hljs-pi,.hljs-prompt,.hljs-pseudo,.hljs-ruby .hljs-string,.hljs-shebang,.hljs-tex .hljs-formula,.hljs-vhdl .hljs-attribute{color:#022e99}.hljs-aggregate,.hljs-apache .hljs-tag,.hljs-bash .hljs-variable,.hljs-built_in,.hljs-css .hljs-tag,.hljs-go .hljs-typename,.hljs-id,.hljs-javadoctag,.hljs-keyword,.hljs-markdown .hljs-strong,.hljs-phpdoc,.hljs-request,.hljs-smalltalk .hljs-class,.hljs-status,.hljs-tex .hljs-command,.hljs-title,.hljs-winutils,.hljs-yardoctag{font-weight:700}.hljs-markdown .hljs-emphasis{font-style:italic}.hljs-nginx .hljs-built_in{font-weight:400}.hljs-coffeescript .hljs-javascript,.hljs-javascript .hljs-xml,.hljs-tex .hljs-formula,.hljs-xml .hljs-cdata,.hljs-xml .hljs-css,.hljs-xml .hljs-javascript,.hljs-xml .hljs-vbscript{opacity:.5} -\ No newline at end of file -+ */.roboto-slab.light{font-weight:100}.roboto-slab.book,.roboto-slab.light{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif}.roboto-slab.book{font-weight:300}.roboto-slab.regular{font-weight:400}.roboto-slab.bold,.roboto-slab.regular{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif}.roboto-slab.bold{font-weight:700}h1,h2,h3,h4,h5,h6{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:300}h1 i{font-size:26px}pre{padding:0}.homepage-hero{padding-top:60px!important;background-color:#8acc37;box-shadow:none;border-radius:0;border:none;color:#000;overflow:hidden;padding-bottom:0;margin-bottom:0}.homepage-hero .text-center{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:700;margin:10px 0}.homepage-hero h2{margin:20px 0}.hero-buttons.container-fluid{padding:20px 0;background-color:#a0d55d}.hero-buttons.container-fluid .btn-hero.btn{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:700;padding:20px 30px;background-image:none;-webkit-filter:none;filter:none;box-shadow:none;border-radius:0;text-shadow:none;border:none;opacity:.8;filter:alpha(opacity=80);margin:0 10px;text-transform:uppercase;border:5px solid #000}@media (max-width:768px){.hero-buttons.container-fluid .btn-hero.btn{display:block;margin-bottom:10px}}.hero-buttons.container-fluid .btn-hero.btn:hover{opacity:1;filter:alpha(opacity=100)}.hero-buttons.container-fluid .btn-hero.btn.btn-secondary{background-color:#a0d55d;color:#000}.hero-buttons.container-fluid .btn-hero.btn.btn-primary{background-color:#000;color:#f5f5f6}.homepage-content.container-fluid{background-color:#fff;padding:40px 0}.homepage-content.container-fluid .lead{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:400}.homepage-content.container-fluid ol,.homepage-content.container-fluid ul{padding:20px 0;margin:0 0 10px}.homepage-content.container-fluid ol li,.homepage-content.container-fluid ul li{list-style:none;padding-bottom:5px}.homepage-content.container-fluid ol li:before,.homepage-content.container-fluid ul li:before{content:'';width:0;height:0;border:3px solid transparent;border-left:3px solid #8acc37;float:left;display:block;margin:6px}@media (max-width:768px){.homepage-content.container-fluid{padding:40px 20px}}.homepage-footer.container-fluid{background-color:#000;box-shadow:none;border-radius:0;color:light;border:none}@media (max-width:768px){.homepage-footer.container-fluid{padding:0 20px}}.homepage-footer.container-fluid .footer-nav{margin:40px 0}.homepage-footer.container-fluid .footer-nav li a{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:700;font-size:16px;line-height:32px}.homepage-footer.container-fluid .footer-nav li a:hover{color:#8acc37;border-bottom-style:solid;border-bottom-color:#8acc37;text-decoration:none}.homepage-footer.container-fluid .twitter{margin-top:20px}.homepage-footer.container-fluid .twitter:first-child{margin-top:40px}body,html{height:100%;background-color:#fff;color:#2d2d2d}.content{background:#f0f0f0}.columns .left-column{background-color:#f5f5f6}.columns .right-column .content-page{padding:10px;background-color:#fff}.container-fluid .navbar-static-top{margin-left:-15px;margin-right:-15px}.responsive-collapse{padding:10px 15px;display:block;background-color:#e7e7e9;border-bottom:1px solid #e7e7e9}.sub-nav-collapse{display:none}.article-tree,.content-area{padding:0}@media screen and (min-width:768px){body{background-color:#fff}.navbar-static-top{position:fixed;z-index:1030;width:100%}.responsive-collapse{display:none}.sub-nav-collapse{display:block!important}.container-fluid.fluid-height{height:100%}.article-tree,.content-area{overflow:auto;height:100%}.columns{height:100%;padding-top:0}.columns .left-column{max-width:400px;border-right:1px solid #e7e7e9;overflow-x:hidden}.columns .right-column .content-page{margin:auto;position:relative;max-width:800px;padding:20px;min-height:100%}}@media screen and (min-width:1200px){.columns .right-column{width:calc(100% - 400px)}}@media only screen and (max-width:800px){table,tbody,td,th,thead,tr{display:block;border:none}thead tr{position:absolute;top:-9999px;left:-9999px}tr{margin-bottom:10px;border-bottom:2px solid #ccc}tr td,tr th{border:1px solid #ccc;border-bottom:none}td{border:none;border-bottom:1px solid #eee;position:relative;padding-left:50%!important;white-space:normal}td,td:before{text-align:left}td:before{position:absolute;top:6px;left:6px;width:45%;padding-right:10px;white-space:nowrap;font-weight:700;content:attr(data-title)}}@media print{.content-area{width:100%!important}h1 a[href]:after{font-size:50%}}body{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif}a{color:#8acc37;border-bottom:1px dotted #8acc37}a:hover{border-bottom-style:solid;border-bottom-color:#000;text-decoration:none}a>code{border-bottom:1px dotted #8acc37}a>code:hover{border-bottom-style:solid}a.folder{border-bottom:none}.btn{display:inline-block}.btn.btn-sidebar{padding:7px 10px;background-image:none;-webkit-filter:none;filter:none;box-shadow:none;background-color:#a0d55d;border:none}.btn.btn-sidebar .icon-bar{display:block;width:18px;height:2px;margin-top:2px;margin-bottom:3px}.btn.btn-sidebar .icon-bar,.btn.btn-sidebar:hover{background-color:#000;box-shadow:none}.btn.btn-sidebar:hover .icon-bar{background-color:#8acc37;box-shadow:none}code{color:#666;border:1px solid #ddd}a code{color:#8acc37}.nav-logo{background:#a41e22;padding:20px;color:#fff;font-size:40px}.navbar{box-shadow:0 1px 5px rgba(0,0,0,.25);background-color:#000;margin-bottom:0}.navbar .container,.navbar .container-fluid{background-image:none;-webkit-filter:none;filter:none;border-bottom:none;padding:0 20px}.navbar .container-fluid .brand,.navbar .container .brand{color:#8acc37;text-shadow:none;font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:700;border-bottom:none}.navbar .container-fluid .navbar-text,.navbar .container-fluid .navbar-text a,.navbar .container .navbar-text,.navbar .container .navbar-text a{color:#8acc37}.code-buttons-text{font-size:12px;line-height:1.5;padding:6px 10px 6px 0;display:inline-block;vertical-align:middle}.nav{background:#272525}.nav.nav-list{padding-left:0;padding-right:0}.nav.nav-list li a{margin:0;padding:6px 15px 6px 20px;font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:400;color:#b3b3b3;font-size:15px;text-shadow:none;border-color:#e7e7e9;border-bottom:none}.nav.nav-list li a .arrow{display:inline-block;position:relative;width:16px;margin-left:-16px}.nav.nav-list li a .arrow:before{position:absolute;display:block;content:"";margin:-.25em 0 0 -.4em;left:50%;top:50%;width:.5em;height:.5em;border-right:.15em solid #000;border-top:.15em solid #000;-webkit-transform:rotate(45deg);transform:rotate(45deg);-webkit-transition-duration:.3s;transition-duration:.3s}.nav.nav-list li a:hover{color:#000;text-shadow:none;background-color:#a0d55d;border-bottom:none}.nav.nav-list li.active a{background-color:#a0d55d}.nav.nav-list li.open>ul{display:block}.nav.nav-list li.open>a,.nav.nav-list li.open>a:focus,.nav.nav-list li.open>a:hover{background-color:transparent}.nav.nav-list li.open>a>.arrow:before{margin-left:-.25em;-webkit-transform:rotate(135deg);transform:rotate(135deg)}.nav.nav-list li ul{display:none;margin-left:15px}.nav.nav-list li ul li a{font-weight:400;font-size:14px;font-family:Helvetica Neue,Helvetica,Arial,sans-serif;line-height:20px;margin:0;margin-left:-15px;padding:3px 30px;border:none;color:#b3b3b3;opacity:.7;filter:alpha(opacity=70)}.nav.nav-list li ul li a:hover{opacity:1;filter:alpha(opacity=100);background-color:transparent}.nav.nav-list li ul li.active a{color:#b3b3b3}.page-header{margin:10px 0;padding:0}.page-header h1{margin-top:0}.page-header h1 a{border-bottom:none}.page-header sub-heading{padding:0,0,20px}pre{border:none;background-color:#343131;border-radius:0;padding:10px;margin-left:-20px;padding-left:30px;margin-right:-20px;padding-right:30px}pre code{background:transparent;border:none;color:#fff}@media (min-width:1150px){.float-view .content-page{height:100%;overflow:auto;padding:0!important;background-color:transparent!important;position:relative}.float-view .content-page article{width:100%;min-height:100%;overflow:auto;position:relative;z-index:1}.float-view .content-page article:before{content:"";width:50%;min-height:100%;overflow:auto;background-color:#fff;display:block;margin:0;position:absolute;z-index:-1}.float-view .content-page table{float:left;clear:left;width:47%;margin-left:1.5%;margin-right:1.5%;background-color:#fff;white-space:normal}.float-view .content-page table code,.float-view .content-page table pre{white-space:normal}.float-view .content-page .page-header{padding:0}.float-view .content-page .page-header,.float-view .content-page blockquote,.float-view .content-page dl,.float-view .content-page h2,.float-view .content-page h3,.float-view .content-page h4,.float-view .content-page h5,.float-view .content-page h6,.float-view .content-page hr,.float-view .content-page ol,.float-view .content-page p,.float-view .content-page ul{float:left;clear:left;width:47%;margin-left:1.5%;margin-right:1.5%;background-color:#fff}.float-view .content-page .page-header:before,.float-view .content-page blockquote:before,.float-view .content-page dl:before,.float-view .content-page h2:before,.float-view .content-page h3:before,.float-view .content-page h4:before,.float-view .content-page h5:before,.float-view .content-page h6:before,.float-view .content-page hr:before,.float-view .content-page ol:before,.float-view .content-page p:before,.float-view .content-page ul:before{width:100%;height:10px;display:block;clear:both}.float-view .content-page .page-header dl,.float-view .content-page .page-header h2,.float-view .content-page .page-header h3,.float-view .content-page .page-header h4,.float-view .content-page .page-header h5,.float-view .content-page .page-header h6,.float-view .content-page .page-header hr,.float-view .content-page .page-header ol,.float-view .content-page .page-header p,.float-view .content-page .page-header pre,.float-view .content-page .page-header ul,.float-view .content-page blockquote dl,.float-view .content-page blockquote h2,.float-view .content-page blockquote h3,.float-view .content-page blockquote h4,.float-view .content-page blockquote h5,.float-view .content-page blockquote h6,.float-view .content-page blockquote hr,.float-view .content-page blockquote ol,.float-view .content-page blockquote p,.float-view .content-page blockquote pre,.float-view .content-page blockquote ul,.float-view .content-page dl dl,.float-view .content-page dl h2,.float-view .content-page dl h3,.float-view .content-page dl h4,.float-view .content-page dl h5,.float-view .content-page dl h6,.float-view .content-page dl hr,.float-view .content-page dl ol,.float-view .content-page dl p,.float-view .content-page dl pre,.float-view .content-page dl ul,.float-view .content-page h2 dl,.float-view .content-page h2 h2,.float-view .content-page h2 h3,.float-view .content-page h2 h4,.float-view .content-page h2 h5,.float-view .content-page h2 h6,.float-view .content-page h2 hr,.float-view .content-page h2 ol,.float-view .content-page h2 p,.float-view .content-page h2 pre,.float-view .content-page h2 ul,.float-view .content-page h3 dl,.float-view .content-page h3 h2,.float-view .content-page h3 h3,.float-view .content-page h3 h4,.float-view .content-page h3 h5,.float-view .content-page h3 h6,.float-view .content-page h3 hr,.float-view .content-page h3 ol,.float-view .content-page h3 p,.float-view .content-page h3 pre,.float-view .content-page h3 ul,.float-view .content-page h4 dl,.float-view .content-page h4 h2,.float-view .content-page h4 h3,.float-view .content-page h4 h4,.float-view .content-page h4 h5,.float-view .content-page h4 h6,.float-view .content-page h4 hr,.float-view .content-page h4 ol,.float-view .content-page h4 p,.float-view .content-page h4 pre,.float-view .content-page h4 ul,.float-view .content-page h5 dl,.float-view .content-page h5 h2,.float-view .content-page h5 h3,.float-view .content-page h5 h4,.float-view .content-page h5 h5,.float-view .content-page h5 h6,.float-view .content-page h5 hr,.float-view .content-page h5 ol,.float-view .content-page h5 p,.float-view .content-page h5 pre,.float-view .content-page h5 ul,.float-view .content-page h6 dl,.float-view .content-page h6 h2,.float-view .content-page h6 h3,.float-view .content-page h6 h4,.float-view .content-page h6 h5,.float-view .content-page h6 h6,.float-view .content-page h6 hr,.float-view .content-page h6 ol,.float-view .content-page h6 p,.float-view .content-page h6 pre,.float-view .content-page h6 ul,.float-view .content-page hr dl,.float-view .content-page hr h2,.float-view .content-page hr h3,.float-view .content-page hr h4,.float-view .content-page hr h5,.float-view .content-page hr h6,.float-view .content-page hr hr,.float-view .content-page hr ol,.float-view .content-page hr p,.float-view .content-page hr pre,.float-view .content-page hr ul,.float-view .content-page ol dl,.float-view .content-page ol h2,.float-view .content-page ol h3,.float-view .content-page ol h4,.float-view .content-page ol h5,.float-view .content-page ol h6,.float-view .content-page ol hr,.float-view .content-page ol ol,.float-view .content-page ol p,.float-view .content-page ol pre,.float-view .content-page ol ul,.float-view .content-page p dl,.float-view .content-page p h2,.float-view .content-page p h3,.float-view .content-page p h4,.float-view .content-page p h5,.float-view .content-page p h6,.float-view .content-page p hr,.float-view .content-page p ol,.float-view .content-page p p,.float-view .content-page p pre,.float-view .content-page p ul,.float-view .content-page ul dl,.float-view .content-page ul h2,.float-view .content-page ul h3,.float-view .content-page ul h4,.float-view .content-page ul h5,.float-view .content-page ul h6,.float-view .content-page ul hr,.float-view .content-page ul ol,.float-view .content-page ul p,.float-view .content-page ul pre,.float-view .content-page ul ul{float:none;display:block}.float-view .content-page hr{border-color:#ddd}.float-view .content-page blockquote p,.float-view .content-page blockquote pre,.float-view .content-page li p,.float-view .content-page li pre{width:100%}.float-view .content-page ol li,.float-view .content-page ul li{margin-left:30px}.float-view .content-page pre{float:left;clear:right;width:47%;border:none;border-left:10px solid #fff;margin:0 0 10px;padding:0 0 0 10px}}table{width:100%;border-bottom:1px solid #e7e7e9;margin-bottom:10px}table tr td,table tr th{padding:8px;line-height:20px;vertical-align:top;border-top:1px solid #e7e7e9;border-left:1px solid #e7e7e9;border-color:#e7e7e9!important}table tr td:last-child,table tr th:last-child{border-right:1px solid #e7e7e9}.footer{position:fixed;bottom:0;left:0;padding:15px}#github-ribbon{position:absolute;top:50px;right:0;z-index:200}.sidebar-links{padding:20px}.sidebar-links a{font-size:13px;font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:400;color:#fff;line-height:28px}.sidebar-links a,.sidebar-links a:hover{border-bottom-color:#fff}.sidebar-links .twitter hr{border-bottom:none;margin-left:-20px;margin-right:-20px}.search{position:relative}.search__field{padding-right:30px}.search__icon{position:absolute;right:12px;top:10px}.TableOfContents{font-size:16px;padding-left:30px;border-left:6px solid #efefef}.TableOfContents p{margin-bottom:0}.TableOfContents .TableOfContents{border-left-width:0;padding-left:20px}.hljs{display:block;padding:.5em}.hljs,.hljs-clojure .hljs-built_in,.hljs-lisp .hljs-title,.hljs-nginx .hljs-title,.hljs-subst,.hljs-tag .hljs-title{color:#f8f8f2}.hljs-meta{color:#75715e}.hljs-keyword{color:#f92672}.hljs-function{color:#89e229}.hljs-function .hljs-params{color:#fff}.hljs-literal,.hljs-number{color:#ae81ff}.hljs-string{color:#e6db74}.hljs-comment{color:#75715e}.hljs-type{color:#66d8ee} -\ No newline at end of file -diff --git a/themes/daux/css/theme-navy.min.css b/themes/daux/css/theme-navy.min.css -index d119372..c050c76 100644 ---- a/themes/daux/css/theme-navy.min.css -+++ b/themes/daux/css/theme-navy.min.css -@@ -2,4 +2,4 @@ - * DAUX.IO - * http://daux.io/ - * MIT License -- */.roboto-slab.light{font-weight:100}.roboto-slab.book,.roboto-slab.light{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif}.roboto-slab.book{font-weight:300}.roboto-slab.regular{font-weight:400}.roboto-slab.bold,.roboto-slab.regular{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif}.roboto-slab.bold{font-weight:700}h1,h2,h3,h4,h5,h6{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:300}h1 i{font-size:26px}pre{padding:0}.homepage-hero{padding-top:60px!important;background-color:#7795b4;box-shadow:none;border-radius:0;border:none;color:#13132a;overflow:hidden;padding-bottom:0;margin-bottom:0}.homepage-hero .text-center{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:700;margin:10px 0}.homepage-hero h2{margin:20px 0}.hero-buttons.container-fluid{padding:20px 0;background-color:#c5c5cb}.hero-buttons.container-fluid .btn-hero.btn{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:700;padding:20px 30px;background-image:none;-webkit-filter:none;filter:none;box-shadow:none;border-radius:0;text-shadow:none;border:none;opacity:.8;filter:alpha(opacity=80);margin:0 10px;text-transform:uppercase;border:5px solid #13132a}@media (max-width:768px){.hero-buttons.container-fluid .btn-hero.btn{display:block;margin-bottom:10px}}.hero-buttons.container-fluid .btn-hero.btn:hover{opacity:1;filter:alpha(opacity=100)}.hero-buttons.container-fluid .btn-hero.btn.btn-secondary{background-color:#c5c5cb;color:#13132a}.hero-buttons.container-fluid .btn-hero.btn.btn-primary{background-color:#13132a;color:#f5f5f6}.homepage-content.container-fluid{background-color:#fff;padding:40px 0}.homepage-content.container-fluid .lead{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:400}.homepage-content.container-fluid ol,.homepage-content.container-fluid ul{padding:20px 0;margin:0 0 10px}.homepage-content.container-fluid ol li,.homepage-content.container-fluid ul li{list-style:none;padding-bottom:5px}.homepage-content.container-fluid ol li:before,.homepage-content.container-fluid ul li:before{content:'';width:0;height:0;border:3px solid transparent;border-left:3px solid #7795b4;float:left;display:block;margin:6px}@media (max-width:768px){.homepage-content.container-fluid{padding:40px 20px}}.homepage-footer.container-fluid{background-color:#13132a;box-shadow:none;border-radius:0;color:light;border:none}@media (max-width:768px){.homepage-footer.container-fluid{padding:0 20px}}.homepage-footer.container-fluid .footer-nav{margin:40px 0}.homepage-footer.container-fluid .footer-nav li a{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:700;font-size:16px;line-height:32px}.homepage-footer.container-fluid .footer-nav li a:hover{color:#7795b4;text-decoration:underline}.homepage-footer.container-fluid .twitter{margin-top:20px}.homepage-footer.container-fluid .twitter:first-child{margin-top:40px}body,html{height:100%;background-color:#fff;color:#2d2d2d}.columns .left-column{background-color:#f5f5f6}.columns .right-column .content-page{padding:10px;background-color:#fff}.container-fluid .navbar-static-top{margin-left:-15px;margin-right:-15px}.responsive-collapse{padding:10px 15px;display:block;background-color:#e7e7e9;border-bottom:1px solid #e7e7e9}.sub-nav-collapse{display:none}.article-tree,.content-area{padding:0}@media screen and (min-width:768px){body{background-color:#7795b4}.navbar-static-top{position:fixed;z-index:1030;width:100%}.responsive-collapse{display:none}.sub-nav-collapse{display:block!important}.container-fluid.fluid-height{height:100%}.article-tree,.content-area{overflow:auto;height:100%}.columns{height:100%;padding-top:50px}.columns .left-column{border-right:1px solid #e7e7e9;overflow-x:hidden}.columns .right-column .content-page{padding:20px;min-height:100%}}@media only screen and (max-width:800px){table,tbody,td,th,thead,tr{display:block;border:none}thead tr{position:absolute;top:-9999px;left:-9999px}tr{margin-bottom:10px;border-bottom:2px solid #ccc}tr td,tr th{border:1px solid #ccc;border-bottom:none}td{border:none;border-bottom:1px solid #eee;position:relative;padding-left:50%!important;white-space:normal}td,td:before{text-align:left}td:before{position:absolute;top:6px;left:6px;width:45%;padding-right:10px;white-space:nowrap;font-weight:700;content:attr(data-title)}}@media print{.content-area{width:100%!important}h1 a[href]:after{font-size:50%}}a{color:#7795b4}.btn{display:inline-block}.btn.btn-sidebar{padding:7px 10px;background-image:none;-webkit-filter:none;filter:none;box-shadow:none;background-color:#c5c5cb;border:none}.btn.btn-sidebar .icon-bar{display:block;width:18px;height:2px;margin-top:2px;margin-bottom:3px}.btn.btn-sidebar .icon-bar,.btn.btn-sidebar:hover{background-color:#13132a;box-shadow:none}.btn.btn-sidebar:hover .icon-bar{background-color:#7795b4;box-shadow:none}code{color:#7795b4}.navbar{box-shadow:0 1px 5px rgba(0,0,0,.25);background-color:#13132a;margin-bottom:0}.navbar .container,.navbar .container-fluid{background-image:none;-webkit-filter:none;filter:none;border-bottom:none;padding:0 20px}.navbar .container-fluid .brand,.navbar .container .brand{color:#7795b4;text-shadow:none;font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:700}.navbar .container-fluid .navbar-text,.navbar .container-fluid .navbar-text a,.navbar .container .navbar-text,.navbar .container .navbar-text a{color:#7795b4}.code-buttons-text{font-size:12px;line-height:1.5;padding:6px 10px 6px 0;display:inline-block;vertical-align:middle}.nav.nav-list{padding-left:0;padding-right:0}.nav.nav-list li a{margin:0;padding:6px 15px 6px 20px;font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:400;color:#13132a;font-size:15px;text-shadow:none;border-color:#e7e7e9}.nav.nav-list li a .arrow{display:inline-block;position:relative;width:16px;margin-left:-16px}.nav.nav-list li a .arrow:before{position:absolute;display:block;content:"";margin:-.25em 0 0 -.4em;left:50%;top:50%;width:.5em;height:.5em;border-right:.15em solid #13132a;border-top:.15em solid #13132a;-webkit-transform:rotate(45deg);transform:rotate(45deg);-webkit-transition-duration:.3s;transition-duration:.3s}.nav.nav-list li a:hover{color:#13132a;text-shadow:none;background-color:#c5c5cb}.nav.nav-list li.active a{background-color:#c5c5cb}.nav.nav-list li.open>ul{display:block}.nav.nav-list li.open>a,.nav.nav-list li.open>a:focus,.nav.nav-list li.open>a:hover{background-color:transparent}.nav.nav-list li.open>a>.arrow:before{margin-left:-.25em;-webkit-transform:rotate(135deg);transform:rotate(135deg)}.nav.nav-list li ul{display:none;margin-left:15px}.nav.nav-list li ul li a{font-weight:400;font-size:14px;font-family:Helvetica Neue,Helvetica,Arial,sans-serif;line-height:20px;margin:0;margin-left:-15px;padding:3px 30px;border:none;color:#2d2d2d;opacity:.7;filter:alpha(opacity=70)}.nav.nav-list li ul li a:hover{opacity:1;filter:alpha(opacity=100);background-color:transparent}.nav.nav-list li ul li.active a{color:#13132a}.page-header{margin:10px 0;padding:0}.page-header h1{margin-top:0}.page-header sub-heading{padding:0,0,20px}pre{border:none;background-color:#7795b4;border-radius:0;padding:10px;margin-left:-20px;padding-left:30px;margin-right:-20px;padding-right:30px}pre code{background:transparent;border:none}@media (min-width:1150px){.float-view .content-page{height:100%;overflow:auto;padding:0!important;background-color:transparent!important;position:relative}.float-view .content-page article{width:100%;min-height:100%;overflow:auto;position:relative;z-index:1}.float-view .content-page article:before{content:"";width:50%;min-height:100%;overflow:auto;background-color:#fff;display:block;margin:0;position:absolute;z-index:-1}.float-view .content-page table{float:left;clear:left;width:47%;margin-left:1.5%;margin-right:1.5%;background-color:#fff;white-space:normal}.float-view .content-page table code,.float-view .content-page table pre{white-space:normal}.float-view .content-page .page-header{padding:0}.float-view .content-page .page-header,.float-view .content-page blockquote,.float-view .content-page dl,.float-view .content-page h2,.float-view .content-page h3,.float-view .content-page h4,.float-view .content-page h5,.float-view .content-page h6,.float-view .content-page hr,.float-view .content-page ol,.float-view .content-page p,.float-view .content-page ul{float:left;clear:left;width:47%;margin-left:1.5%;margin-right:1.5%;background-color:#fff}.float-view .content-page .page-header:before,.float-view .content-page blockquote:before,.float-view .content-page dl:before,.float-view .content-page h2:before,.float-view .content-page h3:before,.float-view .content-page h4:before,.float-view .content-page h5:before,.float-view .content-page h6:before,.float-view .content-page hr:before,.float-view .content-page ol:before,.float-view .content-page p:before,.float-view .content-page ul:before{width:100%;height:10px;display:block;clear:both}.float-view .content-page .page-header dl,.float-view .content-page .page-header h2,.float-view .content-page .page-header h3,.float-view .content-page .page-header h4,.float-view .content-page .page-header h5,.float-view .content-page .page-header h6,.float-view .content-page .page-header hr,.float-view .content-page .page-header ol,.float-view .content-page .page-header p,.float-view .content-page .page-header pre,.float-view .content-page .page-header ul,.float-view .content-page blockquote dl,.float-view .content-page blockquote h2,.float-view .content-page blockquote h3,.float-view .content-page blockquote h4,.float-view .content-page blockquote h5,.float-view .content-page blockquote h6,.float-view .content-page blockquote hr,.float-view .content-page blockquote ol,.float-view .content-page blockquote p,.float-view .content-page blockquote pre,.float-view .content-page blockquote ul,.float-view .content-page dl dl,.float-view .content-page dl h2,.float-view .content-page dl h3,.float-view .content-page dl h4,.float-view .content-page dl h5,.float-view .content-page dl h6,.float-view .content-page dl hr,.float-view .content-page dl ol,.float-view .content-page dl p,.float-view .content-page dl pre,.float-view .content-page dl ul,.float-view .content-page h2 dl,.float-view .content-page h2 h2,.float-view .content-page h2 h3,.float-view .content-page h2 h4,.float-view .content-page h2 h5,.float-view .content-page h2 h6,.float-view .content-page h2 hr,.float-view .content-page h2 ol,.float-view .content-page h2 p,.float-view .content-page h2 pre,.float-view .content-page h2 ul,.float-view .content-page h3 dl,.float-view .content-page h3 h2,.float-view .content-page h3 h3,.float-view .content-page h3 h4,.float-view .content-page h3 h5,.float-view .content-page h3 h6,.float-view .content-page h3 hr,.float-view .content-page h3 ol,.float-view .content-page h3 p,.float-view .content-page h3 pre,.float-view .content-page h3 ul,.float-view .content-page h4 dl,.float-view .content-page h4 h2,.float-view .content-page h4 h3,.float-view .content-page h4 h4,.float-view .content-page h4 h5,.float-view .content-page h4 h6,.float-view .content-page h4 hr,.float-view .content-page h4 ol,.float-view .content-page h4 p,.float-view .content-page h4 pre,.float-view .content-page h4 ul,.float-view .content-page h5 dl,.float-view .content-page h5 h2,.float-view .content-page h5 h3,.float-view .content-page h5 h4,.float-view .content-page h5 h5,.float-view .content-page h5 h6,.float-view .content-page h5 hr,.float-view .content-page h5 ol,.float-view .content-page h5 p,.float-view .content-page h5 pre,.float-view .content-page h5 ul,.float-view .content-page h6 dl,.float-view .content-page h6 h2,.float-view .content-page h6 h3,.float-view .content-page h6 h4,.float-view .content-page h6 h5,.float-view .content-page h6 h6,.float-view .content-page h6 hr,.float-view .content-page h6 ol,.float-view .content-page h6 p,.float-view .content-page h6 pre,.float-view .content-page h6 ul,.float-view .content-page hr dl,.float-view .content-page hr h2,.float-view .content-page hr h3,.float-view .content-page hr h4,.float-view .content-page hr h5,.float-view .content-page hr h6,.float-view .content-page hr hr,.float-view .content-page hr ol,.float-view .content-page hr p,.float-view .content-page hr pre,.float-view .content-page hr ul,.float-view .content-page ol dl,.float-view .content-page ol h2,.float-view .content-page ol h3,.float-view .content-page ol h4,.float-view .content-page ol h5,.float-view .content-page ol h6,.float-view .content-page ol hr,.float-view .content-page ol ol,.float-view .content-page ol p,.float-view .content-page ol pre,.float-view .content-page ol ul,.float-view .content-page p dl,.float-view .content-page p h2,.float-view .content-page p h3,.float-view .content-page p h4,.float-view .content-page p h5,.float-view .content-page p h6,.float-view .content-page p hr,.float-view .content-page p ol,.float-view .content-page p p,.float-view .content-page p pre,.float-view .content-page p ul,.float-view .content-page ul dl,.float-view .content-page ul h2,.float-view .content-page ul h3,.float-view .content-page ul h4,.float-view .content-page ul h5,.float-view .content-page ul h6,.float-view .content-page ul hr,.float-view .content-page ul ol,.float-view .content-page ul p,.float-view .content-page ul pre,.float-view .content-page ul ul{float:none;display:block}.float-view .content-page hr{border-color:#ddd}.float-view .content-page blockquote p,.float-view .content-page blockquote pre,.float-view .content-page li p,.float-view .content-page li pre{width:100%}.float-view .content-page ol li,.float-view .content-page ul li{margin-left:30px}.float-view .content-page pre{float:left;clear:right;width:47%;border:none;border-left:10px solid #fff;margin:0 0 10px;padding:0 0 0 10px}}table{width:100%;border-bottom:1px solid #e7e7e9;margin-bottom:10px}table tr td,table tr th{padding:8px;line-height:20px;vertical-align:top;border-top:1px solid #e7e7e9;border-left:1px solid #e7e7e9;border-color:#e7e7e9!important}table tr td:last-child,table tr th:last-child{border-right:1px solid #e7e7e9}.footer{position:fixed;bottom:0;left:0;padding:15px}#github-ribbon{position:absolute;top:50px;right:0;z-index:200}.sidebar-links{padding:20px}.sidebar-links a{font-size:13px;font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:400;color:#7795b4;line-height:28px}.sidebar-links .twitter hr{border-bottom:none;margin-left:-20px;margin-right:-20px}.search{position:relative}.search__field{padding-right:30px}.search__icon{position:absolute;right:12px;top:10px}.TableOfContents{font-size:16px;padding-left:30px;border-left:6px solid #efefef}.TableOfContents p{margin-bottom:0}.TableOfContents .TableOfContents{border-left-width:0;padding-left:20px}.hljs{display:block;padding:.5em}.hljs,.hljs-clojure .hljs-built_in,.hljs-lisp .hljs-title,.hljs-nginx .hljs-title,.hljs-subst,.hljs-tag .hljs-title{color:#13132a}.hljs-addition,.hljs-aggregate,.hljs-apache .hljs-cbracket,.hljs-apache .hljs-tag,.hljs-bash .hljs-variable,.hljs-constant,.hljs-django .hljs-variable,.hljs-erlang_repl .hljs-function_or_atom,.hljs-flow,.hljs-markdown .hljs-header,.hljs-parent,.hljs-preprocessor,.hljs-ruby .hljs-symbol,.hljs-ruby .hljs-symbol .hljs-string,.hljs-rules .hljs-value,.hljs-rules .hljs-value .hljs-number,.hljs-smalltalk .hljs-class,.hljs-stream,.hljs-string,.hljs-tag .hljs-value,.hljs-template_tag,.hljs-tex .hljs-command,.hljs-tex .hljs-special,.hljs-title{color:#000}.hljs-annotation,.hljs-chunk,.hljs-comment,.hljs-diff .hljs-header,.hljs-markdown .hljs-blockquote,.hljs-template_comment{color:#505050}.hljs-change,.hljs-date,.hljs-go .hljs-constant,.hljs-literal,.hljs-markdown .hljs-bullet,.hljs-markdown .hljs-link_url,.hljs-number,.hljs-regexp,.hljs-smalltalk .hljs-char,.hljs-smalltalk .hljs-symbol{color:#09559b}.hljs-apache .hljs-sqbracket,.hljs-array,.hljs-attr_selector,.hljs-clojure .hljs-attribute,.hljs-coffeescript .hljs-property,.hljs-decorator,.hljs-deletion,.hljs-doctype,.hljs-envvar,.hljs-erlang_repl .hljs-reserved,.hljs-filter .hljs-argument,.hljs-important,.hljs-javadoc,.hljs-label,.hljs-localvars,.hljs-markdown .hljs-link_label,.hljs-nginx .hljs-built_in,.hljs-pi,.hljs-prompt,.hljs-pseudo,.hljs-ruby .hljs-string,.hljs-shebang,.hljs-tex .hljs-formula,.hljs-vhdl .hljs-attribute{color:#001775}.hljs-aggregate,.hljs-apache .hljs-tag,.hljs-bash .hljs-variable,.hljs-built_in,.hljs-css .hljs-tag,.hljs-go .hljs-typename,.hljs-id,.hljs-javadoctag,.hljs-keyword,.hljs-markdown .hljs-strong,.hljs-phpdoc,.hljs-request,.hljs-smalltalk .hljs-class,.hljs-status,.hljs-tex .hljs-command,.hljs-title,.hljs-winutils,.hljs-yardoctag{font-weight:700}.hljs-markdown .hljs-emphasis{font-style:italic}.hljs-nginx .hljs-built_in{font-weight:400}.hljs-coffeescript .hljs-javascript,.hljs-javascript .hljs-xml,.hljs-tex .hljs-formula,.hljs-xml .hljs-cdata,.hljs-xml .hljs-css,.hljs-xml .hljs-javascript,.hljs-xml .hljs-vbscript{opacity:.5} -\ No newline at end of file -+ */.roboto-slab.light{font-weight:100}.roboto-slab.book,.roboto-slab.light{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif}.roboto-slab.book{font-weight:300}.roboto-slab.regular{font-weight:400}.roboto-slab.bold,.roboto-slab.regular{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif}.roboto-slab.bold{font-weight:700}h1,h2,h3,h4,h5,h6{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:300}h1 i{font-size:26px}pre{padding:0}.homepage-hero{padding-top:60px!important;background-color:#7795b4;box-shadow:none;border-radius:0;border:none;color:#13132a;overflow:hidden;padding-bottom:0;margin-bottom:0}.homepage-hero .text-center{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:700;margin:10px 0}.homepage-hero h2{margin:20px 0}.hero-buttons.container-fluid{padding:20px 0;background-color:#c5c5cb}.hero-buttons.container-fluid .btn-hero.btn{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:700;padding:20px 30px;background-image:none;-webkit-filter:none;filter:none;box-shadow:none;border-radius:0;text-shadow:none;border:none;opacity:.8;filter:alpha(opacity=80);margin:0 10px;text-transform:uppercase;border:5px solid #13132a}@media (max-width:768px){.hero-buttons.container-fluid .btn-hero.btn{display:block;margin-bottom:10px}}.hero-buttons.container-fluid .btn-hero.btn:hover{opacity:1;filter:alpha(opacity=100)}.hero-buttons.container-fluid .btn-hero.btn.btn-secondary{background-color:#c5c5cb;color:#13132a}.hero-buttons.container-fluid .btn-hero.btn.btn-primary{background-color:#13132a;color:#f5f5f6}.homepage-content.container-fluid{background-color:#fff;padding:40px 0}.homepage-content.container-fluid .lead{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:400}.homepage-content.container-fluid ol,.homepage-content.container-fluid ul{padding:20px 0;margin:0 0 10px}.homepage-content.container-fluid ol li,.homepage-content.container-fluid ul li{list-style:none;padding-bottom:5px}.homepage-content.container-fluid ol li:before,.homepage-content.container-fluid ul li:before{content:'';width:0;height:0;border:3px solid transparent;border-left:3px solid #7795b4;float:left;display:block;margin:6px}@media (max-width:768px){.homepage-content.container-fluid{padding:40px 20px}}.homepage-footer.container-fluid{background-color:#13132a;box-shadow:none;border-radius:0;color:light;border:none}@media (max-width:768px){.homepage-footer.container-fluid{padding:0 20px}}.homepage-footer.container-fluid .footer-nav{margin:40px 0}.homepage-footer.container-fluid .footer-nav li a{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:700;font-size:16px;line-height:32px}.homepage-footer.container-fluid .footer-nav li a:hover{color:#7795b4;border-bottom-style:solid;border-bottom-color:#7795b4;text-decoration:none}.homepage-footer.container-fluid .twitter{margin-top:20px}.homepage-footer.container-fluid .twitter:first-child{margin-top:40px}body,html{height:100%;background-color:#fff;color:#2d2d2d}.content{background:#f0f0f0}.columns .left-column{background-color:#f5f5f6}.columns .right-column .content-page{padding:10px;background-color:#fff}.container-fluid .navbar-static-top{margin-left:-15px;margin-right:-15px}.responsive-collapse{padding:10px 15px;display:block;background-color:#e7e7e9;border-bottom:1px solid #e7e7e9}.sub-nav-collapse{display:none}.article-tree,.content-area{padding:0}@media screen and (min-width:768px){body{background-color:#fff}.navbar-static-top{position:fixed;z-index:1030;width:100%}.responsive-collapse{display:none}.sub-nav-collapse{display:block!important}.container-fluid.fluid-height{height:100%}.article-tree,.content-area{overflow:auto;height:100%}.columns{height:100%;padding-top:0}.columns .left-column{max-width:400px;border-right:1px solid #e7e7e9;overflow-x:hidden}.columns .right-column .content-page{margin:auto;position:relative;max-width:800px;padding:20px;min-height:100%}}@media screen and (min-width:1200px){.columns .right-column{width:calc(100% - 400px)}}@media only screen and (max-width:800px){table,tbody,td,th,thead,tr{display:block;border:none}thead tr{position:absolute;top:-9999px;left:-9999px}tr{margin-bottom:10px;border-bottom:2px solid #ccc}tr td,tr th{border:1px solid #ccc;border-bottom:none}td{border:none;border-bottom:1px solid #eee;position:relative;padding-left:50%!important;white-space:normal}td,td:before{text-align:left}td:before{position:absolute;top:6px;left:6px;width:45%;padding-right:10px;white-space:nowrap;font-weight:700;content:attr(data-title)}}@media print{.content-area{width:100%!important}h1 a[href]:after{font-size:50%}}body{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif}a{color:#7795b4;border-bottom:1px dotted #7795b4}a:hover{border-bottom-style:solid;border-bottom-color:#13132a;text-decoration:none}a>code{border-bottom:1px dotted #7795b4}a>code:hover{border-bottom-style:solid}a.folder{border-bottom:none}.btn{display:inline-block}.btn.btn-sidebar{padding:7px 10px;background-image:none;-webkit-filter:none;filter:none;box-shadow:none;background-color:#c5c5cb;border:none}.btn.btn-sidebar .icon-bar{display:block;width:18px;height:2px;margin-top:2px;margin-bottom:3px}.btn.btn-sidebar .icon-bar,.btn.btn-sidebar:hover{background-color:#13132a;box-shadow:none}.btn.btn-sidebar:hover .icon-bar{background-color:#7795b4;box-shadow:none}code{color:#666;border:1px solid #ddd}a code{color:#7795b4}.nav-logo{background:#a41e22;padding:20px;color:#fff;font-size:40px}.navbar{box-shadow:0 1px 5px rgba(0,0,0,.25);background-color:#13132a;margin-bottom:0}.navbar .container,.navbar .container-fluid{background-image:none;-webkit-filter:none;filter:none;border-bottom:none;padding:0 20px}.navbar .container-fluid .brand,.navbar .container .brand{color:#7795b4;text-shadow:none;font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:700;border-bottom:none}.navbar .container-fluid .navbar-text,.navbar .container-fluid .navbar-text a,.navbar .container .navbar-text,.navbar .container .navbar-text a{color:#7795b4}.code-buttons-text{font-size:12px;line-height:1.5;padding:6px 10px 6px 0;display:inline-block;vertical-align:middle}.nav{background:#272525}.nav.nav-list{padding-left:0;padding-right:0}.nav.nav-list li a{margin:0;padding:6px 15px 6px 20px;font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:400;color:#b3b3b3;font-size:15px;text-shadow:none;border-color:#e7e7e9;border-bottom:none}.nav.nav-list li a .arrow{display:inline-block;position:relative;width:16px;margin-left:-16px}.nav.nav-list li a .arrow:before{position:absolute;display:block;content:"";margin:-.25em 0 0 -.4em;left:50%;top:50%;width:.5em;height:.5em;border-right:.15em solid #13132a;border-top:.15em solid #13132a;-webkit-transform:rotate(45deg);transform:rotate(45deg);-webkit-transition-duration:.3s;transition-duration:.3s}.nav.nav-list li a:hover{color:#13132a;text-shadow:none;background-color:#c5c5cb;border-bottom:none}.nav.nav-list li.active a{background-color:#c5c5cb}.nav.nav-list li.open>ul{display:block}.nav.nav-list li.open>a,.nav.nav-list li.open>a:focus,.nav.nav-list li.open>a:hover{background-color:transparent}.nav.nav-list li.open>a>.arrow:before{margin-left:-.25em;-webkit-transform:rotate(135deg);transform:rotate(135deg)}.nav.nav-list li ul{display:none;margin-left:15px}.nav.nav-list li ul li a{font-weight:400;font-size:14px;font-family:Helvetica Neue,Helvetica,Arial,sans-serif;line-height:20px;margin:0;margin-left:-15px;padding:3px 30px;border:none;color:#b3b3b3;opacity:.7;filter:alpha(opacity=70)}.nav.nav-list li ul li a:hover{opacity:1;filter:alpha(opacity=100);background-color:transparent}.nav.nav-list li ul li.active a{color:#b3b3b3}.page-header{margin:10px 0;padding:0}.page-header h1{margin-top:0}.page-header h1 a{border-bottom:none}.page-header sub-heading{padding:0,0,20px}pre{border:none;background-color:#343131;border-radius:0;padding:10px;margin-left:-20px;padding-left:30px;margin-right:-20px;padding-right:30px}pre code{background:transparent;border:none;color:#fff}@media (min-width:1150px){.float-view .content-page{height:100%;overflow:auto;padding:0!important;background-color:transparent!important;position:relative}.float-view .content-page article{width:100%;min-height:100%;overflow:auto;position:relative;z-index:1}.float-view .content-page article:before{content:"";width:50%;min-height:100%;overflow:auto;background-color:#fff;display:block;margin:0;position:absolute;z-index:-1}.float-view .content-page table{float:left;clear:left;width:47%;margin-left:1.5%;margin-right:1.5%;background-color:#fff;white-space:normal}.float-view .content-page table code,.float-view .content-page table pre{white-space:normal}.float-view .content-page .page-header{padding:0}.float-view .content-page .page-header,.float-view .content-page blockquote,.float-view .content-page dl,.float-view .content-page h2,.float-view .content-page h3,.float-view .content-page h4,.float-view .content-page h5,.float-view .content-page h6,.float-view .content-page hr,.float-view .content-page ol,.float-view .content-page p,.float-view .content-page ul{float:left;clear:left;width:47%;margin-left:1.5%;margin-right:1.5%;background-color:#fff}.float-view .content-page .page-header:before,.float-view .content-page blockquote:before,.float-view .content-page dl:before,.float-view .content-page h2:before,.float-view .content-page h3:before,.float-view .content-page h4:before,.float-view .content-page h5:before,.float-view .content-page h6:before,.float-view .content-page hr:before,.float-view .content-page ol:before,.float-view .content-page p:before,.float-view .content-page ul:before{width:100%;height:10px;display:block;clear:both}.float-view .content-page .page-header dl,.float-view .content-page .page-header h2,.float-view .content-page .page-header h3,.float-view .content-page .page-header h4,.float-view .content-page .page-header h5,.float-view .content-page .page-header h6,.float-view .content-page .page-header hr,.float-view .content-page .page-header ol,.float-view .content-page .page-header p,.float-view .content-page .page-header pre,.float-view .content-page .page-header ul,.float-view .content-page blockquote dl,.float-view .content-page blockquote h2,.float-view .content-page blockquote h3,.float-view .content-page blockquote h4,.float-view .content-page blockquote h5,.float-view .content-page blockquote h6,.float-view .content-page blockquote hr,.float-view .content-page blockquote ol,.float-view .content-page blockquote p,.float-view .content-page blockquote pre,.float-view .content-page blockquote ul,.float-view .content-page dl dl,.float-view .content-page dl h2,.float-view .content-page dl h3,.float-view .content-page dl h4,.float-view .content-page dl h5,.float-view .content-page dl h6,.float-view .content-page dl hr,.float-view .content-page dl ol,.float-view .content-page dl p,.float-view .content-page dl pre,.float-view .content-page dl ul,.float-view .content-page h2 dl,.float-view .content-page h2 h2,.float-view .content-page h2 h3,.float-view .content-page h2 h4,.float-view .content-page h2 h5,.float-view .content-page h2 h6,.float-view .content-page h2 hr,.float-view .content-page h2 ol,.float-view .content-page h2 p,.float-view .content-page h2 pre,.float-view .content-page h2 ul,.float-view .content-page h3 dl,.float-view .content-page h3 h2,.float-view .content-page h3 h3,.float-view .content-page h3 h4,.float-view .content-page h3 h5,.float-view .content-page h3 h6,.float-view .content-page h3 hr,.float-view .content-page h3 ol,.float-view .content-page h3 p,.float-view .content-page h3 pre,.float-view .content-page h3 ul,.float-view .content-page h4 dl,.float-view .content-page h4 h2,.float-view .content-page h4 h3,.float-view .content-page h4 h4,.float-view .content-page h4 h5,.float-view .content-page h4 h6,.float-view .content-page h4 hr,.float-view .content-page h4 ol,.float-view .content-page h4 p,.float-view .content-page h4 pre,.float-view .content-page h4 ul,.float-view .content-page h5 dl,.float-view .content-page h5 h2,.float-view .content-page h5 h3,.float-view .content-page h5 h4,.float-view .content-page h5 h5,.float-view .content-page h5 h6,.float-view .content-page h5 hr,.float-view .content-page h5 ol,.float-view .content-page h5 p,.float-view .content-page h5 pre,.float-view .content-page h5 ul,.float-view .content-page h6 dl,.float-view .content-page h6 h2,.float-view .content-page h6 h3,.float-view .content-page h6 h4,.float-view .content-page h6 h5,.float-view .content-page h6 h6,.float-view .content-page h6 hr,.float-view .content-page h6 ol,.float-view .content-page h6 p,.float-view .content-page h6 pre,.float-view .content-page h6 ul,.float-view .content-page hr dl,.float-view .content-page hr h2,.float-view .content-page hr h3,.float-view .content-page hr h4,.float-view .content-page hr h5,.float-view .content-page hr h6,.float-view .content-page hr hr,.float-view .content-page hr ol,.float-view .content-page hr p,.float-view .content-page hr pre,.float-view .content-page hr ul,.float-view .content-page ol dl,.float-view .content-page ol h2,.float-view .content-page ol h3,.float-view .content-page ol h4,.float-view .content-page ol h5,.float-view .content-page ol h6,.float-view .content-page ol hr,.float-view .content-page ol ol,.float-view .content-page ol p,.float-view .content-page ol pre,.float-view .content-page ol ul,.float-view .content-page p dl,.float-view .content-page p h2,.float-view .content-page p h3,.float-view .content-page p h4,.float-view .content-page p h5,.float-view .content-page p h6,.float-view .content-page p hr,.float-view .content-page p ol,.float-view .content-page p p,.float-view .content-page p pre,.float-view .content-page p ul,.float-view .content-page ul dl,.float-view .content-page ul h2,.float-view .content-page ul h3,.float-view .content-page ul h4,.float-view .content-page ul h5,.float-view .content-page ul h6,.float-view .content-page ul hr,.float-view .content-page ul ol,.float-view .content-page ul p,.float-view .content-page ul pre,.float-view .content-page ul ul{float:none;display:block}.float-view .content-page hr{border-color:#ddd}.float-view .content-page blockquote p,.float-view .content-page blockquote pre,.float-view .content-page li p,.float-view .content-page li pre{width:100%}.float-view .content-page ol li,.float-view .content-page ul li{margin-left:30px}.float-view .content-page pre{float:left;clear:right;width:47%;border:none;border-left:10px solid #fff;margin:0 0 10px;padding:0 0 0 10px}}table{width:100%;border-bottom:1px solid #e7e7e9;margin-bottom:10px}table tr td,table tr th{padding:8px;line-height:20px;vertical-align:top;border-top:1px solid #e7e7e9;border-left:1px solid #e7e7e9;border-color:#e7e7e9!important}table tr td:last-child,table tr th:last-child{border-right:1px solid #e7e7e9}.footer{position:fixed;bottom:0;left:0;padding:15px}#github-ribbon{position:absolute;top:50px;right:0;z-index:200}.sidebar-links{padding:20px}.sidebar-links a{font-size:13px;font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:400;color:#fff;line-height:28px}.sidebar-links a,.sidebar-links a:hover{border-bottom-color:#fff}.sidebar-links .twitter hr{border-bottom:none;margin-left:-20px;margin-right:-20px}.search{position:relative}.search__field{padding-right:30px}.search__icon{position:absolute;right:12px;top:10px}.TableOfContents{font-size:16px;padding-left:30px;border-left:6px solid #efefef}.TableOfContents p{margin-bottom:0}.TableOfContents .TableOfContents{border-left-width:0;padding-left:20px}.hljs{display:block;padding:.5em}.hljs,.hljs-clojure .hljs-built_in,.hljs-lisp .hljs-title,.hljs-nginx .hljs-title,.hljs-subst,.hljs-tag .hljs-title{color:#f8f8f2}.hljs-meta{color:#75715e}.hljs-keyword{color:#f92672}.hljs-function{color:#89e229}.hljs-function .hljs-params{color:#fff}.hljs-literal,.hljs-number{color:#ae81ff}.hljs-string{color:#e6db74}.hljs-comment{color:#75715e}.hljs-type{color:#66d8ee} -\ No newline at end of file -diff --git a/themes/daux/css/theme-red.min.css b/themes/daux/css/theme-red.min.css -index d4a8ca0..c9f8589 100644 ---- a/themes/daux/css/theme-red.min.css -+++ b/themes/daux/css/theme-red.min.css -@@ -2,4 +2,4 @@ - * DAUX.IO - * http://daux.io/ - * MIT License -- */.roboto-slab.light{font-weight:100}.roboto-slab.book,.roboto-slab.light{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif}.roboto-slab.book{font-weight:300}.roboto-slab.regular{font-weight:400}.roboto-slab.bold,.roboto-slab.regular{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif}.roboto-slab.bold{font-weight:700}h1,h2,h3,h4,h5,h6{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:300}h1 i{font-size:26px}pre{padding:0}.homepage-hero{padding-top:60px!important;background-color:#ecb5a1;box-shadow:none;border-radius:0;border:none;color:#c64641;overflow:hidden;padding-bottom:0;margin-bottom:0}.homepage-hero .text-center{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:700;margin:10px 0}.homepage-hero h2{margin:20px 0}.hero-buttons.container-fluid{padding:20px 0;background-color:#eee}.hero-buttons.container-fluid .btn-hero.btn{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:700;padding:20px 30px;background-image:none;-webkit-filter:none;filter:none;box-shadow:none;border-radius:0;text-shadow:none;border:none;opacity:.8;filter:alpha(opacity=80);margin:0 10px;text-transform:uppercase;border:5px solid #c64641}@media (max-width:768px){.hero-buttons.container-fluid .btn-hero.btn{display:block;margin-bottom:10px}}.hero-buttons.container-fluid .btn-hero.btn:hover{opacity:1;filter:alpha(opacity=100)}.hero-buttons.container-fluid .btn-hero.btn.btn-secondary{background-color:#eee;color:#c64641}.hero-buttons.container-fluid .btn-hero.btn.btn-primary{background-color:#c64641;color:#f7f7f7}.homepage-content.container-fluid{background-color:#fff;padding:40px 0}.homepage-content.container-fluid .lead{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:400}.homepage-content.container-fluid ol,.homepage-content.container-fluid ul{padding:20px 0;margin:0 0 10px}.homepage-content.container-fluid ol li,.homepage-content.container-fluid ul li{list-style:none;padding-bottom:5px}.homepage-content.container-fluid ol li:before,.homepage-content.container-fluid ul li:before{content:'';width:0;height:0;border:3px solid transparent;border-left:3px solid #ecb5a1;float:left;display:block;margin:6px}@media (max-width:768px){.homepage-content.container-fluid{padding:40px 20px}}.homepage-footer.container-fluid{background-color:#c64641;box-shadow:none;border-radius:0;color:light;border:none}@media (max-width:768px){.homepage-footer.container-fluid{padding:0 20px}}.homepage-footer.container-fluid .footer-nav{margin:40px 0}.homepage-footer.container-fluid .footer-nav li a{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:700;font-size:16px;line-height:32px}.homepage-footer.container-fluid .footer-nav li a:hover{color:#ecb5a1;text-decoration:underline}.homepage-footer.container-fluid .twitter{margin-top:20px}.homepage-footer.container-fluid .twitter:first-child{margin-top:40px}body,html{height:100%;background-color:#fff;color:#2d2d2d}.columns .left-column{background-color:#f7f7f7}.columns .right-column .content-page{padding:10px;background-color:#fff}.container-fluid .navbar-static-top{margin-left:-15px;margin-right:-15px}.responsive-collapse{padding:10px 15px;display:block;background-color:#eee;border-bottom:1px solid #eee}.sub-nav-collapse{display:none}.article-tree,.content-area{padding:0}@media screen and (min-width:768px){body{background-color:#ecb5a1}.navbar-static-top{position:fixed;z-index:1030;width:100%}.responsive-collapse{display:none}.sub-nav-collapse{display:block!important}.container-fluid.fluid-height{height:100%}.article-tree,.content-area{overflow:auto;height:100%}.columns{height:100%;padding-top:50px}.columns .left-column{border-right:1px solid #eee;overflow-x:hidden}.columns .right-column .content-page{padding:20px;min-height:100%}}@media only screen and (max-width:800px){table,tbody,td,th,thead,tr{display:block;border:none}thead tr{position:absolute;top:-9999px;left:-9999px}tr{margin-bottom:10px;border-bottom:2px solid #ccc}tr td,tr th{border:1px solid #ccc;border-bottom:none}td{border:none;border-bottom:1px solid #eee;position:relative;padding-left:50%!important;white-space:normal}td,td:before{text-align:left}td:before{position:absolute;top:6px;left:6px;width:45%;padding-right:10px;white-space:nowrap;font-weight:700;content:attr(data-title)}}@media print{.content-area{width:100%!important}h1 a[href]:after{font-size:50%}}a{color:#ecb5a1}.btn{display:inline-block}.btn.btn-sidebar{padding:7px 10px;background-image:none;-webkit-filter:none;filter:none;box-shadow:none;background-color:#eee;border:none}.btn.btn-sidebar .icon-bar{display:block;width:18px;height:2px;margin-top:2px;margin-bottom:3px}.btn.btn-sidebar .icon-bar,.btn.btn-sidebar:hover{background-color:#c64641;box-shadow:none}.btn.btn-sidebar:hover .icon-bar{background-color:#ecb5a1;box-shadow:none}code{color:#ecb5a1}.navbar{box-shadow:0 1px 5px rgba(0,0,0,.25);background-color:#c64641;margin-bottom:0}.navbar .container,.navbar .container-fluid{background-image:none;-webkit-filter:none;filter:none;border-bottom:none;padding:0 20px}.navbar .container-fluid .brand,.navbar .container .brand{color:#ecb5a1;text-shadow:none;font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:700}.navbar .container-fluid .navbar-text,.navbar .container-fluid .navbar-text a,.navbar .container .navbar-text,.navbar .container .navbar-text a{color:#ecb5a1}.code-buttons-text{font-size:12px;line-height:1.5;padding:6px 10px 6px 0;display:inline-block;vertical-align:middle}.nav.nav-list{padding-left:0;padding-right:0}.nav.nav-list li a{margin:0;padding:6px 15px 6px 20px;font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:400;color:#c64641;font-size:15px;text-shadow:none;border-color:#eee}.nav.nav-list li a .arrow{display:inline-block;position:relative;width:16px;margin-left:-16px}.nav.nav-list li a .arrow:before{position:absolute;display:block;content:"";margin:-.25em 0 0 -.4em;left:50%;top:50%;width:.5em;height:.5em;border-right:.15em solid #c64641;border-top:.15em solid #c64641;-webkit-transform:rotate(45deg);transform:rotate(45deg);-webkit-transition-duration:.3s;transition-duration:.3s}.nav.nav-list li a:hover{color:#c64641;text-shadow:none;background-color:#eee}.nav.nav-list li.active a{background-color:#eee}.nav.nav-list li.open>ul{display:block}.nav.nav-list li.open>a,.nav.nav-list li.open>a:focus,.nav.nav-list li.open>a:hover{background-color:transparent}.nav.nav-list li.open>a>.arrow:before{margin-left:-.25em;-webkit-transform:rotate(135deg);transform:rotate(135deg)}.nav.nav-list li ul{display:none;margin-left:15px}.nav.nav-list li ul li a{font-weight:400;font-size:14px;font-family:Helvetica Neue,Helvetica,Arial,sans-serif;line-height:20px;margin:0;margin-left:-15px;padding:3px 30px;border:none;color:#2d2d2d;opacity:.7;filter:alpha(opacity=70)}.nav.nav-list li ul li a:hover{opacity:1;filter:alpha(opacity=100);background-color:transparent}.nav.nav-list li ul li.active a{color:#c64641}.page-header{margin:10px 0;padding:0}.page-header h1{margin-top:0}.page-header sub-heading{padding:0,0,20px}pre{border:none;background-color:#ecb5a1;border-radius:0;padding:10px;margin-left:-20px;padding-left:30px;margin-right:-20px;padding-right:30px}pre code{background:transparent;border:none}@media (min-width:1150px){.float-view .content-page{height:100%;overflow:auto;padding:0!important;background-color:transparent!important;position:relative}.float-view .content-page article{width:100%;min-height:100%;overflow:auto;position:relative;z-index:1}.float-view .content-page article:before{content:"";width:50%;min-height:100%;overflow:auto;background-color:#fff;display:block;margin:0;position:absolute;z-index:-1}.float-view .content-page table{float:left;clear:left;width:47%;margin-left:1.5%;margin-right:1.5%;background-color:#fff;white-space:normal}.float-view .content-page table code,.float-view .content-page table pre{white-space:normal}.float-view .content-page .page-header{padding:0}.float-view .content-page .page-header,.float-view .content-page blockquote,.float-view .content-page dl,.float-view .content-page h2,.float-view .content-page h3,.float-view .content-page h4,.float-view .content-page h5,.float-view .content-page h6,.float-view .content-page hr,.float-view .content-page ol,.float-view .content-page p,.float-view .content-page ul{float:left;clear:left;width:47%;margin-left:1.5%;margin-right:1.5%;background-color:#fff}.float-view .content-page .page-header:before,.float-view .content-page blockquote:before,.float-view .content-page dl:before,.float-view .content-page h2:before,.float-view .content-page h3:before,.float-view .content-page h4:before,.float-view .content-page h5:before,.float-view .content-page h6:before,.float-view .content-page hr:before,.float-view .content-page ol:before,.float-view .content-page p:before,.float-view .content-page ul:before{width:100%;height:10px;display:block;clear:both}.float-view .content-page .page-header dl,.float-view .content-page .page-header h2,.float-view .content-page .page-header h3,.float-view .content-page .page-header h4,.float-view .content-page .page-header h5,.float-view .content-page .page-header h6,.float-view .content-page .page-header hr,.float-view .content-page .page-header ol,.float-view .content-page .page-header p,.float-view .content-page .page-header pre,.float-view .content-page .page-header ul,.float-view .content-page blockquote dl,.float-view .content-page blockquote h2,.float-view .content-page blockquote h3,.float-view .content-page blockquote h4,.float-view .content-page blockquote h5,.float-view .content-page blockquote h6,.float-view .content-page blockquote hr,.float-view .content-page blockquote ol,.float-view .content-page blockquote p,.float-view .content-page blockquote pre,.float-view .content-page blockquote ul,.float-view .content-page dl dl,.float-view .content-page dl h2,.float-view .content-page dl h3,.float-view .content-page dl h4,.float-view .content-page dl h5,.float-view .content-page dl h6,.float-view .content-page dl hr,.float-view .content-page dl ol,.float-view .content-page dl p,.float-view .content-page dl pre,.float-view .content-page dl ul,.float-view .content-page h2 dl,.float-view .content-page h2 h2,.float-view .content-page h2 h3,.float-view .content-page h2 h4,.float-view .content-page h2 h5,.float-view .content-page h2 h6,.float-view .content-page h2 hr,.float-view .content-page h2 ol,.float-view .content-page h2 p,.float-view .content-page h2 pre,.float-view .content-page h2 ul,.float-view .content-page h3 dl,.float-view .content-page h3 h2,.float-view .content-page h3 h3,.float-view .content-page h3 h4,.float-view .content-page h3 h5,.float-view .content-page h3 h6,.float-view .content-page h3 hr,.float-view .content-page h3 ol,.float-view .content-page h3 p,.float-view .content-page h3 pre,.float-view .content-page h3 ul,.float-view .content-page h4 dl,.float-view .content-page h4 h2,.float-view .content-page h4 h3,.float-view .content-page h4 h4,.float-view .content-page h4 h5,.float-view .content-page h4 h6,.float-view .content-page h4 hr,.float-view .content-page h4 ol,.float-view .content-page h4 p,.float-view .content-page h4 pre,.float-view .content-page h4 ul,.float-view .content-page h5 dl,.float-view .content-page h5 h2,.float-view .content-page h5 h3,.float-view .content-page h5 h4,.float-view .content-page h5 h5,.float-view .content-page h5 h6,.float-view .content-page h5 hr,.float-view .content-page h5 ol,.float-view .content-page h5 p,.float-view .content-page h5 pre,.float-view .content-page h5 ul,.float-view .content-page h6 dl,.float-view .content-page h6 h2,.float-view .content-page h6 h3,.float-view .content-page h6 h4,.float-view .content-page h6 h5,.float-view .content-page h6 h6,.float-view .content-page h6 hr,.float-view .content-page h6 ol,.float-view .content-page h6 p,.float-view .content-page h6 pre,.float-view .content-page h6 ul,.float-view .content-page hr dl,.float-view .content-page hr h2,.float-view .content-page hr h3,.float-view .content-page hr h4,.float-view .content-page hr h5,.float-view .content-page hr h6,.float-view .content-page hr hr,.float-view .content-page hr ol,.float-view .content-page hr p,.float-view .content-page hr pre,.float-view .content-page hr ul,.float-view .content-page ol dl,.float-view .content-page ol h2,.float-view .content-page ol h3,.float-view .content-page ol h4,.float-view .content-page ol h5,.float-view .content-page ol h6,.float-view .content-page ol hr,.float-view .content-page ol ol,.float-view .content-page ol p,.float-view .content-page ol pre,.float-view .content-page ol ul,.float-view .content-page p dl,.float-view .content-page p h2,.float-view .content-page p h3,.float-view .content-page p h4,.float-view .content-page p h5,.float-view .content-page p h6,.float-view .content-page p hr,.float-view .content-page p ol,.float-view .content-page p p,.float-view .content-page p pre,.float-view .content-page p ul,.float-view .content-page ul dl,.float-view .content-page ul h2,.float-view .content-page ul h3,.float-view .content-page ul h4,.float-view .content-page ul h5,.float-view .content-page ul h6,.float-view .content-page ul hr,.float-view .content-page ul ol,.float-view .content-page ul p,.float-view .content-page ul pre,.float-view .content-page ul ul{float:none;display:block}.float-view .content-page hr{border-color:#ddd}.float-view .content-page blockquote p,.float-view .content-page blockquote pre,.float-view .content-page li p,.float-view .content-page li pre{width:100%}.float-view .content-page ol li,.float-view .content-page ul li{margin-left:30px}.float-view .content-page pre{float:left;clear:right;width:47%;border:none;border-left:10px solid #fff;margin:0 0 10px;padding:0 0 0 10px}}table{width:100%;border-bottom:1px solid #eee;margin-bottom:10px}table tr td,table tr th{padding:8px;line-height:20px;vertical-align:top;border-top:1px solid #eee;border-left:1px solid #eee;border-color:#eee!important}table tr td:last-child,table tr th:last-child{border-right:1px solid #eee}.footer{position:fixed;bottom:0;left:0;padding:15px}#github-ribbon{position:absolute;top:50px;right:0;z-index:200}.sidebar-links{padding:20px}.sidebar-links a{font-size:13px;font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:400;color:#ecb5a1;line-height:28px}.sidebar-links .twitter hr{border-bottom:none;margin-left:-20px;margin-right:-20px}.search{position:relative}.search__field{padding-right:30px}.search__icon{position:absolute;right:12px;top:10px}.TableOfContents{font-size:16px;padding-left:30px;border-left:6px solid #efefef}.TableOfContents p{margin-bottom:0}.TableOfContents .TableOfContents{border-left-width:0;padding-left:20px}.hljs{display:block;padding:.5em}.hljs,.hljs-clojure .hljs-built_in,.hljs-lisp .hljs-title,.hljs-nginx .hljs-title,.hljs-subst,.hljs-tag .hljs-title{color:#c64641}.hljs-addition,.hljs-aggregate,.hljs-apache .hljs-cbracket,.hljs-apache .hljs-tag,.hljs-bash .hljs-variable,.hljs-constant,.hljs-django .hljs-variable,.hljs-erlang_repl .hljs-function_or_atom,.hljs-flow,.hljs-markdown .hljs-header,.hljs-parent,.hljs-preprocessor,.hljs-ruby .hljs-symbol,.hljs-ruby .hljs-symbol .hljs-string,.hljs-rules .hljs-value,.hljs-rules .hljs-value .hljs-number,.hljs-smalltalk .hljs-class,.hljs-stream,.hljs-string,.hljs-tag .hljs-value,.hljs-template_tag,.hljs-tex .hljs-command,.hljs-tex .hljs-special,.hljs-title{color:#557aa2}.hljs-annotation,.hljs-chunk,.hljs-comment,.hljs-diff .hljs-header,.hljs-markdown .hljs-blockquote,.hljs-template_comment{color:#ecdfd0}.hljs-change,.hljs-date,.hljs-go .hljs-constant,.hljs-literal,.hljs-markdown .hljs-bullet,.hljs-markdown .hljs-link_url,.hljs-number,.hljs-regexp,.hljs-smalltalk .hljs-char,.hljs-smalltalk .hljs-symbol{color:#9b2f7d}.hljs-apache .hljs-sqbracket,.hljs-array,.hljs-attr_selector,.hljs-clojure .hljs-attribute,.hljs-coffeescript .hljs-property,.hljs-decorator,.hljs-deletion,.hljs-doctype,.hljs-envvar,.hljs-erlang_repl .hljs-reserved,.hljs-filter .hljs-argument,.hljs-important,.hljs-javadoc,.hljs-label,.hljs-localvars,.hljs-markdown .hljs-link_label,.hljs-nginx .hljs-built_in,.hljs-pi,.hljs-prompt,.hljs-pseudo,.hljs-ruby .hljs-string,.hljs-shebang,.hljs-tex .hljs-formula,.hljs-vhdl .hljs-attribute{color:#a31621}.hljs-aggregate,.hljs-apache .hljs-tag,.hljs-bash .hljs-variable,.hljs-built_in,.hljs-css .hljs-tag,.hljs-go .hljs-typename,.hljs-id,.hljs-javadoctag,.hljs-keyword,.hljs-markdown .hljs-strong,.hljs-phpdoc,.hljs-request,.hljs-smalltalk .hljs-class,.hljs-status,.hljs-tex .hljs-command,.hljs-title,.hljs-winutils,.hljs-yardoctag{font-weight:700}.hljs-markdown .hljs-emphasis{font-style:italic}.hljs-nginx .hljs-built_in{font-weight:400}.hljs-coffeescript .hljs-javascript,.hljs-javascript .hljs-xml,.hljs-tex .hljs-formula,.hljs-xml .hljs-cdata,.hljs-xml .hljs-css,.hljs-xml .hljs-javascript,.hljs-xml .hljs-vbscript{opacity:.5} -\ No newline at end of file -+ */.roboto-slab.light{font-weight:100}.roboto-slab.book,.roboto-slab.light{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif}.roboto-slab.book{font-weight:300}.roboto-slab.regular{font-weight:400}.roboto-slab.bold,.roboto-slab.regular{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif}.roboto-slab.bold{font-weight:700}h1,h2,h3,h4,h5,h6{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:300}h1 i{font-size:26px}pre{padding:0}.homepage-hero{padding-top:60px!important;background-color:#ecb5a1;box-shadow:none;border-radius:0;border:none;color:#c64641;overflow:hidden;padding-bottom:0;margin-bottom:0}.homepage-hero .text-center{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:700;margin:10px 0}.homepage-hero h2{margin:20px 0}.hero-buttons.container-fluid{padding:20px 0;background-color:#eee}.hero-buttons.container-fluid .btn-hero.btn{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:700;padding:20px 30px;background-image:none;-webkit-filter:none;filter:none;box-shadow:none;border-radius:0;text-shadow:none;border:none;opacity:.8;filter:alpha(opacity=80);margin:0 10px;text-transform:uppercase;border:5px solid #c64641}@media (max-width:768px){.hero-buttons.container-fluid .btn-hero.btn{display:block;margin-bottom:10px}}.hero-buttons.container-fluid .btn-hero.btn:hover{opacity:1;filter:alpha(opacity=100)}.hero-buttons.container-fluid .btn-hero.btn.btn-secondary{background-color:#eee;color:#c64641}.hero-buttons.container-fluid .btn-hero.btn.btn-primary{background-color:#c64641;color:#f7f7f7}.homepage-content.container-fluid{background-color:#fff;padding:40px 0}.homepage-content.container-fluid .lead{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:400}.homepage-content.container-fluid ol,.homepage-content.container-fluid ul{padding:20px 0;margin:0 0 10px}.homepage-content.container-fluid ol li,.homepage-content.container-fluid ul li{list-style:none;padding-bottom:5px}.homepage-content.container-fluid ol li:before,.homepage-content.container-fluid ul li:before{content:'';width:0;height:0;border:3px solid transparent;border-left:3px solid #ecb5a1;float:left;display:block;margin:6px}@media (max-width:768px){.homepage-content.container-fluid{padding:40px 20px}}.homepage-footer.container-fluid{background-color:#c64641;box-shadow:none;border-radius:0;color:light;border:none}@media (max-width:768px){.homepage-footer.container-fluid{padding:0 20px}}.homepage-footer.container-fluid .footer-nav{margin:40px 0}.homepage-footer.container-fluid .footer-nav li a{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:700;font-size:16px;line-height:32px}.homepage-footer.container-fluid .footer-nav li a:hover{color:#ecb5a1;border-bottom-style:solid;border-bottom-color:#ecb5a1;text-decoration:none}.homepage-footer.container-fluid .twitter{margin-top:20px}.homepage-footer.container-fluid .twitter:first-child{margin-top:40px}body,html{height:100%;background-color:#fff;color:#2d2d2d}.content{background:#f0f0f0}.columns .left-column{background-color:#f7f7f7}.columns .right-column .content-page{padding:10px;background-color:#fff}.container-fluid .navbar-static-top{margin-left:-15px;margin-right:-15px}.responsive-collapse{padding:10px 15px;display:block;background-color:#eee;border-bottom:1px solid #eee}.sub-nav-collapse{display:none}.article-tree,.content-area{padding:0}@media screen and (min-width:768px){body{background-color:#fff}.navbar-static-top{position:fixed;z-index:1030;width:100%}.responsive-collapse{display:none}.sub-nav-collapse{display:block!important}.container-fluid.fluid-height{height:100%}.article-tree,.content-area{overflow:auto;height:100%}.columns{height:100%;padding-top:0}.columns .left-column{max-width:400px;border-right:1px solid #eee;overflow-x:hidden}.columns .right-column .content-page{margin:auto;position:relative;max-width:800px;padding:20px;min-height:100%}}@media screen and (min-width:1200px){.columns .right-column{width:calc(100% - 400px)}}@media only screen and (max-width:800px){table,tbody,td,th,thead,tr{display:block;border:none}thead tr{position:absolute;top:-9999px;left:-9999px}tr{margin-bottom:10px;border-bottom:2px solid #ccc}tr td,tr th{border:1px solid #ccc;border-bottom:none}td{border:none;border-bottom:1px solid #eee;position:relative;padding-left:50%!important;white-space:normal}td,td:before{text-align:left}td:before{position:absolute;top:6px;left:6px;width:45%;padding-right:10px;white-space:nowrap;font-weight:700;content:attr(data-title)}}@media print{.content-area{width:100%!important}h1 a[href]:after{font-size:50%}}body{font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif}a{color:#ecb5a1;border-bottom:1px dotted #ecb5a1}a:hover{border-bottom-style:solid;border-bottom-color:#c64641;text-decoration:none}a>code{border-bottom:1px dotted #ecb5a1}a>code:hover{border-bottom-style:solid}a.folder{border-bottom:none}.btn{display:inline-block}.btn.btn-sidebar{padding:7px 10px;background-image:none;-webkit-filter:none;filter:none;box-shadow:none;background-color:#eee;border:none}.btn.btn-sidebar .icon-bar{display:block;width:18px;height:2px;margin-top:2px;margin-bottom:3px}.btn.btn-sidebar .icon-bar,.btn.btn-sidebar:hover{background-color:#c64641;box-shadow:none}.btn.btn-sidebar:hover .icon-bar{background-color:#ecb5a1;box-shadow:none}code{color:#666;border:1px solid #ddd}a code{color:#ecb5a1}.nav-logo{background:#a41e22;padding:20px;color:#fff;font-size:40px}.navbar{box-shadow:0 1px 5px rgba(0,0,0,.25);background-color:#c64641;margin-bottom:0}.navbar .container,.navbar .container-fluid{background-image:none;-webkit-filter:none;filter:none;border-bottom:none;padding:0 20px}.navbar .container-fluid .brand,.navbar .container .brand{color:#ecb5a1;text-shadow:none;font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:700;border-bottom:none}.navbar .container-fluid .navbar-text,.navbar .container-fluid .navbar-text a,.navbar .container .navbar-text,.navbar .container .navbar-text a{color:#ecb5a1}.code-buttons-text{font-size:12px;line-height:1.5;padding:6px 10px 6px 0;display:inline-block;vertical-align:middle}.nav{background:#272525}.nav.nav-list{padding-left:0;padding-right:0}.nav.nav-list li a{margin:0;padding:6px 15px 6px 20px;font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:400;color:#b3b3b3;font-size:15px;text-shadow:none;border-color:#eee;border-bottom:none}.nav.nav-list li a .arrow{display:inline-block;position:relative;width:16px;margin-left:-16px}.nav.nav-list li a .arrow:before{position:absolute;display:block;content:"";margin:-.25em 0 0 -.4em;left:50%;top:50%;width:.5em;height:.5em;border-right:.15em solid #c64641;border-top:.15em solid #c64641;-webkit-transform:rotate(45deg);transform:rotate(45deg);-webkit-transition-duration:.3s;transition-duration:.3s}.nav.nav-list li a:hover{color:#c64641;text-shadow:none;background-color:#eee;border-bottom:none}.nav.nav-list li.active a{background-color:#eee}.nav.nav-list li.open>ul{display:block}.nav.nav-list li.open>a,.nav.nav-list li.open>a:focus,.nav.nav-list li.open>a:hover{background-color:transparent}.nav.nav-list li.open>a>.arrow:before{margin-left:-.25em;-webkit-transform:rotate(135deg);transform:rotate(135deg)}.nav.nav-list li ul{display:none;margin-left:15px}.nav.nav-list li ul li a{font-weight:400;font-size:14px;font-family:Helvetica Neue,Helvetica,Arial,sans-serif;line-height:20px;margin:0;margin-left:-15px;padding:3px 30px;border:none;color:#b3b3b3;opacity:.7;filter:alpha(opacity=70)}.nav.nav-list li ul li a:hover{opacity:1;filter:alpha(opacity=100);background-color:transparent}.nav.nav-list li ul li.active a{color:#b3b3b3}.page-header{margin:10px 0;padding:0}.page-header h1{margin-top:0}.page-header h1 a{border-bottom:none}.page-header sub-heading{padding:0,0,20px}pre{border:none;background-color:#343131;border-radius:0;padding:10px;margin-left:-20px;padding-left:30px;margin-right:-20px;padding-right:30px}pre code{background:transparent;border:none;color:#fff}@media (min-width:1150px){.float-view .content-page{height:100%;overflow:auto;padding:0!important;background-color:transparent!important;position:relative}.float-view .content-page article{width:100%;min-height:100%;overflow:auto;position:relative;z-index:1}.float-view .content-page article:before{content:"";width:50%;min-height:100%;overflow:auto;background-color:#fff;display:block;margin:0;position:absolute;z-index:-1}.float-view .content-page table{float:left;clear:left;width:47%;margin-left:1.5%;margin-right:1.5%;background-color:#fff;white-space:normal}.float-view .content-page table code,.float-view .content-page table pre{white-space:normal}.float-view .content-page .page-header{padding:0}.float-view .content-page .page-header,.float-view .content-page blockquote,.float-view .content-page dl,.float-view .content-page h2,.float-view .content-page h3,.float-view .content-page h4,.float-view .content-page h5,.float-view .content-page h6,.float-view .content-page hr,.float-view .content-page ol,.float-view .content-page p,.float-view .content-page ul{float:left;clear:left;width:47%;margin-left:1.5%;margin-right:1.5%;background-color:#fff}.float-view .content-page .page-header:before,.float-view .content-page blockquote:before,.float-view .content-page dl:before,.float-view .content-page h2:before,.float-view .content-page h3:before,.float-view .content-page h4:before,.float-view .content-page h5:before,.float-view .content-page h6:before,.float-view .content-page hr:before,.float-view .content-page ol:before,.float-view .content-page p:before,.float-view .content-page ul:before{width:100%;height:10px;display:block;clear:both}.float-view .content-page .page-header dl,.float-view .content-page .page-header h2,.float-view .content-page .page-header h3,.float-view .content-page .page-header h4,.float-view .content-page .page-header h5,.float-view .content-page .page-header h6,.float-view .content-page .page-header hr,.float-view .content-page .page-header ol,.float-view .content-page .page-header p,.float-view .content-page .page-header pre,.float-view .content-page .page-header ul,.float-view .content-page blockquote dl,.float-view .content-page blockquote h2,.float-view .content-page blockquote h3,.float-view .content-page blockquote h4,.float-view .content-page blockquote h5,.float-view .content-page blockquote h6,.float-view .content-page blockquote hr,.float-view .content-page blockquote ol,.float-view .content-page blockquote p,.float-view .content-page blockquote pre,.float-view .content-page blockquote ul,.float-view .content-page dl dl,.float-view .content-page dl h2,.float-view .content-page dl h3,.float-view .content-page dl h4,.float-view .content-page dl h5,.float-view .content-page dl h6,.float-view .content-page dl hr,.float-view .content-page dl ol,.float-view .content-page dl p,.float-view .content-page dl pre,.float-view .content-page dl ul,.float-view .content-page h2 dl,.float-view .content-page h2 h2,.float-view .content-page h2 h3,.float-view .content-page h2 h4,.float-view .content-page h2 h5,.float-view .content-page h2 h6,.float-view .content-page h2 hr,.float-view .content-page h2 ol,.float-view .content-page h2 p,.float-view .content-page h2 pre,.float-view .content-page h2 ul,.float-view .content-page h3 dl,.float-view .content-page h3 h2,.float-view .content-page h3 h3,.float-view .content-page h3 h4,.float-view .content-page h3 h5,.float-view .content-page h3 h6,.float-view .content-page h3 hr,.float-view .content-page h3 ol,.float-view .content-page h3 p,.float-view .content-page h3 pre,.float-view .content-page h3 ul,.float-view .content-page h4 dl,.float-view .content-page h4 h2,.float-view .content-page h4 h3,.float-view .content-page h4 h4,.float-view .content-page h4 h5,.float-view .content-page h4 h6,.float-view .content-page h4 hr,.float-view .content-page h4 ol,.float-view .content-page h4 p,.float-view .content-page h4 pre,.float-view .content-page h4 ul,.float-view .content-page h5 dl,.float-view .content-page h5 h2,.float-view .content-page h5 h3,.float-view .content-page h5 h4,.float-view .content-page h5 h5,.float-view .content-page h5 h6,.float-view .content-page h5 hr,.float-view .content-page h5 ol,.float-view .content-page h5 p,.float-view .content-page h5 pre,.float-view .content-page h5 ul,.float-view .content-page h6 dl,.float-view .content-page h6 h2,.float-view .content-page h6 h3,.float-view .content-page h6 h4,.float-view .content-page h6 h5,.float-view .content-page h6 h6,.float-view .content-page h6 hr,.float-view .content-page h6 ol,.float-view .content-page h6 p,.float-view .content-page h6 pre,.float-view .content-page h6 ul,.float-view .content-page hr dl,.float-view .content-page hr h2,.float-view .content-page hr h3,.float-view .content-page hr h4,.float-view .content-page hr h5,.float-view .content-page hr h6,.float-view .content-page hr hr,.float-view .content-page hr ol,.float-view .content-page hr p,.float-view .content-page hr pre,.float-view .content-page hr ul,.float-view .content-page ol dl,.float-view .content-page ol h2,.float-view .content-page ol h3,.float-view .content-page ol h4,.float-view .content-page ol h5,.float-view .content-page ol h6,.float-view .content-page ol hr,.float-view .content-page ol ol,.float-view .content-page ol p,.float-view .content-page ol pre,.float-view .content-page ol ul,.float-view .content-page p dl,.float-view .content-page p h2,.float-view .content-page p h3,.float-view .content-page p h4,.float-view .content-page p h5,.float-view .content-page p h6,.float-view .content-page p hr,.float-view .content-page p ol,.float-view .content-page p p,.float-view .content-page p pre,.float-view .content-page p ul,.float-view .content-page ul dl,.float-view .content-page ul h2,.float-view .content-page ul h3,.float-view .content-page ul h4,.float-view .content-page ul h5,.float-view .content-page ul h6,.float-view .content-page ul hr,.float-view .content-page ul ol,.float-view .content-page ul p,.float-view .content-page ul pre,.float-view .content-page ul ul{float:none;display:block}.float-view .content-page hr{border-color:#ddd}.float-view .content-page blockquote p,.float-view .content-page blockquote pre,.float-view .content-page li p,.float-view .content-page li pre{width:100%}.float-view .content-page ol li,.float-view .content-page ul li{margin-left:30px}.float-view .content-page pre{float:left;clear:right;width:47%;border:none;border-left:10px solid #fff;margin:0 0 10px;padding:0 0 0 10px}}table{width:100%;border-bottom:1px solid #eee;margin-bottom:10px}table tr td,table tr th{padding:8px;line-height:20px;vertical-align:top;border-top:1px solid #eee;border-left:1px solid #eee;border-color:#eee!important}table tr td:last-child,table tr th:last-child{border-right:1px solid #eee}.footer{position:fixed;bottom:0;left:0;padding:15px}#github-ribbon{position:absolute;top:50px;right:0;z-index:200}.sidebar-links{padding:20px}.sidebar-links a{font-size:13px;font-family:Roboto Slab,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:400;color:#fff;line-height:28px}.sidebar-links a,.sidebar-links a:hover{border-bottom-color:#fff}.sidebar-links .twitter hr{border-bottom:none;margin-left:-20px;margin-right:-20px}.search{position:relative}.search__field{padding-right:30px}.search__icon{position:absolute;right:12px;top:10px}.TableOfContents{font-size:16px;padding-left:30px;border-left:6px solid #efefef}.TableOfContents p{margin-bottom:0}.TableOfContents .TableOfContents{border-left-width:0;padding-left:20px}.hljs{display:block;padding:.5em}.hljs,.hljs-clojure .hljs-built_in,.hljs-lisp .hljs-title,.hljs-nginx .hljs-title,.hljs-subst,.hljs-tag .hljs-title{color:#f8f8f2}.hljs-meta{color:#75715e}.hljs-keyword{color:#f92672}.hljs-function{color:#89e229}.hljs-function .hljs-params{color:#fff}.hljs-literal,.hljs-number{color:#ae81ff}.hljs-string{color:#e6db74}.hljs-comment{color:#75715e}.hljs-type{color:#66d8ee} -\ No newline at end of file -diff --git a/themes/daux/css/theme.min.css b/themes/daux/css/theme.min.css -index 920b93f..e95f4a4 100644 ---- a/themes/daux/css/theme.min.css -+++ b/themes/daux/css/theme.min.css -@@ -3,5 +3,5 @@ - * Copyright 2011-2015 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - */ --/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0} -+/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0} - /*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{background:transparent!important;color:#000!important;box-shadow:none!important;text-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.label{border:1px solid #000}}@font-face{font-family:Glyphicons Halflings;src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:Glyphicons Halflings;font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-search:before{content:"\e003"}.glyphicon-chevron-right:before{content:"\e080"}*,:after,:before{box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:Helvetica Neue,Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.img-responsive{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;transition:all .2s ease-in-out;display:inline-block;max-width:100%;height:auto}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role=button]{cursor:pointer}h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:400;line-height:1;color:#777}h1,h2,h3{margin-top:20px;margin-bottom:10px}h1 small,h2 small,h3 small{font-size:65%}h4,h5,h6{margin-top:10px;margin-bottom:10px}h4 small,h5 small,h6 small{font-size:75%}h1{font-size:36px}h2{font-size:30px}h3{font-size:24px}h4{font-size:18px}h5{font-size:14px}h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}small{font-size:85%}.mark,mark{background-color:#fcf8e3;padding:.2em}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:focus,a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:focus,a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:focus,a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:focus,a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:focus,a.text-danger:hover{color:#843534}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-inline,.list-unstyled{padding-left:0;list-style:none}.list-inline{margin-left:-5px}.list-inline>li{display:inline-block;padding-left:5px;padding-right:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0;text-align:right}.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,Courier New,monospace}code{color:#c7254e;background-color:#f9f2f4;border-radius:4px}code,kbd{padding:2px 4px;font-size:90%}kbd{color:#fff;background-color:#333;border-radius:3px;box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;word-break:break-all;word-wrap:break-word;color:#333;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}.row{margin-left:-15px;margin-right:-15px}.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12{position:relative;min-height:1px;padding-left:15px;padding-right:15px}@media (min-width:768px){.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}fieldset{margin:0;min-width:0}fieldset,legend{padding:0;border:0}legend{display:block;width:100%;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{box-sizing:border-box}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px\9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{padding-top:7px}.form-control,output{display:block;font-size:14px;line-height:1.42857143;color:#555}.form-control{width:100%;height:34px;padding:6px 12px;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=datetime-local].form-control,input[type=month].form-control,input[type=time].form-control{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],.input-group-sm input[type=time],input[type=date].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm,input[type=time].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],.input-group-lg input[type=time],input[type=date].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg,input[type=time].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox-inline input[type=checkbox],.checkbox input[type=checkbox],.radio-inline input[type=radio],.radio input[type=radio]{position:absolute;margin-left:-20px;margin-top:4px\9}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;vertical-align:middle;font-weight:400;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}.checkbox-inline.disabled,.checkbox.disabled label,.radio-inline.disabled,.radio.disabled label,fieldset[disabled] .checkbox-inline,fieldset[disabled] .checkbox label,fieldset[disabled] .radio-inline,fieldset[disabled] .radio label,fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.form-control-static{padding-top:7px;padding-bottom:7px;margin-bottom:0;min-height:34px}.form-control-static.input-lg,.form-control-static.input-sm{padding-left:0;padding-right:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success.checkbox-inline label,.has-success.checkbox label,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.radio-inline label,.has-success.radio label{color:#3c763d}.has-success .form-control{border-color:#3c763d;box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;border-color:#3c763d;background-color:#dff0d8}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning.checkbox-inline label,.has-warning.checkbox label,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.radio-inline label,.has-warning.radio label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;border-color:#8a6d3b;background-color:#fcf8e3}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error.checkbox-inline label,.has-error.checkbox label,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.radio-inline label,.has-error.radio label{color:#a94442}.has-error .form-control{border-color:#a94442;box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;border-color:#a94442;background-color:#f2dede}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{margin-top:0;margin-bottom:0;padding-top:7px}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-left:-15px;margin-right:-15px}@media (min-width:768px){.form-horizontal .control-label{text-align:right;margin-bottom:0;padding-top:7px}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:14.333333px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;margin-bottom:0;font-weight:400;text-align:center;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;background-image:none;border:1px solid transparent;white-space:nowrap;padding:6px 12px;font-size:14px;line-height:1.42857143;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{outline:0;background-image:none;box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;opacity:.65;filter:alpha(opacity=65);box-shadow:none}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.focus,.btn-default:focus{color:#333;background-color:#e6e6e6;border-color:#8c8c8c}.btn-default.active,.btn-default:active,.btn-default:hover,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.dropdown-toggle.btn-default.focus,.open>.dropdown-toggle.btn-default:focus,.open>.dropdown-toggle.btn-default:hover{color:#333;background-color:#d4d4d4;border-color:#8c8c8c}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#286090;border-color:#122b40}.btn-primary.active,.btn-primary:active,.btn-primary:hover,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.dropdown-toggle.btn-primary.focus,.open>.dropdown-toggle.btn-primary:focus,.open>.dropdown-toggle.btn-primary:hover{color:#fff;background-color:#204d74;border-color:#122b40}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-group-lg>.btn{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px}.btn-group-sm>.btn,.btn-group-xs>.btn,.btn-sm{font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn{padding:1px 5px}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar .btn-group{float:left}.btn-toolbar>.btn-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-bottom-left-radius:0;border-top-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-left:8px;padding-right:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-left:12px;padding-right:12px}.btn-group.open .dropdown-toggle{box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{box-shadow:none}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-bottom-left-radius:4px;border-top-right-radius:0;border-top-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-right-radius:0;border-top-left-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{float:none;display:table-cell;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio],[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.nav{margin-bottom:0;padding-left:0;list-style:none}.nav>li,.nav>li>a{position:relative;display:block}.nav>li>a{padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;background-color:transparent;cursor:not-allowed}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{overflow-x:visible;padding-right:15px;padding-left:15px;border-top:1px solid transparent;box-shadow:inset 0 1px 0 hsla(0,0%,100%,.1);-webkit-overflow-scrolling:touch}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-static-top .navbar-collapse{padding-left:0;padding-right:0}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-brand{float:left;padding:15px;font-size:18px;line-height:20px;height:50px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container-fluid .navbar-brand,.navbar>.container .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;margin-right:15px;padding:9px 10px;margin-top:8px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{margin:8px -15px;padding:10px 15px;border-top:1px solid transparent;border-bottom:1px solid transparent;box-shadow:inset 0 1px 0 hsla(0,0%,100%,.1),0 1px 0 hsla(0,0%,100%,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;border:0;margin-left:0;margin-right:0;padding-top:0;padding-bottom:0;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-right-radius:0;border-top-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-left:15px;margin-right:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.pager{padding-left:0;margin:20px 0;list-style:none;text-align:center}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;background-color:#fff;cursor:not-allowed}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.row:after,.row:before{content:" ";display:table}.btn-group-vertical>.btn-group:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.row:after{clear:both}.center-block{display:block;margin-left:auto;margin-right:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table!important}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table!important}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table!important}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table!important}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table!important}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} -\ No newline at end of file -diff --git a/themes/daux/js/daux.js b/themes/daux/js/daux.js -index dea4f26..c91a646 100644 ---- a/themes/daux/js/daux.js -+++ b/themes/daux/js/daux.js -@@ -138,3 +138,15 @@ $(function () { - onResize(); - }); - -+// Horrible hack to ensure that scroll position remains the same after refresh -+var scrollKey = 'scroll_' + window.location.pathname; -+ -+$('.right-column').scroll(function() { -+ sessionStorage.setItem(scrollKey, $(this).scrollTop()); -+}); -+ -+$(document).ready(function() { -+ if (performance.navigation.type === 1 && sessionStorage.getItem(scrollKey) !== null) { -+ $('.right-column').scrollTop(+sessionStorage.getItem(scrollKey)); -+ } -+}); -\ No newline at end of file -diff --git a/themes/daux/js/highlight.pack.js b/themes/daux/js/highlight.pack.js -index e0ea3ee..9d49c09 100644 ---- a/themes/daux/js/highlight.pack.js -+++ b/themes/daux/js/highlight.pack.js -@@ -1 +1,575 @@ --!function(e){"undefined"!=typeof exports?e(exports):(window.hljs=e({}),"function"==typeof define&&define.amd&&define("hljs",[],function(){return window.hljs}))}(function(e){function n(e){return e.replace(/&/gm,"&").replace(//gm,">")}function t(e){return e.nodeName.toLowerCase()}function r(e,n){var t=e&&e.exec(n);return t&&0==t.index}function a(e){return/^(no-?highlight|plain|text)$/i.test(e)}function i(e){var n,t,r,i=e.className+" ";if(i+=e.parentNode?e.parentNode.className:"",t=/\blang(?:uage)?-([\w-]+)\b/i.exec(i))return w(t[1])?t[1]:"no-highlight";for(i=i.split(/\s+/),n=0,r=i.length;r>n;n++)if(w(i[n])||a(i[n]))return i[n]}function o(e,n){var t,r={};for(t in e)r[t]=e[t];if(n)for(t in n)r[t]=n[t];return r}function u(e){var n=[];return function r(e,a){for(var i=e.firstChild;i;i=i.nextSibling)3==i.nodeType?a+=i.nodeValue.length:1==i.nodeType&&(n.push({event:"start",offset:a,node:i}),a=r(i,a),t(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:a,node:i}));return a}(e,0),n}function c(e,r,a){function i(){return e.length&&r.length?e[0].offset!=r[0].offset?e[0].offset"}function u(e){l+=""}function c(e){("start"==e.event?o:u)(e.node)}for(var s=0,l="",f=[];e.length||r.length;){var g=i();if(l+=n(a.substr(s,g[0].offset-s)),s=g[0].offset,g==e){f.reverse().forEach(u);do c(g.splice(0,1)[0]),g=i();while(g==e&&g.length&&g[0].offset==s);f.reverse().forEach(o)}else"start"==g[0].event?f.push(g[0].node):f.pop(),c(g.splice(0,1)[0])}return l+n(a.substr(s))}function s(e){function n(e){return e&&e.source||e}function t(t,r){return new RegExp(n(t),"m"+(e.cI?"i":"")+(r?"g":""))}function r(a,i){if(!a.compiled){if(a.compiled=!0,a.k=a.k||a.bK,a.k){var u={},c=function(n,t){e.cI&&(t=t.toLowerCase()),t.split(" ").forEach(function(e){var t=e.split("|");u[t[0]]=[n,t[1]?Number(t[1]):1]})};"string"==typeof a.k?c("keyword",a.k):Object.keys(a.k).forEach(function(e){c(e,a.k[e])}),a.k=u}a.lR=t(a.l||/\b\w+\b/,!0),i&&(a.bK&&(a.b="\\b("+a.bK.split(" ").join("|")+")\\b"),a.b||(a.b=/\B|\b/),a.bR=t(a.b),a.e||a.eW||(a.e=/\B|\b/),a.e&&(a.eR=t(a.e)),a.tE=n(a.e)||"",a.eW&&i.tE&&(a.tE+=(a.e?"|":"")+i.tE)),a.i&&(a.iR=t(a.i)),void 0===a.r&&(a.r=1),a.c||(a.c=[]);var s=[];a.c.forEach(function(e){e.v?e.v.forEach(function(n){s.push(o(e,n))}):s.push("self"==e?a:e)}),a.c=s,a.c.forEach(function(e){r(e,a)}),a.starts&&r(a.starts,i);var l=a.c.map(function(e){return e.bK?"\\.?("+e.b+")\\.?":e.b}).concat([a.tE,a.i]).map(n).filter(Boolean);a.t=l.length?t(l.join("|"),!0):{exec:function(){return null}}}}r(e)}function l(e,t,a,i){function o(e,n){for(var t=0;t";return i+=e+'">',i+n+o}function p(){if(!L.k)return n(M);var e="",t=0;L.lR.lastIndex=0;for(var r=L.lR.exec(M);r;){e+=n(M.substr(t,r.index-t));var a=g(L,r);a?(B+=a[1],e+=h(a[0],n(r[0]))):e+=n(r[0]),t=L.lR.lastIndex,r=L.lR.exec(M)}return e+n(M.substr(t))}function d(){var e="string"==typeof L.sL;if(e&&!x[L.sL])return n(M);var t=e?l(L.sL,M,!0,y[L.sL]):f(M,L.sL.length?L.sL:void 0);return L.r>0&&(B+=t.r),e&&(y[L.sL]=t.top),h(t.language,t.value,!1,!0)}function b(){return void 0!==L.sL?d():p()}function v(e,t){var r=e.cN?h(e.cN,"",!0):"";e.rB?(k+=r,M=""):e.eB?(k+=n(t)+r,M=""):(k+=r,M=t),L=Object.create(e,{parent:{value:L}})}function m(e,t){if(M+=e,void 0===t)return k+=b(),0;var r=o(t,L);if(r)return k+=b(),v(r,t),r.rB?0:t.length;var a=u(L,t);if(a){var i=L;i.rE||i.eE||(M+=t),k+=b();do L.cN&&(k+=""),B+=L.r,L=L.parent;while(L!=a.parent);return i.eE&&(k+=n(t)),M="",a.starts&&v(a.starts,""),i.rE?0:t.length}if(c(t,L))throw new Error('Illegal lexeme "'+t+'" for mode "'+(L.cN||"")+'"');return M+=t,t.length||1}var N=w(e);if(!N)throw new Error('Unknown language: "'+e+'"');s(N);var R,L=i||N,y={},k="";for(R=L;R!=N;R=R.parent)R.cN&&(k=h(R.cN,"",!0)+k);var M="",B=0;try{for(var C,j,I=0;;){if(L.t.lastIndex=I,C=L.t.exec(t),!C)break;j=m(t.substr(I,C.index-I),C[0]),I=C.index+j}for(m(t.substr(I)),R=L;R.parent;R=R.parent)R.cN&&(k+="");return{r:B,value:k,language:e,top:L}}catch(O){if(-1!=O.message.indexOf("Illegal"))return{r:0,value:n(t)};throw O}}function f(e,t){t=t||E.languages||Object.keys(x);var r={r:0,value:n(e)},a=r;return t.forEach(function(n){if(w(n)){var t=l(n,e,!1);t.language=n,t.r>a.r&&(a=t),t.r>r.r&&(a=r,r=t)}}),a.language&&(r.second_best=a),r}function g(e){return E.tabReplace&&(e=e.replace(/^((<[^>]+>|\t)+)/gm,function(e,n){return n.replace(/\t/g,E.tabReplace)})),E.useBR&&(e=e.replace(/\n/g,"
")),e}function h(e,n,t){var r=n?R[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),-1===e.indexOf(r)&&a.push(r),a.join(" ").trim()}function p(e){var n=i(e);if(!a(n)){var t;E.useBR?(t=document.createElementNS("http://www.w3.org/1999/xhtml","div"),t.innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n")):t=e;var r=t.textContent,o=n?l(n,r,!0):f(r),s=u(t);if(s.length){var p=document.createElementNS("http://www.w3.org/1999/xhtml","div");p.innerHTML=o.value,o.value=c(s,u(p),r)}o.value=g(o.value),e.innerHTML=o.value,e.className=h(e.className,n,o.language),e.result={language:o.language,re:o.r},o.second_best&&(e.second_best={language:o.second_best.language,re:o.second_best.r})}}function d(e){E=o(E,e)}function b(){if(!b.called){b.called=!0;var e=document.querySelectorAll("pre code");Array.prototype.forEach.call(e,p)}}function v(){addEventListener("DOMContentLoaded",b,!1),addEventListener("load",b,!1)}function m(n,t){var r=x[n]=t(e);r.aliases&&r.aliases.forEach(function(e){R[e]=n})}function N(){return Object.keys(x)}function w(e){return e=(e||"").toLowerCase(),x[e]||x[R[e]]}var E={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0},x={},R={};return e.highlight=l,e.highlightAuto=f,e.fixMarkup=g,e.highlightBlock=p,e.configure=d,e.initHighlighting=b,e.initHighlightingOnLoad=v,e.registerLanguage=m,e.listLanguages=N,e.getLanguage=w,e.inherit=o,e.IR="[a-zA-Z]\\w*",e.UIR="[a-zA-Z_]\\w*",e.NR="\\b\\d+(\\.\\d+)?",e.CNR="(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",e.BNR="\\b(0b[01]+)",e.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",e.BE={b:"\\\\[\\s\\S]",r:0},e.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[e.BE]},e.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[e.BE]},e.PWM={b:/\b(a|an|the|are|I|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|like)\b/},e.C=function(n,t,r){var a=e.inherit({cN:"comment",b:n,e:t,c:[]},r||{});return a.c.push(e.PWM),a.c.push({cN:"doctag",b:"(?:TODO|FIXME|NOTE|BUG|XXX):",r:0}),a},e.CLCM=e.C("//","$"),e.CBCM=e.C("/\\*","\\*/"),e.HCM=e.C("#","$"),e.NM={cN:"number",b:e.NR,r:0},e.CNM={cN:"number",b:e.CNR,r:0},e.BNM={cN:"number",b:e.BNR,r:0},e.CSSNM={cN:"number",b:e.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0},e.RM={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[e.BE,{b:/\[/,e:/\]/,r:0,c:[e.BE]}]},e.TM={cN:"title",b:e.IR,r:0},e.UTM={cN:"title",b:e.UIR,r:0},e});hljs.registerLanguage("parser3",function(r){var e=r.C("{","}",{c:["self"]});return{sL:"xml",r:0,c:[r.C("^#","$"),r.C("\\^rem{","}",{r:10,c:[e]}),{cN:"preprocessor",b:"^@(?:BASE|USE|CLASS|OPTIONS)$",r:10},{cN:"title",b:"@[\\w\\-]+\\[[\\w^;\\-]*\\](?:\\[[\\w^;\\-]*\\])?(?:.*)$"},{cN:"variable",b:"\\$\\{?[\\w\\-\\.\\:]+\\}?"},{cN:"keyword",b:"\\^[\\w\\-\\.\\:]+"},{cN:"number",b:"\\^#[0-9a-fA-F]+"},r.CNM]}});hljs.registerLanguage("nginx",function(e){var r={cN:"variable",v:[{b:/\$\d+/},{b:/\$\{/,e:/}/},{b:"[\\$\\@]"+e.UIR}]},b={eW:!0,l:"[a-z/_]+",k:{built_in:"on off yes no true false none blocked debug info notice warn error crit select break last permanent redirect kqueue rtsig epoll poll /dev/poll"},r:0,i:"=>",c:[e.HCM,{cN:"string",c:[e.BE,r],v:[{b:/"/,e:/"/},{b:/'/,e:/'/}]},{cN:"url",b:"([a-z]+):/",e:"\\s",eW:!0,eE:!0,c:[r]},{cN:"regexp",c:[e.BE,r],v:[{b:"\\s\\^",e:"\\s|{|;",rE:!0},{b:"~\\*?\\s+",e:"\\s|{|;",rE:!0},{b:"\\*(\\.[a-z\\-]+)+"},{b:"([a-z\\-]+\\.)+\\*"}]},{cN:"number",b:"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?\\b"},{cN:"number",b:"\\b\\d+[kKmMgGdshdwy]*\\b",r:0},r]};return{aliases:["nginxconf"],c:[e.HCM,{b:e.UIR+"\\s",e:";|{",rB:!0,c:[{cN:"title",b:e.UIR,starts:b}],r:0}],i:"[^\\s\\}]"}});hljs.registerLanguage("cs",function(e){var r="abstract as base bool break byte case catch char checked const continue decimal dynamic default delegate do double else enum event explicit extern false finally fixed float for foreach goto if implicit in int interface internal is lock long null when object operator out override params private protected public readonly ref sbyte sealed short sizeof stackalloc static string struct switch this true try typeof uint ulong unchecked unsafe ushort using virtual volatile void while async protected public private internal ascending descending from get group into join let orderby partial select set value var where yield",t=e.IR+"(<"+e.IR+">)?";return{aliases:["csharp"],k:r,i:/::/,c:[e.C("///","$",{rB:!0,c:[{cN:"xmlDocTag",v:[{b:"///",r:0},{b:""},{b:""}]}]}),e.CLCM,e.CBCM,{cN:"preprocessor",b:"#",e:"$",k:"if else elif endif define undef warning error line region endregion pragma checksum"},{cN:"string",b:'@"',e:'"',c:[{b:'""'}]},e.ASM,e.QSM,e.CNM,{bK:"class interface",e:/[{;=]/,i:/[^\s:]/,c:[e.TM,e.CLCM,e.CBCM]},{bK:"namespace",e:/[{;=]/,i:/[^\s:]/,c:[{cN:"title",b:"[a-zA-Z](\\.?\\w)*",r:0},e.CLCM,e.CBCM]},{bK:"new return throw await",r:0},{cN:"function",b:"("+t+"\\s+)+"+e.IR+"\\s*\\(",rB:!0,e:/[{;=]/,eE:!0,k:r,c:[{b:e.IR+"\\s*\\(",rB:!0,c:[e.TM],r:0},{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,k:r,r:0,c:[e.ASM,e.QSM,e.CNM,e.CBCM]},e.CLCM,e.CBCM]}]}});hljs.registerLanguage("diff",function(e){return{aliases:["patch"],c:[{cN:"chunk",r:10,v:[{b:/^@@ +\-\d+,\d+ +\+\d+,\d+ +@@$/},{b:/^\*\*\* +\d+,\d+ +\*\*\*\*$/},{b:/^\-\-\- +\d+,\d+ +\-\-\-\-$/}]},{cN:"header",v:[{b:/Index: /,e:/$/},{b:/=====/,e:/=====$/},{b:/^\-\-\-/,e:/$/},{b:/^\*{3} /,e:/$/},{b:/^\+\+\+/,e:/$/},{b:/\*{5}/,e:/\*{5}$/}]},{cN:"addition",b:"^\\+",e:"$"},{cN:"deletion",b:"^\\-",e:"$"},{cN:"change",b:"^\\!",e:"$"}]}});hljs.registerLanguage("haskell",function(e){var c=[e.C("--","$"),e.C("{-","-}",{c:["self"]})],a={cN:"pragma",b:"{-#",e:"#-}"},i={cN:"preprocessor",b:"^#",e:"$"},n={cN:"type",b:"\\b[A-Z][\\w']*",r:0},t={cN:"container",b:"\\(",e:"\\)",i:'"',c:[a,i,{cN:"type",b:"\\b[A-Z][\\w]*(\\((\\.\\.|,|\\w+)\\))?"},e.inherit(e.TM,{b:"[_a-z][\\w']*"})].concat(c)},l={cN:"container",b:"{",e:"}",c:t.c};return{aliases:["hs"],k:"let in if then else case of where do module import hiding qualified type data newtype deriving class instance as default infix infixl infixr foreign export ccall stdcall cplusplus jvm dotnet safe unsafe family forall mdo proc rec",c:[{cN:"module",b:"\\bmodule\\b",e:"where",k:"module where",c:[t].concat(c),i:"\\W\\.|;"},{cN:"import",b:"\\bimport\\b",e:"$",k:"import|0 qualified as hiding",c:[t].concat(c),i:"\\W\\.|;"},{cN:"class",b:"^(\\s*)?(class|instance)\\b",e:"where",k:"class family instance where",c:[n,t].concat(c)},{cN:"typedef",b:"\\b(data|(new)?type)\\b",e:"$",k:"data family type newtype deriving",c:[a,n,t,l].concat(c)},{cN:"default",bK:"default",e:"$",c:[n,t].concat(c)},{cN:"infix",bK:"infix infixl infixr",e:"$",c:[e.CNM].concat(c)},{cN:"foreign",b:"\\bforeign\\b",e:"$",k:"foreign import export ccall stdcall cplusplus jvm dotnet safe unsafe",c:[n,e.QSM].concat(c)},{cN:"shebang",b:"#!\\/usr\\/bin\\/env runhaskell",e:"$"},a,i,e.QSM,e.CNM,n,e.inherit(e.TM,{b:"^[_a-z][\\w']*"}),{b:"->|<-"}].concat(c)}});hljs.registerLanguage("erlang-repl",function(r){return{k:{special_functions:"spawn spawn_link self",reserved:"after and andalso|10 band begin bnot bor bsl bsr bxor case catch cond div end fun if let not of or orelse|10 query receive rem try when xor"},c:[{cN:"prompt",b:"^[0-9]+> ",r:10},r.C("%","$"),{cN:"number",b:"\\b(\\d+#[a-fA-F0-9]+|\\d+(\\.\\d+)?([eE][-+]?\\d+)?)",r:0},r.ASM,r.QSM,{cN:"constant",b:"\\?(::)?([A-Z]\\w*(::)?)+"},{cN:"arrow",b:"->"},{cN:"ok",b:"ok"},{cN:"exclamation_mark",b:"!"},{cN:"function_or_atom",b:"(\\b[a-z'][a-zA-Z0-9_']*:[a-z'][a-zA-Z0-9_']*)|(\\b[a-z'][a-zA-Z0-9_']*)",r:0},{cN:"variable",b:"[A-Z][a-zA-Z0-9_']*",r:0}]}});hljs.registerLanguage("vbnet",function(e){return{aliases:["vb"],cI:!0,k:{keyword:"addhandler addressof alias and andalso aggregate ansi as assembly auto binary by byref byval call case catch class compare const continue custom declare default delegate dim distinct do each equals else elseif end enum erase error event exit explicit finally for friend from function get global goto group handles if implements imports in inherits interface into is isfalse isnot istrue join key let lib like loop me mid mod module mustinherit mustoverride mybase myclass namespace narrowing new next not notinheritable notoverridable of off on operator option optional or order orelse overloads overridable overrides paramarray partial preserve private property protected public raiseevent readonly redim rem removehandler resume return select set shadows shared skip static step stop structure strict sub synclock take text then throw to try unicode until using when where while widening with withevents writeonly xor",built_in:"boolean byte cbool cbyte cchar cdate cdec cdbl char cint clng cobj csbyte cshort csng cstr ctype date decimal directcast double gettype getxmlnamespace iif integer long object sbyte short single string trycast typeof uinteger ulong ushort",literal:"true false nothing"},i:"//|{|}|endif|gosub|variant|wend",c:[e.inherit(e.QSM,{c:[{b:'""'}]}),e.C("'","$",{rB:!0,c:[{cN:"xmlDocTag",b:"'''|",c:[e.PWM]},{cN:"xmlDocTag",b:"",c:[e.PWM]}]}),e.CNM,{cN:"preprocessor",b:"#",e:"$",k:"if else elseif end region externalsource"}]}});hljs.registerLanguage("mizar",function(e){return{k:"environ vocabularies notations constructors definitions registrations theorems schemes requirements begin end definition registration cluster existence pred func defpred deffunc theorem proof let take assume then thus hence ex for st holds consider reconsider such that and in provided of as from be being by means equals implies iff redefine define now not or attr is mode suppose per cases set thesis contradiction scheme reserve struct correctness compatibility coherence symmetry assymetry reflexivity irreflexivity connectedness uniqueness commutativity idempotence involutiveness projectivity",c:[e.C("::","$")]}});hljs.registerLanguage("bash",function(e){var t={cN:"variable",v:[{b:/\$[\w\d#@][\w\d_]*/},{b:/\$\{(.*?)}/}]},s={cN:"string",b:/"/,e:/"/,c:[e.BE,t,{cN:"variable",b:/\$\(/,e:/\)/,c:[e.BE]}]},a={cN:"string",b:/'/,e:/'/};return{aliases:["sh","zsh"],l:/-?[a-z\.]+/,k:{keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",operator:"-ne -eq -lt -gt -f -d -e -s -l -a"},c:[{cN:"shebang",b:/^#![^\n]+sh\s*$/,r:10},{cN:"function",b:/\w[\w\d_]*\s*\(\s*\)\s*\{/,rB:!0,c:[e.inherit(e.TM,{b:/\w[\w\d_]*/})],r:0},e.HCM,e.NM,s,a,t]}});hljs.registerLanguage("markdown",function(e){return{aliases:["md","mkdown","mkd"],c:[{cN:"header",v:[{b:"^#{1,6}",e:"$"},{b:"^.+?\\n[=-]{2,}$"}]},{b:"<",e:">",sL:"xml",r:0},{cN:"bullet",b:"^([*+-]|(\\d+\\.))\\s+"},{cN:"strong",b:"[*_]{2}.+?[*_]{2}"},{cN:"emphasis",v:[{b:"\\*.+?\\*"},{b:"_.+?_",r:0}]},{cN:"blockquote",b:"^>\\s+",e:"$"},{cN:"code",v:[{b:"`.+?`"},{b:"^( {4}| )",e:"$",r:0}]},{cN:"horizontal_rule",b:"^[-\\*]{3,}",e:"$"},{b:"\\[.+?\\][\\(\\[].*?[\\)\\]]",rB:!0,c:[{cN:"link_label",b:"\\[",e:"\\]",eB:!0,rE:!0,r:0},{cN:"link_url",b:"\\]\\(",e:"\\)",eB:!0,eE:!0},{cN:"link_reference",b:"\\]\\[",e:"\\]",eB:!0,eE:!0}],r:10},{b:"^\\[.+\\]:",rB:!0,c:[{cN:"link_reference",b:"\\[",e:"\\]:",eB:!0,eE:!0,starts:{cN:"link_url",e:"$"}}]}]}});hljs.registerLanguage("handlebars",function(e){var a="each in with if else unless bindattr action collection debugger log outlet template unbound view yield";return{aliases:["hbs","html.hbs","html.handlebars"],cI:!0,sL:"xml",c:[{cN:"expression",b:"{{",e:"}}",c:[{cN:"begin-block",b:"#[a-zA-Z- .]+",k:a},{cN:"string",b:'"',e:'"'},{cN:"end-block",b:"\\/[a-zA-Z- .]+",k:a},{cN:"variable",b:"[a-zA-Z-.]+",k:a}]}]}});hljs.registerLanguage("javascript",function(e){return{aliases:["js"],k:{keyword:"in of if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const export super debugger as async await",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect Promise"},c:[{cN:"pi",r:10,b:/^\s*['"]use (strict|asm)['"]/},e.ASM,e.QSM,{cN:"string",b:"`",e:"`",c:[e.BE,{cN:"subst",b:"\\$\\{",e:"\\}"}]},e.CLCM,e.CBCM,{cN:"number",v:[{b:"\\b(0[bB][01]+)"},{b:"\\b(0[oO][0-7]+)"},{b:e.CNR}],r:0},{b:"("+e.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[e.CLCM,e.CBCM,e.RM,{b:/\s*[);\]]/,r:0,sL:"xml"}],r:0},{cN:"function",bK:"function",e:/\{/,eE:!0,c:[e.inherit(e.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/}),{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,c:[e.CLCM,e.CBCM]}],i:/\[|%/},{b:/\$[(.]/},{b:"\\."+e.IR,r:0},{bK:"import",e:"[;$]",k:"import from as",c:[e.ASM,e.QSM]},{cN:"class",bK:"class",e:/[{;=]/,eE:!0,i:/[:"\[\]]/,c:[{bK:"extends"},e.UTM]}],i:/#/}});hljs.registerLanguage("r",function(e){var r="([a-zA-Z]|\\.[a-zA-Z.])[a-zA-Z0-9._]*";return{c:[e.HCM,{b:r,l:r,k:{keyword:"function if in break next repeat else for return switch while try tryCatch stop warning require library attach detach source setMethod setGeneric setGroupGeneric setClass ...",literal:"NULL NA TRUE FALSE T F Inf NaN NA_integer_|10 NA_real_|10 NA_character_|10 NA_complex_|10"},r:0},{cN:"number",b:"0[xX][0-9a-fA-F]+[Li]?\\b",r:0},{cN:"number",b:"\\d+(?:[eE][+\\-]?\\d*)?L\\b",r:0},{cN:"number",b:"\\d+\\.(?!\\d)(?:i\\b)?",r:0},{cN:"number",b:"\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d*)?i?\\b",r:0},{cN:"number",b:"\\.\\d+(?:[eE][+\\-]?\\d*)?i?\\b",r:0},{b:"`",e:"`",r:0},{cN:"string",c:[e.BE],v:[{b:'"',e:'"'},{b:"'",e:"'"}]}]}});hljs.registerLanguage("ini",function(e){var c={cN:"string",c:[e.BE],v:[{b:"'''",e:"'''",r:10},{b:'"""',e:'"""',r:10},{b:'"',e:'"'},{b:"'",e:"'"}]};return{aliases:["toml"],cI:!0,i:/\S/,c:[e.C(";","$"),e.HCM,{cN:"title",b:/^\s*\[+/,e:/\]+/},{cN:"setting",b:/^[a-z0-9\[\]_-]+\s*=\s*/,e:"$",c:[{cN:"value",eW:!0,k:"on off true false yes no",c:[{cN:"variable",v:[{b:/\$[\w\d"][\w\d_]*/},{b:/\$\{(.*?)}/}]},c,{cN:"number",b:/([\+\-]+)?[\d]+_[\d_]+/},e.NM],r:0}]}]}});hljs.registerLanguage("apache",function(e){var r={cN:"number",b:"[\\$%]\\d+"};return{aliases:["apacheconf"],cI:!0,c:[e.HCM,{cN:"tag",b:""},{cN:"keyword",b:/\w+/,r:0,k:{common:"order deny allow setenv rewriterule rewriteengine rewritecond documentroot sethandler errordocument loadmodule options header listen serverroot servername"},starts:{e:/$/,r:0,k:{literal:"on off all"},c:[{cN:"sqbracket",b:"\\s\\[",e:"\\]$"},{cN:"cbracket",b:"[\\$%]\\{",e:"\\}",c:["self",r]},r,e.QSM]}}],i:/\S/}});hljs.registerLanguage("sql",function(e){var t=e.C("--","$");return{cI:!0,i:/[<>{}*]/,c:[{cN:"operator",bK:"begin end start commit rollback savepoint lock alter create drop rename call delete do handler insert load replace select truncate update set show pragma grant merge describe use explain help declare prepare execute deallocate release unlock purge reset change stop analyze cache flush optimize repair kill install uninstall checksum restore check backup revoke",e:/;/,eW:!0,k:{keyword:"abort abs absolute acc acce accep accept access accessed accessible account acos action activate add addtime admin administer advanced advise aes_decrypt aes_encrypt after agent aggregate ali alia alias allocate allow alter always analyze ancillary and any anydata anydataset anyschema anytype apply archive archived archivelog are as asc ascii asin assembly assertion associate asynchronous at atan atn2 attr attri attrib attribu attribut attribute attributes audit authenticated authentication authid authors auto autoallocate autodblink autoextend automatic availability avg backup badfile basicfile before begin beginning benchmark between bfile bfile_base big bigfile bin binary_double binary_float binlog bit_and bit_count bit_length bit_or bit_xor bitmap blob_base block blocksize body both bound buffer_cache buffer_pool build bulk by byte byteordermark bytes c cache caching call calling cancel capacity cascade cascaded case cast catalog category ceil ceiling chain change changed char_base char_length character_length characters characterset charindex charset charsetform charsetid check checksum checksum_agg child choose chr chunk class cleanup clear client clob clob_base clone close cluster_id cluster_probability cluster_set clustering coalesce coercibility col collate collation collect colu colum column column_value columns columns_updated comment commit compact compatibility compiled complete composite_limit compound compress compute concat concat_ws concurrent confirm conn connec connect connect_by_iscycle connect_by_isleaf connect_by_root connect_time connection consider consistent constant constraint constraints constructor container content contents context contributors controlfile conv convert convert_tz corr corr_k corr_s corresponding corruption cos cost count count_big counted covar_pop covar_samp cpu_per_call cpu_per_session crc32 create creation critical cross cube cume_dist curdate current current_date current_time current_timestamp current_user cursor curtime customdatum cycle d data database databases datafile datafiles datalength date_add date_cache date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts day day_to_second dayname dayofmonth dayofweek dayofyear days db_role_change dbtimezone ddl deallocate declare decode decompose decrement decrypt deduplicate def defa defau defaul default defaults deferred defi defin define degrees delayed delegate delete delete_all delimited demand dense_rank depth dequeue des_decrypt des_encrypt des_key_file desc descr descri describ describe descriptor deterministic diagnostics difference dimension direct_load directory disable disable_all disallow disassociate discardfile disconnect diskgroup distinct distinctrow distribute distributed div do document domain dotnet double downgrade drop dumpfile duplicate duration e each edition editionable editions element ellipsis else elsif elt empty enable enable_all enclosed encode encoding encrypt end end-exec endian enforced engine engines enqueue enterprise entityescaping eomonth error errors escaped evalname evaluate event eventdata events except exception exceptions exchange exclude excluding execu execut execute exempt exists exit exp expire explain export export_set extended extent external external_1 external_2 externally extract f failed failed_login_attempts failover failure far fast feature_set feature_value fetch field fields file file_name_convert filesystem_like_logging final finish first first_value fixed flash_cache flashback floor flush following follows for forall force form forma format found found_rows freelist freelists freepools fresh from from_base64 from_days ftp full function g general generated get get_format get_lock getdate getutcdate global global_name globally go goto grant grants greatest group group_concat group_id grouping grouping_id groups gtid_subtract guarantee guard handler hash hashkeys having hea head headi headin heading heap help hex hierarchy high high_priority hosts hour http i id ident_current ident_incr ident_seed identified identity idle_time if ifnull ignore iif ilike ilm immediate import in include including increment index indexes indexing indextype indicator indices inet6_aton inet6_ntoa inet_aton inet_ntoa infile initial initialized initially initrans inmemory inner innodb input insert install instance instantiable instr interface interleaved intersect into invalidate invisible is is_free_lock is_ipv4 is_ipv4_compat is_not is_not_null is_used_lock isdate isnull isolation iterate java join json json_exists k keep keep_duplicates key keys kill l language large last last_day last_insert_id last_value lax lcase lead leading least leaves left len lenght length less level levels library like like2 like4 likec limit lines link list listagg little ln load load_file lob lobs local localtime localtimestamp locate locator lock locked log log10 log2 logfile logfiles logging logical logical_reads_per_call logoff logon logs long loop low low_priority lower lpad lrtrim ltrim m main make_set makedate maketime managed management manual map mapping mask master master_pos_wait match matched materialized max maxextents maximize maxinstances maxlen maxlogfiles maxloghistory maxlogmembers maxsize maxtrans md5 measures median medium member memcompress memory merge microsecond mid migration min minextents minimum mining minus minute minvalue missing mod mode model modification modify module monitoring month months mount move movement multiset mutex n name name_const names nan national native natural nav nchar nclob nested never new newline next nextval no no_write_to_binlog noarchivelog noaudit nobadfile nocheck nocompress nocopy nocycle nodelay nodiscardfile noentityescaping noguarantee nokeep nologfile nomapping nomaxvalue nominimize nominvalue nomonitoring none noneditionable nonschema noorder nopr nopro noprom nopromp noprompt norely noresetlogs noreverse normal norowdependencies noschemacheck noswitch not nothing notice notrim novalidate now nowait nth_value nullif nulls num numb numbe nvarchar nvarchar2 object ocicoll ocidate ocidatetime ociduration ociinterval ociloblocator ocinumber ociref ocirefcursor ocirowid ocistring ocitype oct octet_length of off offline offset oid oidindex old on online only opaque open operations operator optimal optimize option optionally or oracle oracle_date oradata ord ordaudio orddicom orddoc order ordimage ordinality ordvideo organization orlany orlvary out outer outfile outline output over overflow overriding p package pad parallel parallel_enable parameters parent parse partial partition partitions pascal passing password password_grace_time password_lock_time password_reuse_max password_reuse_time password_verify_function patch path patindex pctincrease pctthreshold pctused pctversion percent percent_rank percentile_cont percentile_disc performance period period_add period_diff permanent physical pi pipe pipelined pivot pluggable plugin policy position post_transaction pow power pragma prebuilt precedes preceding precision prediction prediction_cost prediction_details prediction_probability prediction_set prepare present preserve prior priority private private_sga privileges procedural procedure procedure_analyze processlist profiles project prompt protection public publishingservername purge quarter query quick quiesce quota quotename radians raise rand range rank raw read reads readsize rebuild record records recover recovery recursive recycle redo reduced ref reference referenced references referencing refresh regexp_like register regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy reject rekey relational relative relaylog release release_lock relies_on relocate rely rem remainder rename repair repeat replace replicate replication required reset resetlogs resize resource respect restore restricted result result_cache resumable resume retention return returning returns reuse reverse revoke right rlike role roles rollback rolling rollup round row row_count rowdependencies rowid rownum rows rtrim rules safe salt sample save savepoint sb1 sb2 sb4 scan schema schemacheck scn scope scroll sdo_georaster sdo_topo_geometry search sec_to_time second section securefile security seed segment select self sequence sequential serializable server servererror session session_user sessions_per_user set sets settings sha sha1 sha2 share shared shared_pool short show shrink shutdown si_averagecolor si_colorhistogram si_featurelist si_positionalcolor si_stillimage si_texture siblings sid sign sin size size_t sizes skip slave sleep smalldatetimefromparts smallfile snapshot some soname sort soundex source space sparse spfile split sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_small_result sql_variant_property sqlcode sqldata sqlerror sqlname sqlstate sqrt square standalone standby start starting startup statement static statistics stats_binomial_test stats_crosstab stats_ks_test stats_mode stats_mw_test stats_one_way_anova stats_t_test_ stats_t_test_indep stats_t_test_one stats_t_test_paired stats_wsr_test status std stddev stddev_pop stddev_samp stdev stop storage store stored str str_to_date straight_join strcmp strict string struct stuff style subdate subpartition subpartitions substitutable substr substring subtime subtring_index subtype success sum suspend switch switchoffset switchover sync synchronous synonym sys sys_xmlagg sysasm sysaux sysdate sysdatetimeoffset sysdba sysoper system system_user sysutcdatetime t table tables tablespace tan tdo template temporary terminated tertiary_weights test than then thread through tier ties time time_format time_zone timediff timefromparts timeout timestamp timestampadd timestampdiff timezone_abbr timezone_minute timezone_region to to_base64 to_date to_days to_seconds todatetimeoffset trace tracking transaction transactional translate translation treat trigger trigger_nestlevel triggers trim truncate try_cast try_convert try_parse type ub1 ub2 ub4 ucase unarchived unbounded uncompress under undo unhex unicode uniform uninstall union unique unix_timestamp unknown unlimited unlock unpivot unrecoverable unsafe unsigned until untrusted unusable unused update updated upgrade upped upper upsert url urowid usable usage use use_stored_outlines user user_data user_resources users using utc_date utc_timestamp uuid uuid_short validate validate_password_strength validation valist value values var var_samp varcharc vari varia variab variabl variable variables variance varp varraw varrawc varray verify version versions view virtual visible void wait wallet warning warnings week weekday weekofyear wellformed when whene whenev wheneve whenever where while whitespace with within without work wrapped xdb xml xmlagg xmlattributes xmlcast xmlcolattval xmlelement xmlexists xmlforest xmlindex xmlnamespaces xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltype xor year year_to_month years yearweek",literal:"true false null",built_in:"array bigint binary bit blob boolean char character date dec decimal float int int8 integer interval number numeric real record serial serial8 smallint text varchar varying void"},c:[{cN:"string",b:"'",e:"'",c:[e.BE,{b:"''"}]},{cN:"string",b:'"',e:'"',c:[e.BE,{b:'""'}]},{cN:"string",b:"`",e:"`",c:[e.BE]},e.CNM,e.CBCM,t]},e.CBCM,t]}});hljs.registerLanguage("vhdl",function(e){var t="\\d(_|\\d)*",r="[eE][-+]?"+t,n=t+"(\\."+t+")?("+r+")?",o="\\w+",i=t+"#"+o+"(\\."+o+")?#("+r+")?",a="\\b("+i+"|"+n+")";return{cI:!0,k:{keyword:"abs access after alias all and architecture array assert attribute begin block body buffer bus case component configuration constant context cover disconnect downto default else elsif end entity exit fairness file for force function generate generic group guarded if impure in inertial inout is label library linkage literal loop map mod nand new next nor not null of on open or others out package port postponed procedure process property protected pure range record register reject release rem report restrict restrict_guarantee return rol ror select sequence severity shared signal sla sll sra srl strong subtype then to transport type unaffected units until use variable vmode vprop vunit wait when while with xnor xor",typename:"boolean bit character severity_level integer time delay_length natural positive string bit_vector file_open_kind file_open_status std_ulogic std_ulogic_vector std_logic std_logic_vector unsigned signed boolean_vector integer_vector real_vector time_vector"},i:"{",c:[e.CBCM,e.C("--","$"),e.QSM,{cN:"number",b:a,r:0},{cN:"literal",b:"'(U|X|0|1|Z|W|L|H|-)'",c:[e.BE]},{cN:"attribute",b:"'[A-Za-z](_?[A-Za-z0-9])*",c:[e.BE]}]}});hljs.registerLanguage("django",function(e){var t={cN:"filter",b:/\|[A-Za-z]+:?/,k:"truncatewords removetags linebreaksbr yesno get_digit timesince random striptags filesizeformat escape linebreaks length_is ljust rjust cut urlize fix_ampersands title floatformat capfirst pprint divisibleby add make_list unordered_list urlencode timeuntil urlizetrunc wordcount stringformat linenumbers slice date dictsort dictsortreversed default_if_none pluralize lower join center default truncatewords_html upper length phone2numeric wordwrap time addslashes slugify first escapejs force_escape iriencode last safe safeseq truncatechars localize unlocalize localtime utc timezone",c:[{cN:"argument",b:/"/,e:/"/},{cN:"argument",b:/'/,e:/'/}]};return{aliases:["jinja"],cI:!0,sL:"xml",c:[e.C(/\{%\s*comment\s*%}/,/\{%\s*endcomment\s*%}/),e.C(/\{#/,/#}/),{cN:"template_tag",b:/\{%/,e:/%}/,k:"comment endcomment load templatetag ifchanged endifchanged if endif firstof for endfor in ifnotequal endifnotequal widthratio extends include spaceless endspaceless regroup by as ifequal endifequal ssi now with cycle url filter endfilter debug block endblock else autoescape endautoescape csrf_token empty elif endwith static trans blocktrans endblocktrans get_static_prefix get_media_prefix plural get_current_language language get_available_languages get_current_language_bidi get_language_info get_language_info_list localize endlocalize localtime endlocaltime timezone endtimezone get_current_timezone verbatim",c:[t]},{cN:"variable",b:/\{\{/,e:/}}/,c:[t]}]}});hljs.registerLanguage("typescript",function(e){var r={keyword:"in if for while finally var new function|0 do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const class public private protected get set super static implements enum export import declare type namespace abstract",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document any number boolean string void"};return{aliases:["ts"],k:r,c:[{cN:"pi",b:/^\s*['"]use strict['"]/,r:0},e.ASM,e.QSM,e.CLCM,e.CBCM,{cN:"number",v:[{b:"\\b(0[bB][01]+)"},{b:"\\b(0[oO][0-7]+)"},{b:e.CNR}],r:0},{b:"("+e.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[e.CLCM,e.CBCM,e.RM],r:0},{cN:"function",b:"function",e:/[\{;]/,eE:!0,k:r,c:["self",e.inherit(e.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/}),{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,k:r,c:[e.CLCM,e.CBCM],i:/["'\(]/}],i:/\[|%/,r:0},{cN:"constructor",bK:"constructor",e:/\{/,eE:!0,r:10},{cN:"module",bK:"module",e:/\{/,eE:!0},{cN:"interface",bK:"interface",e:/\{/,eE:!0,k:"interface extends"},{b:/\$[(.]/},{b:"\\."+e.IR,r:0}]}});hljs.registerLanguage("makefile",function(e){var a={cN:"variable",b:/\$\(/,e:/\)/,c:[e.BE]};return{aliases:["mk","mak"],c:[e.HCM,{b:/^\w+\s*\W*=/,rB:!0,r:0,starts:{cN:"constant",e:/\s*\W*=/,eE:!0,starts:{e:/$/,r:0,c:[a]}}},{cN:"title",b:/^[\w]+:\s*$/},{cN:"phony",b:/^\.PHONY:/,e:/$/,k:".PHONY",l:/[\.\w]+/},{b:/^\t+/,e:/$/,r:0,c:[e.QSM,a]}]}});hljs.registerLanguage("ruleslanguage",function(T){return{k:{keyword:"BILL_PERIOD BILL_START BILL_STOP RS_EFFECTIVE_START RS_EFFECTIVE_STOP RS_JURIS_CODE RS_OPCO_CODE INTDADDATTRIBUTE|5 INTDADDVMSG|5 INTDBLOCKOP|5 INTDBLOCKOPNA|5 INTDCLOSE|5 INTDCOUNT|5 INTDCOUNTSTATUSCODE|5 INTDCREATEMASK|5 INTDCREATEDAYMASK|5 INTDCREATEFACTORMASK|5 INTDCREATEHANDLE|5 INTDCREATEOVERRIDEDAYMASK|5 INTDCREATEOVERRIDEMASK|5 INTDCREATESTATUSCODEMASK|5 INTDCREATETOUPERIOD|5 INTDDELETE|5 INTDDIPTEST|5 INTDEXPORT|5 INTDGETERRORCODE|5 INTDGETERRORMESSAGE|5 INTDISEQUAL|5 INTDJOIN|5 INTDLOAD|5 INTDLOADACTUALCUT|5 INTDLOADDATES|5 INTDLOADHIST|5 INTDLOADLIST|5 INTDLOADLISTDATES|5 INTDLOADLISTENERGY|5 INTDLOADLISTHIST|5 INTDLOADRELATEDCHANNEL|5 INTDLOADSP|5 INTDLOADSTAGING|5 INTDLOADUOM|5 INTDLOADUOMDATES|5 INTDLOADUOMHIST|5 INTDLOADVERSION|5 INTDOPEN|5 INTDREADFIRST|5 INTDREADNEXT|5 INTDRECCOUNT|5 INTDRELEASE|5 INTDREPLACE|5 INTDROLLAVG|5 INTDROLLPEAK|5 INTDSCALAROP|5 INTDSCALE|5 INTDSETATTRIBUTE|5 INTDSETDSTPARTICIPANT|5 INTDSETSTRING|5 INTDSETVALUE|5 INTDSETVALUESTATUS|5 INTDSHIFTSTARTTIME|5 INTDSMOOTH|5 INTDSORT|5 INTDSPIKETEST|5 INTDSUBSET|5 INTDTOU|5 INTDTOURELEASE|5 INTDTOUVALUE|5 INTDUPDATESTATS|5 INTDVALUE|5 STDEV INTDDELETEEX|5 INTDLOADEXACTUAL|5 INTDLOADEXCUT|5 INTDLOADEXDATES|5 INTDLOADEX|5 INTDLOADEXRELATEDCHANNEL|5 INTDSAVEEX|5 MVLOAD|5 MVLOADACCT|5 MVLOADACCTDATES|5 MVLOADACCTHIST|5 MVLOADDATES|5 MVLOADHIST|5 MVLOADLIST|5 MVLOADLISTDATES|5 MVLOADLISTHIST|5 IF FOR NEXT DONE SELECT END CALL ABORT CLEAR CHANNEL FACTOR LIST NUMBER OVERRIDE SET WEEK DISTRIBUTIONNODE ELSE WHEN THEN OTHERWISE IENUM CSV INCLUDE LEAVE RIDER SAVE DELETE NOVALUE SECTION WARN SAVE_UPDATE DETERMINANT LABEL REPORT REVENUE EACH IN FROM TOTAL CHARGE BLOCK AND OR CSV_FILE RATE_CODE AUXILIARY_DEMAND UIDACCOUNT RS BILL_PERIOD_SELECT HOURS_PER_MONTH INTD_ERROR_STOP SEASON_SCHEDULE_NAME ACCOUNTFACTOR ARRAYUPPERBOUND CALLSTOREDPROC GETADOCONNECTION GETCONNECT GETDATASOURCE GETQUALIFIER GETUSERID HASVALUE LISTCOUNT LISTOP LISTUPDATE LISTVALUE PRORATEFACTOR RSPRORATE SETBINPATH SETDBMONITOR WQ_OPEN BILLINGHOURS DATE DATEFROMFLOAT DATETIMEFROMSTRING DATETIMETOSTRING DATETOFLOAT DAY DAYDIFF DAYNAME DBDATETIME HOUR MINUTE MONTH MONTHDIFF MONTHHOURS MONTHNAME ROUNDDATE SAMEWEEKDAYLASTYEAR SECOND WEEKDAY WEEKDIFF YEAR YEARDAY YEARSTR COMPSUM HISTCOUNT HISTMAX HISTMIN HISTMINNZ HISTVALUE MAXNRANGE MAXRANGE MINRANGE COMPIKVA COMPKVA COMPKVARFROMKQKW COMPLF IDATTR FLAG LF2KW LF2KWH MAXKW POWERFACTOR READING2USAGE AVGSEASON MAXSEASON MONTHLYMERGE SEASONVALUE SUMSEASON ACCTREADDATES ACCTTABLELOAD CONFIGADD CONFIGGET CREATEOBJECT CREATEREPORT EMAILCLIENT EXPBLKMDMUSAGE EXPMDMUSAGE EXPORT_USAGE FACTORINEFFECT GETUSERSPECIFIEDSTOP INEFFECT ISHOLIDAY RUNRATE SAVE_PROFILE SETREPORTTITLE USEREXIT WATFORRUNRATE TO TABLE ACOS ASIN ATAN ATAN2 BITAND CEIL COS COSECANT COSH COTANGENT DIVQUOT DIVREM EXP FABS FLOOR FMOD FREPM FREXPN LOG LOG10 MAX MAXN MIN MINNZ MODF POW ROUND ROUND2VALUE ROUNDINT SECANT SIN SINH SQROOT TAN TANH FLOAT2STRING FLOAT2STRINGNC INSTR LEFT LEN LTRIM MID RIGHT RTRIM STRING STRINGNC TOLOWER TOUPPER TRIM NUMDAYS READ_DATE STAGING",built_in:"IDENTIFIER OPTIONS XML_ELEMENT XML_OP XML_ELEMENT_OF DOMDOCCREATE DOMDOCLOADFILE DOMDOCLOADXML DOMDOCSAVEFILE DOMDOCGETROOT DOMDOCADDPI DOMNODEGETNAME DOMNODEGETTYPE DOMNODEGETVALUE DOMNODEGETCHILDCT DOMNODEGETFIRSTCHILD DOMNODEGETSIBLING DOMNODECREATECHILDELEMENT DOMNODESETATTRIBUTE DOMNODEGETCHILDELEMENTCT DOMNODEGETFIRSTCHILDELEMENT DOMNODEGETSIBLINGELEMENT DOMNODEGETATTRIBUTECT DOMNODEGETATTRIBUTEI DOMNODEGETATTRIBUTEBYNAME DOMNODEGETBYNAME"},c:[T.CLCM,T.CBCM,T.ASM,T.QSM,T.CNM,{cN:"array",v:[{b:"#\\s+[a-zA-Z\\ \\.]*",r:0},{b:"#[a-zA-Z\\ \\.]+"}]}]}});hljs.registerLanguage("clojure",function(e){var t={built_in:"def defonce cond apply if-not if-let if not not= = < > <= >= == + / * - rem quot neg? pos? delay? symbol? keyword? true? false? integer? empty? coll? list? set? ifn? fn? associative? sequential? sorted? counted? reversible? number? decimal? class? distinct? isa? float? rational? reduced? ratio? odd? even? char? seq? vector? string? map? nil? contains? zero? instance? not-every? not-any? libspec? -> ->> .. . inc compare do dotimes mapcat take remove take-while drop letfn drop-last take-last drop-while while intern condp case reduced cycle split-at split-with repeat replicate iterate range merge zipmap declare line-seq sort comparator sort-by dorun doall nthnext nthrest partition eval doseq await await-for let agent atom send send-off release-pending-sends add-watch mapv filterv remove-watch agent-error restart-agent set-error-handler error-handler set-error-mode! error-mode shutdown-agents quote var fn loop recur throw try monitor-enter monitor-exit defmacro defn defn- macroexpand macroexpand-1 for dosync and or when when-not when-let comp juxt partial sequence memoize constantly complement identity assert peek pop doto proxy defstruct first rest cons defprotocol cast coll deftype defrecord last butlast sigs reify second ffirst fnext nfirst nnext defmulti defmethod meta with-meta ns in-ns create-ns import refer keys select-keys vals key val rseq name namespace promise into transient persistent! conj! assoc! dissoc! pop! disj! use class type num float double short byte boolean bigint biginteger bigdec print-method print-dup throw-if printf format load compile get-in update-in pr pr-on newline flush read slurp read-line subvec with-open memfn time re-find re-groups rand-int rand mod locking assert-valid-fdecl alias resolve ref deref refset swap! reset! set-validator! compare-and-set! alter-meta! reset-meta! commute get-validator alter ref-set ref-history-count ref-min-history ref-max-history ensure sync io! new next conj set! to-array future future-call into-array aset gen-class reduce map filter find empty hash-map hash-set sorted-map sorted-map-by sorted-set sorted-set-by vec vector seq flatten reverse assoc dissoc list disj get union difference intersection extend extend-type extend-protocol int nth delay count concat chunk chunk-buffer chunk-append chunk-first chunk-rest max min dec unchecked-inc-int unchecked-inc unchecked-dec-inc unchecked-dec unchecked-negate unchecked-add-int unchecked-add unchecked-subtract-int unchecked-subtract chunk-next chunk-cons chunked-seq? prn vary-meta lazy-seq spread list* str find-keyword keyword symbol gensym force rationalize"},r="a-zA-Z_\\-!.?+*=<>&#'",n="["+r+"]["+r+"0-9/;:]*",a="[-+]?\\d+(\\.\\d+)?",o={b:n,r:0},s={cN:"number",b:a,r:0},c=e.inherit(e.QSM,{i:null}),i=e.C(";","$",{r:0}),d={cN:"literal",b:/\b(true|false|nil)\b/},l={cN:"collection",b:"[\\[\\{]",e:"[\\]\\}]"},m={cN:"comment",b:"\\^"+n},p=e.C("\\^\\{","\\}"),u={cN:"attribute",b:"[:]"+n},f={cN:"list",b:"\\(",e:"\\)"},h={eW:!0,r:0},y={k:t,l:n,cN:"keyword",b:n,starts:h},b=[f,c,m,p,i,u,l,s,d,o];return f.c=[e.C("comment",""),y,h],h.c=b,l.c=b,{aliases:["clj"],i:/\S/,c:[f,c,m,p,i,u,l,s,d]}});hljs.registerLanguage("css",function(e){var c="[a-zA-Z-][a-zA-Z0-9_-]*",a={cN:"function",b:c+"\\(",rB:!0,eE:!0,e:"\\("},r={cN:"rule",b:/[A-Z\_\.\-]+\s*:/,rB:!0,e:";",eW:!0,c:[{cN:"attribute",b:/\S/,e:":",eE:!0,starts:{cN:"value",eW:!0,eE:!0,c:[a,e.CSSNM,e.QSM,e.ASM,e.CBCM,{cN:"hexcolor",b:"#[0-9A-Fa-f]+"},{cN:"important",b:"!important"}]}}]};return{cI:!0,i:/[=\/|'\$]/,c:[e.CBCM,{cN:"id",b:/\#[A-Za-z0-9_-]+/},{cN:"class",b:/\.[A-Za-z0-9_-]+/},{cN:"attr_selector",b:/\[/,e:/\]/,i:"$"},{cN:"pseudo",b:/:(:)?[a-zA-Z0-9\_\-\+\(\)"']+/},{cN:"at_rule",b:"@(font-face|page)",l:"[a-z-]+",k:"font-face page"},{cN:"at_rule",b:"@",e:"[{;]",c:[{cN:"keyword",b:/\S+/},{b:/\s/,eW:!0,eE:!0,r:0,c:[a,e.ASM,e.QSM,e.CSSNM]}]},{cN:"tag",b:c,r:0},{cN:"rules",b:"{",e:"}",i:/\S/,c:[e.CBCM,r]}]}});hljs.registerLanguage("delphi",function(e){var r="exports register file shl array record property for mod while set ally label uses raise not stored class safecall var interface or private static exit index inherited to else stdcall override shr asm far resourcestring finalization packed virtual out and protected library do xorwrite goto near function end div overload object unit begin string on inline repeat until destructor write message program with read initialization except default nil if case cdecl in downto threadvar of try pascal const external constructor type public then implementation finally published procedure",t=[e.CLCM,e.C(/\{/,/\}/,{r:0}),e.C(/\(\*/,/\*\)/,{r:10})],i={cN:"string",b:/'/,e:/'/,c:[{b:/''/}]},c={cN:"string",b:/(#\d+)+/},o={b:e.IR+"\\s*=\\s*class\\s*\\(",rB:!0,c:[e.TM]},n={cN:"function",bK:"function constructor destructor procedure",e:/[:;]/,k:"function constructor|10 destructor|10 procedure|10",c:[e.TM,{cN:"params",b:/\(/,e:/\)/,k:r,c:[i,c]}].concat(t)};return{cI:!0,k:r,i:/"|\$[G-Zg-z]|\/\*|<\/|\|/,c:[i,c,e.NM,o,n].concat(t)}});hljs.registerLanguage("applescript",function(e){var t=e.inherit(e.QSM,{i:""}),r={cN:"params",b:"\\(",e:"\\)",c:["self",e.CNM,t]},o=e.C("--","$"),n=e.C("\\(\\*","\\*\\)",{c:["self",o]}),a=[o,n,e.HCM];return{aliases:["osascript"],k:{keyword:"about above after against and around as at back before beginning behind below beneath beside between but by considering contain contains continue copy div does eighth else end equal equals error every exit fifth first for fourth from front get given global if ignoring in into is it its last local me middle mod my ninth not of on onto or over prop property put ref reference repeat returning script second set seventh since sixth some tell tenth that the|0 then third through thru timeout times to transaction try until where while whose with without",constant:"AppleScript false linefeed return pi quote result space tab true",type:"alias application boolean class constant date file integer list number real record string text",command:"activate beep count delay launch log offset read round run say summarize write",property:"character characters contents day frontmost id item length month name paragraph paragraphs rest reverse running time version weekday word words year"},c:[t,e.CNM,{cN:"type",b:"\\bPOSIX file\\b"},{cN:"command",b:"\\b(clipboard info|the clipboard|info for|list (disks|folder)|mount volume|path to|(close|open for) access|(get|set) eof|current date|do shell script|get volume settings|random number|set volume|system attribute|system info|time to GMT|(load|run|store) script|scripting components|ASCII (character|number)|localized string|choose (application|color|file|file name|folder|from list|remote application|URL)|display (alert|dialog))\\b|^\\s*return\\b"},{cN:"constant",b:"\\b(text item delimiters|current application|missing value)\\b"},{cN:"keyword",b:"\\b(apart from|aside from|instead of|out of|greater than|isn't|(doesn't|does not) (equal|come before|come after|contain)|(greater|less) than( or equal)?|(starts?|ends|begins?) with|contained by|comes (before|after)|a (ref|reference))\\b"},{cN:"property",b:"\\b(POSIX path|(date|time) string|quoted form)\\b"},{cN:"function_start",bK:"on",i:"[${=;\\n]",c:[e.UTM,r]}].concat(a),i:"//|->|=>|\\[\\["}});hljs.registerLanguage("mel",function(e){return{k:"int float string vector matrix if else switch case default while do for in break continue global proc return about abs addAttr addAttributeEditorNodeHelp addDynamic addNewShelfTab addPP addPanelCategory addPrefixToName advanceToNextDrivenKey affectedNet affects aimConstraint air alias aliasAttr align alignCtx alignCurve alignSurface allViewFit ambientLight angle angleBetween animCone animCurveEditor animDisplay animView annotate appendStringArray applicationName applyAttrPreset applyTake arcLenDimContext arcLengthDimension arclen arrayMapper art3dPaintCtx artAttrCtx artAttrPaintVertexCtx artAttrSkinPaintCtx artAttrTool artBuildPaintMenu artFluidAttrCtx artPuttyCtx artSelectCtx artSetPaintCtx artUserPaintCtx assignCommand assignInputDevice assignViewportFactories attachCurve attachDeviceAttr attachSurface attrColorSliderGrp attrCompatibility attrControlGrp attrEnumOptionMenu attrEnumOptionMenuGrp attrFieldGrp attrFieldSliderGrp attrNavigationControlGrp attrPresetEditWin attributeExists attributeInfo attributeMenu attributeQuery autoKeyframe autoPlace bakeClip bakeFluidShading bakePartialHistory bakeResults bakeSimulation basename basenameEx batchRender bessel bevel bevelPlus binMembership bindSkin blend2 blendShape blendShapeEditor blendShapePanel blendTwoAttr blindDataType boneLattice boundary boxDollyCtx boxZoomCtx bufferCurve buildBookmarkMenu buildKeyframeMenu button buttonManip CBG cacheFile cacheFileCombine cacheFileMerge cacheFileTrack camera cameraView canCreateManip canvas capitalizeString catch catchQuiet ceil changeSubdivComponentDisplayLevel changeSubdivRegion channelBox character characterMap characterOutlineEditor characterize chdir checkBox checkBoxGrp checkDefaultRenderGlobals choice circle circularFillet clamp clear clearCache clip clipEditor clipEditorCurrentTimeCtx clipSchedule clipSchedulerOutliner clipTrimBefore closeCurve closeSurface cluster cmdFileOutput cmdScrollFieldExecuter cmdScrollFieldReporter cmdShell coarsenSubdivSelectionList collision color colorAtPoint colorEditor colorIndex colorIndexSliderGrp colorSliderButtonGrp colorSliderGrp columnLayout commandEcho commandLine commandPort compactHairSystem componentEditor compositingInterop computePolysetVolume condition cone confirmDialog connectAttr connectControl connectDynamic connectJoint connectionInfo constrain constrainValue constructionHistory container containsMultibyte contextInfo control convertFromOldLayers convertIffToPsd convertLightmap convertSolidTx convertTessellation convertUnit copyArray copyFlexor copyKey copySkinWeights cos cpButton cpCache cpClothSet cpCollision cpConstraint cpConvClothToMesh cpForces cpGetSolverAttr cpPanel cpProperty cpRigidCollisionFilter cpSeam cpSetEdit cpSetSolverAttr cpSolver cpSolverTypes cpTool cpUpdateClothUVs createDisplayLayer createDrawCtx createEditor createLayeredPsdFile createMotionField createNewShelf createNode createRenderLayer createSubdivRegion cross crossProduct ctxAbort ctxCompletion ctxEditMode ctxTraverse currentCtx currentTime currentTimeCtx currentUnit curve curveAddPtCtx curveCVCtx curveEPCtx curveEditorCtx curveIntersect curveMoveEPCtx curveOnSurface curveSketchCtx cutKey cycleCheck cylinder dagPose date defaultLightListCheckBox defaultNavigation defineDataServer defineVirtualDevice deformer deg_to_rad delete deleteAttr deleteShadingGroupsAndMaterials deleteShelfTab deleteUI deleteUnusedBrushes delrandstr detachCurve detachDeviceAttr detachSurface deviceEditor devicePanel dgInfo dgdirty dgeval dgtimer dimWhen directKeyCtx directionalLight dirmap dirname disable disconnectAttr disconnectJoint diskCache displacementToPoly displayAffected displayColor displayCull displayLevelOfDetail displayPref displayRGBColor displaySmoothness displayStats displayString displaySurface distanceDimContext distanceDimension doBlur dolly dollyCtx dopeSheetEditor dot dotProduct doubleProfileBirailSurface drag dragAttrContext draggerContext dropoffLocator duplicate duplicateCurve duplicateSurface dynCache dynControl dynExport dynExpression dynGlobals dynPaintEditor dynParticleCtx dynPref dynRelEdPanel dynRelEditor dynamicLoad editAttrLimits editDisplayLayerGlobals editDisplayLayerMembers editRenderLayerAdjustment editRenderLayerGlobals editRenderLayerMembers editor editorTemplate effector emit emitter enableDevice encodeString endString endsWith env equivalent equivalentTol erf error eval evalDeferred evalEcho event exactWorldBoundingBox exclusiveLightCheckBox exec executeForEachObject exists exp expression expressionEditorListen extendCurve extendSurface extrude fcheck fclose feof fflush fgetline fgetword file fileBrowserDialog fileDialog fileExtension fileInfo filetest filletCurve filter filterCurve filterExpand filterStudioImport findAllIntersections findAnimCurves findKeyframe findMenuItem findRelatedSkinCluster finder firstParentOf fitBspline flexor floatEq floatField floatFieldGrp floatScrollBar floatSlider floatSlider2 floatSliderButtonGrp floatSliderGrp floor flow fluidCacheInfo fluidEmitter fluidVoxelInfo flushUndo fmod fontDialog fopen formLayout format fprint frameLayout fread freeFormFillet frewind fromNativePath fwrite gamma gauss geometryConstraint getApplicationVersionAsFloat getAttr getClassification getDefaultBrush getFileList getFluidAttr getInputDeviceRange getMayaPanelTypes getModifiers getPanel getParticleAttr getPluginResource getenv getpid glRender glRenderEditor globalStitch gmatch goal gotoBindPose grabColor gradientControl gradientControlNoAttr graphDollyCtx graphSelectContext graphTrackCtx gravity grid gridLayout group groupObjectsByName HfAddAttractorToAS HfAssignAS HfBuildEqualMap HfBuildFurFiles HfBuildFurImages HfCancelAFR HfConnectASToHF HfCreateAttractor HfDeleteAS HfEditAS HfPerformCreateAS HfRemoveAttractorFromAS HfSelectAttached HfSelectAttractors HfUnAssignAS hardenPointCurve hardware hardwareRenderPanel headsUpDisplay headsUpMessage help helpLine hermite hide hilite hitTest hotBox hotkey hotkeyCheck hsv_to_rgb hudButton hudSlider hudSliderButton hwReflectionMap hwRender hwRenderLoad hyperGraph hyperPanel hyperShade hypot iconTextButton iconTextCheckBox iconTextRadioButton iconTextRadioCollection iconTextScrollList iconTextStaticLabel ikHandle ikHandleCtx ikHandleDisplayScale ikSolver ikSplineHandleCtx ikSystem ikSystemInfo ikfkDisplayMethod illustratorCurves image imfPlugins inheritTransform insertJoint insertJointCtx insertKeyCtx insertKnotCurve insertKnotSurface instance instanceable instancer intField intFieldGrp intScrollBar intSlider intSliderGrp interToUI internalVar intersect iprEngine isAnimCurve isConnected isDirty isParentOf isSameObject isTrue isValidObjectName isValidString isValidUiName isolateSelect itemFilter itemFilterAttr itemFilterRender itemFilterType joint jointCluster jointCtx jointDisplayScale jointLattice keyTangent keyframe keyframeOutliner keyframeRegionCurrentTimeCtx keyframeRegionDirectKeyCtx keyframeRegionDollyCtx keyframeRegionInsertKeyCtx keyframeRegionMoveKeyCtx keyframeRegionScaleKeyCtx keyframeRegionSelectKeyCtx keyframeRegionSetKeyCtx keyframeRegionTrackCtx keyframeStats lassoContext lattice latticeDeformKeyCtx launch launchImageEditor layerButton layeredShaderPort layeredTexturePort layout layoutDialog lightList lightListEditor lightListPanel lightlink lineIntersection linearPrecision linstep listAnimatable listAttr listCameras listConnections listDeviceAttachments listHistory listInputDeviceAxes listInputDeviceButtons listInputDevices listMenuAnnotation listNodeTypes listPanelCategories listRelatives listSets listTransforms listUnselected listerEditor loadFluid loadNewShelf loadPlugin loadPluginLanguageResources loadPrefObjects localizedPanelLabel lockNode loft log longNameOf lookThru ls lsThroughFilter lsType lsUI Mayatomr mag makeIdentity makeLive makePaintable makeRoll makeSingleSurface makeTubeOn makebot manipMoveContext manipMoveLimitsCtx manipOptions manipRotateContext manipRotateLimitsCtx manipScaleContext manipScaleLimitsCtx marker match max memory menu menuBarLayout menuEditor menuItem menuItemToShelf menuSet menuSetPref messageLine min minimizeApp mirrorJoint modelCurrentTimeCtx modelEditor modelPanel mouse movIn movOut move moveIKtoFK moveKeyCtx moveVertexAlongDirection multiProfileBirailSurface mute nParticle nameCommand nameField namespace namespaceInfo newPanelItems newton nodeCast nodeIconButton nodeOutliner nodePreset nodeType noise nonLinear normalConstraint normalize nurbsBoolean nurbsCopyUVSet nurbsCube nurbsEditUV nurbsPlane nurbsSelect nurbsSquare nurbsToPoly nurbsToPolygonsPref nurbsToSubdiv nurbsToSubdivPref nurbsUVSet nurbsViewDirectionVector objExists objectCenter objectLayer objectType objectTypeUI obsoleteProc oceanNurbsPreviewPlane offsetCurve offsetCurveOnSurface offsetSurface openGLExtension openMayaPref optionMenu optionMenuGrp optionVar orbit orbitCtx orientConstraint outlinerEditor outlinerPanel overrideModifier paintEffectsDisplay pairBlend palettePort paneLayout panel panelConfiguration panelHistory paramDimContext paramDimension paramLocator parent parentConstraint particle particleExists particleInstancer particleRenderInfo partition pasteKey pathAnimation pause pclose percent performanceOptions pfxstrokes pickWalk picture pixelMove planarSrf plane play playbackOptions playblast plugAttr plugNode pluginInfo pluginResourceUtil pointConstraint pointCurveConstraint pointLight pointMatrixMult pointOnCurve pointOnSurface pointPosition poleVectorConstraint polyAppend polyAppendFacetCtx polyAppendVertex polyAutoProjection polyAverageNormal polyAverageVertex polyBevel polyBlendColor polyBlindData polyBoolOp polyBridgeEdge polyCacheMonitor polyCheck polyChipOff polyClipboard polyCloseBorder polyCollapseEdge polyCollapseFacet polyColorBlindData polyColorDel polyColorPerVertex polyColorSet polyCompare polyCone polyCopyUV polyCrease polyCreaseCtx polyCreateFacet polyCreateFacetCtx polyCube polyCut polyCutCtx polyCylinder polyCylindricalProjection polyDelEdge polyDelFacet polyDelVertex polyDuplicateAndConnect polyDuplicateEdge polyEditUV polyEditUVShell polyEvaluate polyExtrudeEdge polyExtrudeFacet polyExtrudeVertex polyFlipEdge polyFlipUV polyForceUV polyGeoSampler polyHelix polyInfo polyInstallAction polyLayoutUV polyListComponentConversion polyMapCut polyMapDel polyMapSew polyMapSewMove polyMergeEdge polyMergeEdgeCtx polyMergeFacet polyMergeFacetCtx polyMergeUV polyMergeVertex polyMirrorFace polyMoveEdge polyMoveFacet polyMoveFacetUV polyMoveUV polyMoveVertex polyNormal polyNormalPerVertex polyNormalizeUV polyOptUvs polyOptions polyOutput polyPipe polyPlanarProjection polyPlane polyPlatonicSolid polyPoke polyPrimitive polyPrism polyProjection polyPyramid polyQuad polyQueryBlindData polyReduce polySelect polySelectConstraint polySelectConstraintMonitor polySelectCtx polySelectEditCtx polySeparate polySetToFaceNormal polySewEdge polyShortestPathCtx polySmooth polySoftEdge polySphere polySphericalProjection polySplit polySplitCtx polySplitEdge polySplitRing polySplitVertex polyStraightenUVBorder polySubdivideEdge polySubdivideFacet polyToSubdiv polyTorus polyTransfer polyTriangulate polyUVSet polyUnite polyWedgeFace popen popupMenu pose pow preloadRefEd print progressBar progressWindow projFileViewer projectCurve projectTangent projectionContext projectionManip promptDialog propModCtx propMove psdChannelOutliner psdEditTextureFile psdExport psdTextureFile putenv pwd python querySubdiv quit rad_to_deg radial radioButton radioButtonGrp radioCollection radioMenuItemCollection rampColorPort rand randomizeFollicles randstate rangeControl readTake rebuildCurve rebuildSurface recordAttr recordDevice redo reference referenceEdit referenceQuery refineSubdivSelectionList refresh refreshAE registerPluginResource rehash reloadImage removeJoint removeMultiInstance removePanelCategory rename renameAttr renameSelectionList renameUI render renderGlobalsNode renderInfo renderLayerButton renderLayerParent renderLayerPostProcess renderLayerUnparent renderManip renderPartition renderQualityNode renderSettings renderThumbnailUpdate renderWindowEditor renderWindowSelectContext renderer reorder reorderDeformers requires reroot resampleFluid resetAE resetPfxToPolyCamera resetTool resolutionNode retarget reverseCurve reverseSurface revolve rgb_to_hsv rigidBody rigidSolver roll rollCtx rootOf rot rotate rotationInterpolation roundConstantRadius rowColumnLayout rowLayout runTimeCommand runup sampleImage saveAllShelves saveAttrPreset saveFluid saveImage saveInitialState saveMenu savePrefObjects savePrefs saveShelf saveToolSettings scale scaleBrushBrightness scaleComponents scaleConstraint scaleKey scaleKeyCtx sceneEditor sceneUIReplacement scmh scriptCtx scriptEditorInfo scriptJob scriptNode scriptTable scriptToShelf scriptedPanel scriptedPanelType scrollField scrollLayout sculpt searchPathArray seed selLoadSettings select selectContext selectCurveCV selectKey selectKeyCtx selectKeyframeRegionCtx selectMode selectPref selectPriority selectType selectedNodes selectionConnection separator setAttr setAttrEnumResource setAttrMapping setAttrNiceNameResource setConstraintRestPosition setDefaultShadingGroup setDrivenKeyframe setDynamic setEditCtx setEditor setFluidAttr setFocus setInfinity setInputDeviceMapping setKeyCtx setKeyPath setKeyframe setKeyframeBlendshapeTargetWts setMenuMode setNodeNiceNameResource setNodeTypeFlag setParent setParticleAttr setPfxToPolyCamera setPluginResource setProject setStampDensity setStartupMessage setState setToolTo setUITemplate setXformManip sets shadingConnection shadingGeometryRelCtx shadingLightRelCtx shadingNetworkCompare shadingNode shapeCompare shelfButton shelfLayout shelfTabLayout shellField shortNameOf showHelp showHidden showManipCtx showSelectionInTitle showShadingGroupAttrEditor showWindow sign simplify sin singleProfileBirailSurface size sizeBytes skinCluster skinPercent smoothCurve smoothTangentSurface smoothstep snap2to2 snapKey snapMode snapTogetherCtx snapshot soft softMod softModCtx sort sound soundControl source spaceLocator sphere sphrand spotLight spotLightPreviewPort spreadSheetEditor spring sqrt squareSurface srtContext stackTrace startString startsWith stitchAndExplodeShell stitchSurface stitchSurfacePoints strcmp stringArrayCatenate stringArrayContains stringArrayCount stringArrayInsertAtIndex stringArrayIntersector stringArrayRemove stringArrayRemoveAtIndex stringArrayRemoveDuplicates stringArrayRemoveExact stringArrayToString stringToStringArray strip stripPrefixFromName stroke subdAutoProjection subdCleanTopology subdCollapse subdDuplicateAndConnect subdEditUV subdListComponentConversion subdMapCut subdMapSewMove subdMatchTopology subdMirror subdToBlind subdToPoly subdTransferUVsToCache subdiv subdivCrease subdivDisplaySmoothness substitute substituteAllString substituteGeometry substring surface surfaceSampler surfaceShaderList swatchDisplayPort switchTable symbolButton symbolCheckBox sysFile system tabLayout tan tangentConstraint texLatticeDeformContext texManipContext texMoveContext texMoveUVShellContext texRotateContext texScaleContext texSelectContext texSelectShortestPathCtx texSmudgeUVContext texWinToolCtx text textCurves textField textFieldButtonGrp textFieldGrp textManip textScrollList textToShelf textureDisplacePlane textureHairColor texturePlacementContext textureWindow threadCount threePointArcCtx timeControl timePort timerX toNativePath toggle toggleAxis toggleWindowVisibility tokenize tokenizeList tolerance tolower toolButton toolCollection toolDropped toolHasOptions toolPropertyWindow torus toupper trace track trackCtx transferAttributes transformCompare transformLimits translator trim trunc truncateFluidCache truncateHairCache tumble tumbleCtx turbulence twoPointArcCtx uiRes uiTemplate unassignInputDevice undo undoInfo ungroup uniform unit unloadPlugin untangleUV untitledFileName untrim upAxis updateAE userCtx uvLink uvSnapshot validateShelfName vectorize view2dToolCtx viewCamera viewClipPlane viewFit viewHeadOn viewLookAt viewManip viewPlace viewSet visor volumeAxis vortex waitCursor warning webBrowser webBrowserPrefs whatIs window windowPref wire wireContext workspace wrinkle wrinkleContext writeTake xbmLangPathList xform",i:"/,sL:"php"},e={eW:!0,i:/]+/}]}]}]};return{aliases:["html","xhtml","rss","atom","xsl","plist"],cI:!0,c:[{cN:"doctype",b:"",r:10,c:[{b:"\\[",e:"\\]"}]},t.C("",{r:10}),{cN:"cdata",b:"<\\!\\[CDATA\\[",e:"\\]\\]>",r:10},{cN:"tag",b:"|$)",e:">",k:{title:"style"},c:[e],starts:{e:"",rE:!0,sL:"css"}},{cN:"tag",b:"|$)",e:">",k:{title:"script"},c:[e],starts:{e:"",rE:!0,sL:["actionscript","javascript","handlebars"]}},c,{cN:"pi",b:/<\?\w+/,e:/\?>/,r:10},{cN:"tag",b:"",c:[{cN:"title",b:/[^ \/><\n\t]+/,r:0},e]}]}});hljs.registerLanguage("ruby",function(e){var c="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?",r="and false then defined module in return redo if BEGIN retry end for true self when next until do begin unless END rescue nil else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor",b={cN:"doctag",b:"@[A-Za-z]+"},a={cN:"value",b:"#<",e:">"},n=[e.C("#","$",{c:[b]}),e.C("^\\=begin","^\\=end",{c:[b],r:10}),e.C("^__END__","\\n$")],s={cN:"subst",b:"#\\{",e:"}",k:r},t={cN:"string",c:[e.BE,s],v:[{b:/'/,e:/'/},{b:/"/,e:/"/},{b:/`/,e:/`/},{b:"%[qQwWx]?\\(",e:"\\)"},{b:"%[qQwWx]?\\[",e:"\\]"},{b:"%[qQwWx]?{",e:"}"},{b:"%[qQwWx]?<",e:">"},{b:"%[qQwWx]?/",e:"/"},{b:"%[qQwWx]?%",e:"%"},{b:"%[qQwWx]?-",e:"-"},{b:"%[qQwWx]?\\|",e:"\\|"},{b:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/}]},i={cN:"params",b:"\\(",e:"\\)",k:r},d=[t,a,{cN:"class",bK:"class module",e:"$|;",i:/=/,c:[e.inherit(e.TM,{b:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{cN:"inheritance",b:"<\\s*",c:[{cN:"parent",b:"("+e.IR+"::)?"+e.IR}]}].concat(n)},{cN:"function",bK:"def",e:"$|;",c:[e.inherit(e.TM,{b:c}),i].concat(n)},{cN:"constant",b:"(::)?(\\b[A-Z]\\w*(::)?)+",r:0},{cN:"symbol",b:e.UIR+"(\\!|\\?)?:",r:0},{cN:"symbol",b:":",c:[t,{b:c}],r:0},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{cN:"variable",b:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{b:"("+e.RSR+")\\s*",c:[a,{cN:"regexp",c:[e.BE,s],i:/\n/,v:[{b:"/",e:"/[a-z]*"},{b:"%r{",e:"}[a-z]*"},{b:"%r\\(",e:"\\)[a-z]*"},{b:"%r!",e:"![a-z]*"},{b:"%r\\[",e:"\\][a-z]*"}]}].concat(n),r:0}].concat(n);s.c=d,i.c=d;var o="[>?]>",l="[\\w#]+\\(\\w+\\):\\d+:\\d+>",u="(\\w+-)?\\d+\\.\\d+\\.\\d(p\\d+)?[^>]+>",N=[{b:/^\s*=>/,cN:"status",starts:{e:"$",c:d}},{cN:"prompt",b:"^("+o+"|"+l+"|"+u+")",starts:{e:"$",c:d}}];return{aliases:["rb","gemspec","podspec","thor","irb"],k:r,i:/\/\*/,c:n.concat(N).concat(d)}});hljs.registerLanguage("objectivec",function(e){var t={cN:"built_in",b:"(AV|CA|CF|CG|CI|MK|MP|NS|UI)\\w+"},i={keyword:"int float while char export sizeof typedef const struct for union unsigned long volatile static bool mutable if do return goto void enum else break extern asm case short default double register explicit signed typename this switch continue wchar_t inline readonly assign readwrite self @synchronized id typeof nonatomic super unichar IBOutlet IBAction strong weak copy in out inout bycopy byref oneway __strong __weak __block __autoreleasing @private @protected @public @try @property @end @throw @catch @finally @autoreleasepool @synthesize @dynamic @selector @optional @required",literal:"false true FALSE TRUE nil YES NO NULL",built_in:"BOOL dispatch_once_t dispatch_queue_t dispatch_sync dispatch_async dispatch_once"},o=/[a-zA-Z@][a-zA-Z0-9_]*/,n="@interface @class @protocol @implementation";return{aliases:["mm","objc","obj-c"],k:i,l:o,i:""}]}]},{cN:"class",b:"("+n.split(" ").join("|")+")\\b",e:"({|$)",eE:!0,k:n,l:o,c:[e.UTM]},{cN:"variable",b:"\\."+e.UIR,r:0}]}});hljs.registerLanguage("rust",function(e){var t="([uif](8|16|32|64|size))?",r=e.inherit(e.CBCM);return r.c.push("self"),{aliases:["rs"],k:{keyword:"alignof as be box break const continue crate do else enum extern false fn for if impl in let loop match mod mut offsetof once priv proc pub pure ref return self Self sizeof static struct super trait true type typeof unsafe unsized use virtual while where yield int i8 i16 i32 i64 uint u8 u32 u64 float f32 f64 str char bool",built_in:"Copy Send Sized Sync Drop Fn FnMut FnOnce drop Box ToOwned Clone PartialEq PartialOrd Eq Ord AsRef AsMut Into From Default Iterator Extend IntoIterator DoubleEndedIterator ExactSizeIterator Option Some None Result Ok Err SliceConcatExt String ToString Vec assert! assert_eq! bitflags! bytes! cfg! col! concat! concat_idents! debug_assert! debug_assert_eq! env! panic! file! format! format_args! include_bin! include_str! line! local_data_key! module_path! option_env! print! println! select! stringify! try! unimplemented! unreachable! vec! write! writeln!"},l:e.IR+"!?",i:""}]}});hljs.registerLanguage("smalltalk",function(a){var r="[a-z][a-zA-Z0-9_]*",s={cN:"char",b:"\\$.{1}"},c={cN:"symbol",b:"#"+a.UIR};return{aliases:["st"],k:"self super nil true false thisContext",c:[a.C('"','"'),a.ASM,{cN:"class",b:"\\b[A-Z][A-Za-z0-9_]*",r:0},{cN:"method",b:r+":",r:0},a.CNM,c,s,{cN:"localvars",b:"\\|[ ]*"+r+"([ ]+"+r+")*[ ]*\\|",rB:!0,e:/\|/,i:/\S/,c:[{b:"(\\|[ ]*)?"+r}]},{cN:"array",b:"\\#\\(",e:"\\)",c:[a.ASM,s,a.CNM,c]}]}});hljs.registerLanguage("lasso",function(e){var r="[a-zA-Z_][a-zA-Z0-9_.]*",a="<\\?(lasso(script)?|=)",t="\\]|\\?>",s={literal:"true false none minimal full all void bw nbw ew new cn ncn lt lte gt gte eq neq rx nrx ft",built_in:"array date decimal duration integer map pair string tag xml null boolean bytes keyword list locale queue set stack staticarray local var variable global data self inherited currentcapture givenblock",keyword:"error_code error_msg error_pop error_push error_reset cache database_names database_schemanames database_tablenames define_tag define_type email_batch encode_set html_comment handle handle_error header if inline iterate ljax_target link link_currentaction link_currentgroup link_currentrecord link_detail link_firstgroup link_firstrecord link_lastgroup link_lastrecord link_nextgroup link_nextrecord link_prevgroup link_prevrecord log loop namespace_using output_none portal private protect records referer referrer repeating resultset rows search_args search_arguments select sort_args sort_arguments thread_atomic value_list while abort case else if_empty if_false if_null if_true loop_abort loop_continue loop_count params params_up return return_value run_children soap_definetag soap_lastrequest soap_lastresponse tag_name ascending average by define descending do equals frozen group handle_failure import in into join let match max min on order parent protected provide public require returnhome skip split_thread sum take thread to trait type where with yield yieldhome"},n=e.C("",{r:0}),i={cN:"preprocessor",b:"\\[noprocess\\]",starts:{cN:"markup",e:"\\[/noprocess\\]",rE:!0,c:[n]}},o={cN:"preprocessor",b:"\\[/noprocess|"+a},l={cN:"variable",b:"'"+r+"'"},c=[e.C("/\\*\\*!","\\*/"),e.CLCM,e.CBCM,e.inherit(e.CNM,{b:e.CNR+"|(infinity|nan)\\b"}),e.inherit(e.ASM,{i:null}),e.inherit(e.QSM,{i:null}),{cN:"string",b:"`",e:"`"},{cN:"variable",v:[{b:"[#$]"+r},{b:"#",e:"\\d+",i:"\\W"}]},{cN:"tag",b:"::\\s*",e:r,i:"\\W"},{cN:"attribute",v:[{b:"-(?!infinity)"+e.UIR,r:0},{b:"(\\.\\.\\.)"}]},{cN:"subst",v:[{b:"->\\s*",c:[l]},{b:"->|\\\\|&&?|\\|\\||!(?!=|>)|(and|or|not)\\b",r:0}]},{cN:"built_in",b:"\\.\\.?\\s*",r:0,c:[l]},{cN:"class",bK:"define",rE:!0,e:"\\(|=>",c:[e.inherit(e.TM,{b:e.UIR+"(=(?!>))?"})]}];return{aliases:["ls","lassoscript"],cI:!0,l:r+"|&[lg]t;",k:s,c:[{cN:"preprocessor",b:t,r:0,starts:{cN:"markup",e:"\\[|"+a,rE:!0,r:0,c:[n]}},i,o,{cN:"preprocessor",b:"\\[no_square_brackets",starts:{e:"\\[/no_square_brackets\\]",l:r+"|&[lg]t;",k:s,c:[{cN:"preprocessor",b:t,r:0,starts:{cN:"markup",e:"\\[noprocess\\]|"+a,rE:!0,c:[n]}},i,o].concat(c)}},{cN:"preprocessor",b:"\\[",r:0},{cN:"shebang",b:"^#!.+lasso9\\b",r:10}].concat(c)}});hljs.registerLanguage("matlab",function(e){var a=[e.CNM,{cN:"string",b:"'",e:"'",c:[e.BE,{b:"''"}]}],s={r:0,c:[{cN:"operator",b:/'['\.]*/}]};return{k:{keyword:"break case catch classdef continue else elseif end enumerated events for function global if methods otherwise parfor persistent properties return spmd switch try while",built_in:"sin sind sinh asin asind asinh cos cosd cosh acos acosd acosh tan tand tanh atan atand atan2 atanh sec secd sech asec asecd asech csc cscd csch acsc acscd acsch cot cotd coth acot acotd acoth hypot exp expm1 log log1p log10 log2 pow2 realpow reallog realsqrt sqrt nthroot nextpow2 abs angle complex conj imag real unwrap isreal cplxpair fix floor ceil round mod rem sign airy besselj bessely besselh besseli besselk beta betainc betaln ellipj ellipke erf erfc erfcx erfinv expint gamma gammainc gammaln psi legendre cross dot factor isprime primes gcd lcm rat rats perms nchoosek factorial cart2sph cart2pol pol2cart sph2cart hsv2rgb rgb2hsv zeros ones eye repmat rand randn linspace logspace freqspace meshgrid accumarray size length ndims numel disp isempty isequal isequalwithequalnans cat reshape diag blkdiag tril triu fliplr flipud flipdim rot90 find sub2ind ind2sub bsxfun ndgrid permute ipermute shiftdim circshift squeeze isscalar isvector ans eps realmax realmin pi i inf nan isnan isinf isfinite j why compan gallery hadamard hankel hilb invhilb magic pascal rosser toeplitz vander wilkinson"},i:'(//|"|#|/\\*|\\s+/\\w+)',c:[{cN:"function",bK:"function",e:"$",c:[e.UTM,{cN:"params",b:"\\(",e:"\\)"},{cN:"params",b:"\\[",e:"\\]"}]},{b:/[a-zA-Z_][a-zA-Z_0-9]*'['\.]*/,rB:!0,r:0,c:[{b:/[a-zA-Z_][a-zA-Z_0-9]*/,r:0},s.c[0]]},{cN:"matrix",b:"\\[",e:"\\]",c:a,r:0,starts:s},{cN:"cell",b:"\\{",e:/}/,c:a,r:0,starts:s},{b:/\)/,r:0,starts:s},e.C("^\\s*\\%\\{\\s*$","^\\s*\\%\\}\\s*$"),e.C("\\%","$")].concat(a)}});hljs.registerLanguage("python",function(e){var r={cN:"prompt",b:/^(>>>|\.\.\.) /},b={cN:"string",c:[e.BE],v:[{b:/(u|b)?r?'''/,e:/'''/,c:[r],r:10},{b:/(u|b)?r?"""/,e:/"""/,c:[r],r:10},{b:/(u|r|ur)'/,e:/'/,r:10},{b:/(u|r|ur)"/,e:/"/,r:10},{b:/(b|br)'/,e:/'/},{b:/(b|br)"/,e:/"/},e.ASM,e.QSM]},a={cN:"number",r:0,v:[{b:e.BNR+"[lLjJ]?"},{b:"\\b(0o[0-7]+)[lLjJ]?"},{b:e.CNR+"[lLjJ]?"}]},l={cN:"params",b:/\(/,e:/\)/,c:["self",r,a,b]};return{aliases:["py","gyp"],k:{keyword:"and elif is global as in if from raise for except finally print import pass return exec else break not with class assert yield try while continue del or def lambda async await nonlocal|10 None True False",built_in:"Ellipsis NotImplemented"},i:/(<\/|->|\?)/,c:[r,a,b,e.HCM,{v:[{cN:"function",bK:"def",r:10},{cN:"class",bK:"class"}],e:/:/,i:/[${=;\n,]/,c:[e.UTM,l]},{cN:"decorator",b:/^[\t ]*@/,e:/$/},{b:/\b(print|exec)\(/}]}});hljs.registerLanguage("scala",function(e){var t={cN:"annotation",b:"@[A-Za-z]+"},r={cN:"string",b:'u?r?"""',e:'"""',r:10},a={cN:"symbol",b:"'\\w[\\w\\d_]*(?!')"},c={cN:"type",b:"\\b[A-Z][A-Za-z0-9_]*",r:0},i={cN:"title",b:/[^0-9\n\t "'(),.`{}\[\]:;][^\n\t "'(),.`{}\[\]:;]+|[^0-9\n\t "'(),.`{}\[\]:;=]/,r:0},n={cN:"class",bK:"class object trait type",e:/[:={\[(\n;]/,c:[{cN:"keyword",bK:"extends with",r:10},i]},l={cN:"function",bK:"def",e:/[:={\[(\n;]/,c:[i]};return{k:{literal:"true false null",keyword:"type yield lazy override def with val var sealed abstract private trait object if forSome for while throw finally protected extends import final return else break new catch super class case package default try this match continue throws implicit"},c:[e.CLCM,e.CBCM,r,e.QSM,a,c,l,n,e.CNM,t]}});hljs.registerLanguage("swift",function(e){var i={keyword:"__COLUMN__ __FILE__ __FUNCTION__ __LINE__ as as! as? associativity break case catch class continue convenience default defer deinit didSet do dynamic dynamicType else enum extension fallthrough false final for func get guard if import in indirect infix init inout internal is lazy left let mutating nil none nonmutating operator optional override postfix precedence prefix private protocol Protocol public repeat required rethrows return right self Self set static struct subscript super switch throw throws true try try! try? Type typealias unowned var weak where while willSet",literal:"true false nil",built_in:"abs advance alignof alignofValue anyGenerator assert assertionFailure bridgeFromObjectiveC bridgeFromObjectiveCUnconditional bridgeToObjectiveC bridgeToObjectiveCUnconditional c contains count countElements countLeadingZeros debugPrint debugPrintln distance dropFirst dropLast dump encodeBitsAsWords enumerate equal fatalError filter find getBridgedObjectiveCType getVaList indices insertionSort isBridgedToObjectiveC isBridgedVerbatimToObjectiveC isUniquelyReferenced isUniquelyReferencedNonObjC join lazy lexicographicalCompare map max maxElement min minElement numericCast overlaps partition posix precondition preconditionFailure print println quickSort readLine reduce reflect reinterpretCast reverse roundUpToAlignment sizeof sizeofValue sort split startsWith stride strideof strideofValue swap toString transcode underestimateCount unsafeAddressOf unsafeBitCast unsafeDowncast unsafeUnwrap unsafeReflect withExtendedLifetime withObjectAtPlusZero withUnsafePointer withUnsafePointerToObject withUnsafeMutablePointer withUnsafeMutablePointers withUnsafePointer withUnsafePointers withVaList zip"},t={cN:"type",b:"\\b[A-Z][\\w']*",r:0},n=e.C("/\\*","\\*/",{c:["self"]}),r={cN:"subst",b:/\\\(/,e:"\\)",k:i,c:[]},a={cN:"number",b:"\\b([\\d_]+(\\.[\\deE_]+)?|0x[a-fA-F0-9_]+(\\.[a-fA-F0-9p_]+)?|0b[01_]+|0o[0-7_]+)\\b",r:0},o=e.inherit(e.QSM,{c:[r,e.BE]});return r.c=[a],{k:i,c:[o,e.CLCM,n,t,a,{cN:"func",bK:"func",e:"{",eE:!0,c:[e.inherit(e.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/,i:/\(/}),{cN:"generics",b://,i:/>/},{cN:"params",b:/\(/,e:/\)/,endsParent:!0,k:i,c:["self",a,o,e.CBCM,{b:":"}],i:/["']/}],i:/\[|%/},{cN:"class",bK:"struct protocol class extension enum",k:i,e:"\\{",eE:!0,c:[e.inherit(e.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/})]},{cN:"preprocessor",b:"(@warn_unused_result|@exported|@lazy|@noescape|@NSCopying|@NSManaged|@objc|@convention|@required|@noreturn|@IBAction|@IBDesignable|@IBInspectable|@IBOutlet|@infix|@prefix|@postfix|@autoclosure|@testable|@available|@nonobjc|@NSApplicationMain|@UIApplicationMain)"},{bK:"import",e:/$/,c:[e.CLCM,n]}]}});hljs.registerLanguage("lisp",function(b){var e="[a-zA-Z_\\-\\+\\*\\/\\<\\=\\>\\&\\#][a-zA-Z0-9_\\-\\+\\*\\/\\<\\=\\>\\&\\#!]*",c="\\|[^]*?\\|",r="(\\-|\\+)?\\d+(\\.\\d+|\\/\\d+)?((d|e|f|l|s|D|E|F|L|S)(\\+|\\-)?\\d+)?",a={cN:"shebang",b:"^#!",e:"$"},i={cN:"literal",b:"\\b(t{1}|nil)\\b"},l={cN:"number",v:[{b:r,r:0},{b:"#(b|B)[0-1]+(/[0-1]+)?"},{b:"#(o|O)[0-7]+(/[0-7]+)?"},{b:"#(x|X)[0-9a-fA-F]+(/[0-9a-fA-F]+)?"},{b:"#(c|C)\\("+r+" +"+r,e:"\\)"}]},t=b.inherit(b.QSM,{i:null}),d=b.C(";","$",{r:0}),n={cN:"variable",b:"\\*",e:"\\*"},u={cN:"keyword",b:"[:&]"+e},N={b:e,r:0},o={b:c},s={b:"\\(",e:"\\)",c:["self",i,t,l,N]},v={cN:"quoted",c:[l,t,n,u,s,N],v:[{b:"['`]\\(",e:"\\)"},{b:"\\(quote ",e:"\\)",k:"quote"},{b:"'"+c}]},f={cN:"quoted",v:[{b:"'"+e},{b:"#'"+e+"(::"+e+")*"}]},g={cN:"list",b:"\\(\\s*",e:"\\)"},q={eW:!0,r:0};return g.c=[{cN:"keyword",v:[{b:e},{b:c}]},q],q.c=[v,f,g,i,l,t,d,n,u,o,N],{i:/\S/,c:[l,a,i,t,d,v,f,g,N]}});hljs.registerLanguage("lua",function(e){var t="\\[=*\\[",a="\\]=*\\]",r={b:t,e:a,c:["self"]},n=[e.C("--(?!"+t+")","$"),e.C("--"+t,a,{c:[r],r:10})];return{l:e.UIR,k:{keyword:"and break do else elseif end false for if in local nil not or repeat return then true until while",built_in:"_G _VERSION assert collectgarbage dofile error getfenv getmetatable ipairs load loadfile loadstring module next pairs pcall print rawequal rawget rawset require select setfenv setmetatable tonumber tostring type unpack xpcall coroutine debug io math os package string table"},c:n.concat([{cN:"function",bK:"function",e:"\\)",c:[e.inherit(e.TM,{b:"([_a-zA-Z]\\w*\\.)*([_a-zA-Z]\\w*:)?[_a-zA-Z]\\w*"}),{cN:"params",b:"\\(",eW:!0,c:n}].concat(n)},e.CNM,e.ASM,e.QSM,{cN:"string",b:t,e:a,c:[r],r:5}])}});hljs.registerLanguage("coffeescript",function(e){var c={keyword:"in if for while finally new do return else break catch instanceof throw try this switch continue typeof delete debugger super then unless until loop of by when and or is isnt not",literal:"true false null undefined yes no on off",built_in:"npm require console print module global window document"},n="[A-Za-z$_][0-9A-Za-z$_]*",r={cN:"subst",b:/#\{/,e:/}/,k:c},t=[e.BNM,e.inherit(e.CNM,{starts:{e:"(\\s*/)?",r:0}}),{cN:"string",v:[{b:/'''/,e:/'''/,c:[e.BE]},{b:/'/,e:/'/,c:[e.BE]},{b:/"""/,e:/"""/,c:[e.BE,r]},{b:/"/,e:/"/,c:[e.BE,r]}]},{cN:"regexp",v:[{b:"///",e:"///",c:[r,e.HCM]},{b:"//[gim]*",r:0},{b:/\/(?![ *])(\\\/|.)*?\/[gim]*(?=\W|$)/}]},{cN:"property",b:"@"+n},{b:"`",e:"`",eB:!0,eE:!0,sL:"javascript"}];r.c=t;var s=e.inherit(e.TM,{b:n}),i="(\\(.*\\))?\\s*\\B[-=]>",o={cN:"params",b:"\\([^\\(]",rB:!0,c:[{b:/\(/,e:/\)/,k:c,c:["self"].concat(t)}]};return{aliases:["coffee","cson","iced"],k:c,i:/\/\*/,c:t.concat([e.C("###","###"),e.HCM,{cN:"function",b:"^\\s*"+n+"\\s*=\\s*"+i,e:"[-=]>",rB:!0,c:[s,o]},{b:/[:\(,=]\s*/,r:0,c:[{cN:"function",b:i,e:"[-=]>",rB:!0,c:[o]}]},{cN:"class",bK:"class",e:"$",i:/[:="\[\]]/,c:[{bK:"extends",eW:!0,i:/[:="\[\]]/,c:[s]},s]},{cN:"attribute",b:n+":",e:":",rB:!0,rE:!0,r:0}])}});hljs.registerLanguage("dos",function(e){var r=e.C(/@?rem\b/,/$/,{r:10}),t={cN:"label",b:"^\\s*[A-Za-z._?][A-Za-z0-9_$#@~.?]*(:|\\s+label)",r:0};return{aliases:["bat","cmd"],cI:!0,i:/\/\*/,k:{flow:"if else goto for in do call exit not exist errorlevel defined",operator:"equ neq lss leq gtr geq",keyword:"shift cd dir echo setlocal endlocal set pause copy",stream:"prn nul lpt3 lpt2 lpt1 con com4 com3 com2 com1 aux",winutils:"ping net ipconfig taskkill xcopy ren del",built_in:"append assoc at attrib break cacls cd chcp chdir chkdsk chkntfs cls cmd color comp compact convert date dir diskcomp diskcopy doskey erase fs find findstr format ftype graftabl help keyb label md mkdir mode more move path pause print popd pushd promt rd recover rem rename replace restore rmdir shiftsort start subst time title tree type ver verify vol"},c:[{cN:"envvar",b:/%%[^ ]|%[^ ]+?%|![^ ]+?!/},{cN:"function",b:t.b,e:"goto:eof",c:[e.inherit(e.TM,{b:"([_a-zA-Z]\\w*\\.)*([_a-zA-Z]\\w*:)?[_a-zA-Z]\\w*"}),r]},{cN:"number",b:"\\b\\d+",r:0},r]}});hljs.registerLanguage("avrasm",function(r){return{cI:!0,l:"\\.?"+r.IR,k:{keyword:"adc add adiw and andi asr bclr bld brbc brbs brcc brcs break breq brge brhc brhs brid brie brlo brlt brmi brne brpl brsh brtc brts brvc brvs bset bst call cbi cbr clc clh cli cln clr cls clt clv clz com cp cpc cpi cpse dec eicall eijmp elpm eor fmul fmuls fmulsu icall ijmp in inc jmp ld ldd ldi lds lpm lsl lsr mov movw mul muls mulsu neg nop or ori out pop push rcall ret reti rjmp rol ror sbc sbr sbrc sbrs sec seh sbi sbci sbic sbis sbiw sei sen ser ses set sev sez sleep spm st std sts sub subi swap tst wdr",built_in:"r0 r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11 r12 r13 r14 r15 r16 r17 r18 r19 r20 r21 r22 r23 r24 r25 r26 r27 r28 r29 r30 r31 x|0 xh xl y|0 yh yl z|0 zh zl ucsr1c udr1 ucsr1a ucsr1b ubrr1l ubrr1h ucsr0c ubrr0h tccr3c tccr3a tccr3b tcnt3h tcnt3l ocr3ah ocr3al ocr3bh ocr3bl ocr3ch ocr3cl icr3h icr3l etimsk etifr tccr1c ocr1ch ocr1cl twcr twdr twar twsr twbr osccal xmcra xmcrb eicra spmcsr spmcr portg ddrg ping portf ddrf sreg sph spl xdiv rampz eicrb eimsk gimsk gicr eifr gifr timsk tifr mcucr mcucsr tccr0 tcnt0 ocr0 assr tccr1a tccr1b tcnt1h tcnt1l ocr1ah ocr1al ocr1bh ocr1bl icr1h icr1l tccr2 tcnt2 ocr2 ocdr wdtcr sfior eearh eearl eedr eecr porta ddra pina portb ddrb pinb portc ddrc pinc portd ddrd pind spdr spsr spcr udr0 ucsr0a ucsr0b ubrr0l acsr admux adcsr adch adcl porte ddre pine pinf",preprocessor:".byte .cseg .db .def .device .dseg .dw .endmacro .equ .eseg .exit .include .list .listmac .macro .nolist .org .set"},c:[r.CBCM,r.C(";","$",{r:0}),r.CNM,r.BNM,{cN:"number",b:"\\b(\\$[a-zA-Z0-9]+|0o[0-7]+)"},r.QSM,{cN:"string",b:"'",e:"[^\\\\]'",i:"[^\\\\][^']"},{cN:"label",b:"^[A-Za-z0-9_.$]+:"},{cN:"preprocessor",b:"#",e:"$"},{cN:"localvars",b:"@[0-9]+"}]}});hljs.registerLanguage("profile",function(e){return{c:[e.CNM,{cN:"built_in",b:"{",e:"}$",eB:!0,eE:!0,c:[e.ASM,e.QSM],r:0},{cN:"filename",b:"[a-zA-Z_][\\da-zA-Z_]+\\.[\\da-zA-Z_]{1,3}",e:":",eE:!0},{cN:"header",b:"(ncalls|tottime|cumtime)",e:"$",k:"ncalls tottime|10 cumtime|10 filename",r:10},{cN:"summary",b:"function calls",e:"$",c:[e.CNM],r:10},e.ASM,e.QSM,{cN:"function",b:"\\(",e:"\\)$",c:[e.UTM],r:0}]}});hljs.registerLanguage("tex",function(c){var e={cN:"command",b:"\\\\[a-zA-Zа-яА-я]+[\\*]?"},m={cN:"command",b:"\\\\[^a-zA-Zа-яА-я0-9]"},r={cN:"special",b:"[{}\\[\\]\\&#~]",r:0};return{c:[{b:"\\\\[a-zA-Zа-яА-я]+[\\*]? *= *-?\\d*\\.?\\d+(pt|pc|mm|cm|in|dd|cc|ex|em)?",rB:!0,c:[e,m,{cN:"number",b:" *=",e:"-?\\d*\\.?\\d+(pt|pc|mm|cm|in|dd|cc|ex|em)?",eB:!0}],r:10},e,m,r,{cN:"formula",b:"\\$\\$",e:"\\$\\$",c:[e,m,r],r:0},{cN:"formula",b:"\\$",e:"\\$",c:[e,m,r],r:0},c.C("%","$",{r:0})]}});hljs.registerLanguage("perl",function(e){var t="getpwent getservent quotemeta msgrcv scalar kill dbmclose undef lc ma syswrite tr send umask sysopen shmwrite vec qx utime local oct semctl localtime readpipe do return format read sprintf dbmopen pop getpgrp not getpwnam rewinddir qqfileno qw endprotoent wait sethostent bless s|0 opendir continue each sleep endgrent shutdown dump chomp connect getsockname die socketpair close flock exists index shmgetsub for endpwent redo lstat msgctl setpgrp abs exit select print ref gethostbyaddr unshift fcntl syscall goto getnetbyaddr join gmtime symlink semget splice x|0 getpeername recv log setsockopt cos last reverse gethostbyname getgrnam study formline endhostent times chop length gethostent getnetent pack getprotoent getservbyname rand mkdir pos chmod y|0 substr endnetent printf next open msgsnd readdir use unlink getsockopt getpriority rindex wantarray hex system getservbyport endservent int chr untie rmdir prototype tell listen fork shmread ucfirst setprotoent else sysseek link getgrgid shmctl waitpid unpack getnetbyname reset chdir grep split require caller lcfirst until warn while values shift telldir getpwuid my getprotobynumber delete and sort uc defined srand accept package seekdir getprotobyname semop our rename seek if q|0 chroot sysread setpwent no crypt getc chown sqrt write setnetent setpriority foreach tie sin msgget map stat getlogin unless elsif truncate exec keys glob tied closedirioctl socket readlink eval xor readline binmode setservent eof ord bind alarm pipe atan2 getgrent exp time push setgrent gt lt or ne m|0 break given say state when",r={cN:"subst",b:"[$@]\\{",e:"\\}",k:t},s={b:"->{",e:"}"},n={cN:"variable",v:[{b:/\$\d/},{b:/[\$%@](\^\w\b|#\w+(::\w+)*|{\w+}|\w+(::\w*)*)/},{b:/[\$%@][^\s\w{]/,r:0}]},o=[e.BE,r,n],i=[n,e.HCM,e.C("^\\=\\w","\\=cut",{eW:!0}),s,{cN:"string",c:o,v:[{b:"q[qwxr]?\\s*\\(",e:"\\)",r:5},{b:"q[qwxr]?\\s*\\[",e:"\\]",r:5},{b:"q[qwxr]?\\s*\\{",e:"\\}",r:5},{b:"q[qwxr]?\\s*\\|",e:"\\|",r:5},{b:"q[qwxr]?\\s*\\<",e:"\\>",r:5},{b:"qw\\s+q",e:"q",r:5},{b:"'",e:"'",c:[e.BE]},{b:'"',e:'"'},{b:"`",e:"`",c:[e.BE]},{b:"{\\w+}",c:[],r:0},{b:"-?\\w+\\s*\\=\\>",c:[],r:0}]},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{b:"(\\/\\/|"+e.RSR+"|\\b(split|return|print|reverse|grep)\\b)\\s*",k:"split return print reverse grep",r:0,c:[e.HCM,{cN:"regexp",b:"(s|tr|y)/(\\\\.|[^/])*/(\\\\.|[^/])*/[a-z]*",r:10},{cN:"regexp",b:"(m|qr)?/",e:"/[a-z]*",c:[e.BE],r:0}]},{cN:"sub",bK:"sub",e:"(\\s*\\(.*?\\))?[;{]",r:5},{cN:"operator",b:"-\\w\\b",r:0},{b:"^__DATA__$",e:"^__END__$",sL:"mojolicious",c:[{b:"^@@.*",e:"$",cN:"comment"}]}];return r.c=i,s.c=i,{aliases:["pl"],k:t,c:i}});hljs.registerLanguage("erlang",function(e){var r="[a-z'][a-zA-Z0-9_']*",c="("+r+":"+r+"|"+r+")",a={keyword:"after and andalso|10 band begin bnot bor bsl bzr bxor case catch cond div end fun if let not of orelse|10 query receive rem try when xor",literal:"false true"},n=e.C("%","$"),i={cN:"number",b:"\\b(\\d+#[a-fA-F0-9]+|\\d+(\\.\\d+)?([eE][-+]?\\d+)?)",r:0},b={b:"fun\\s+"+r+"/\\d+"},d={b:c+"\\(",e:"\\)",rB:!0,r:0,c:[{cN:"function_name",b:c,r:0},{b:"\\(",e:"\\)",eW:!0,rE:!0,r:0}]},o={cN:"tuple",b:"{",e:"}",r:0},t={cN:"variable",b:"\\b_([A-Z][A-Za-z0-9_]*)?",r:0},l={cN:"variable",b:"[A-Z][a-zA-Z0-9_]*",r:0},f={b:"#"+e.UIR,r:0,rB:!0,c:[{cN:"record_name",b:"#"+e.UIR,r:0},{b:"{",e:"}",r:0}]},s={bK:"fun receive if try case",e:"end",k:a};s.c=[n,b,e.inherit(e.ASM,{cN:""}),s,d,e.QSM,i,o,t,l,f];var u=[n,b,s,d,e.QSM,i,o,t,l,f];d.c[1].c=u,o.c=u,f.c[1].c=u;var v={cN:"params",b:"\\(",e:"\\)",c:u};return{aliases:["erl"],k:a,i:"(",rB:!0,i:"\\(|#|//|/\\*|\\\\|:|;",c:[v,e.inherit(e.TM,{b:r})],starts:{e:";|\\.",k:a,c:u}},n,{cN:"pp",b:"^-",e:"\\.",r:0,eE:!0,rB:!0,l:"-"+e.IR,k:"-module -record -undef -export -ifdef -ifndef -author -copyright -doc -vsn -import -include -include_lib -compile -define -else -endif -file -behaviour -behavior -spec",c:[v]},i,e.QSM,f,t,l,o,{b:/\.$/}]}});hljs.registerLanguage("cpp",function(t){var e={cN:"keyword",b:"\\b[a-z\\d_]*_t\\b"},r={cN:"string",v:[t.inherit(t.QSM,{b:'((u8?|U)|L)?"'}),{b:'(u8?|U)?R"',e:'"',c:[t.BE]},{b:"'\\\\?.",e:"'",i:"."}]},s={cN:"number",v:[{b:"\\b(\\d+(\\.\\d*)?|\\.\\d+)(u|U|l|L|ul|UL|f|F)"},{b:t.CNR}]},i={cN:"preprocessor",b:"#",e:"$",k:"if else elif endif define undef warning error line pragma ifdef ifndef",c:[{b:/\\\n/,r:0},{bK:"include",e:"$",c:[r,{cN:"string",b:"<",e:">",i:"\\n"}]},r,s,t.CLCM,t.CBCM]},a=t.IR+"\\s*\\(",c={keyword:"int float while private char catch export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const struct for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using class asm case typeid short reinterpret_cast|10 default double register explicit signed typename try this switch continue inline delete alignof constexpr decltype noexcept static_assert thread_local restrict _Bool complex _Complex _Imaginary atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong",built_in:"std string cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap array shared_ptr abort abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf",literal:"true false nullptr NULL"};return{aliases:["c","cc","h","c++","h++","hpp"],k:c,i:"",k:c,c:["self",e]},{b:t.IR+"::",k:c},{bK:"new throw return else",r:0},{cN:"function",b:"("+t.IR+"[\\*&\\s]+)+"+a,rB:!0,e:/[{;=]/,eE:!0,k:c,i:/[^\w\s\*&]/,c:[{b:a,rB:!0,c:[t.TM],r:0},{cN:"params",b:/\(/,e:/\)/,k:c,r:0,c:[t.CLCM,t.CBCM,r,s]},t.CLCM,t.CBCM,i]}]}});hljs.registerLanguage("glsl",function(e){return{k:{keyword:"atomic_uint attribute bool break bvec2 bvec3 bvec4 case centroid coherent const continue default discard dmat2 dmat2x2 dmat2x3 dmat2x4 dmat3 dmat3x2 dmat3x3 dmat3x4 dmat4 dmat4x2 dmat4x3 dmat4x4 do double dvec2 dvec3 dvec4 else flat float for highp if iimage1D iimage1DArray iimage2D iimage2DArray iimage2DMS iimage2DMSArray iimage2DRect iimage3D iimageBuffer iimageCube iimageCubeArray image1D image1DArray image2D image2DArray image2DMS image2DMSArray image2DRect image3D imageBuffer imageCube imageCubeArray in inout int invariant isampler1D isampler1DArray isampler2D isampler2DArray isampler2DMS isampler2DMSArray isampler2DRect isampler3D isamplerBuffer isamplerCube isamplerCubeArray ivec2 ivec3 ivec4 layout lowp mat2 mat2x2 mat2x3 mat2x4 mat3 mat3x2 mat3x3 mat3x4 mat4 mat4x2 mat4x3 mat4x4 mediump noperspective out patch precision readonly restrict return sample sampler1D sampler1DArray sampler1DArrayShadow sampler1DShadow sampler2D sampler2DArray sampler2DArrayShadow sampler2DMS sampler2DMSArray sampler2DRect sampler2DRectShadow sampler2DShadow sampler3D samplerBuffer samplerCube samplerCubeArray samplerCubeArrayShadow samplerCubeShadow smooth struct subroutine switch uimage1D uimage1DArray uimage2D uimage2DArray uimage2DMS uimage2DMSArray uimage2DRect uimage3D uimageBuffer uimageCube uimageCubeArray uint uniform usampler1D usampler1DArray usampler2D usampler2DArray usampler2DMS usampler2DMSArray usampler2DRect usampler3D usamplerBuffer usamplerCube usamplerCubeArray uvec2 uvec3 uvec4 varying vec2 vec3 vec4 void volatile while writeonly",built_in:"gl_BackColor gl_BackLightModelProduct gl_BackLightProduct gl_BackMaterial gl_BackSecondaryColor gl_ClipDistance gl_ClipPlane gl_ClipVertex gl_Color gl_DepthRange gl_EyePlaneQ gl_EyePlaneR gl_EyePlaneS gl_EyePlaneT gl_Fog gl_FogCoord gl_FogFragCoord gl_FragColor gl_FragCoord gl_FragData gl_FragDepth gl_FrontColor gl_FrontFacing gl_FrontLightModelProduct gl_FrontLightProduct gl_FrontMaterial gl_FrontSecondaryColor gl_InstanceID gl_InvocationID gl_Layer gl_LightModel gl_LightSource gl_MaxAtomicCounterBindings gl_MaxAtomicCounterBufferSize gl_MaxClipDistances gl_MaxClipPlanes gl_MaxCombinedAtomicCounterBuffers gl_MaxCombinedAtomicCounters gl_MaxCombinedImageUniforms gl_MaxCombinedImageUnitsAndFragmentOutputs gl_MaxCombinedTextureImageUnits gl_MaxDrawBuffers gl_MaxFragmentAtomicCounterBuffers gl_MaxFragmentAtomicCounters gl_MaxFragmentImageUniforms gl_MaxFragmentInputComponents gl_MaxFragmentUniformComponents gl_MaxFragmentUniformVectors gl_MaxGeometryAtomicCounterBuffers gl_MaxGeometryAtomicCounters gl_MaxGeometryImageUniforms gl_MaxGeometryInputComponents gl_MaxGeometryOutputComponents gl_MaxGeometryOutputVertices gl_MaxGeometryTextureImageUnits gl_MaxGeometryTotalOutputComponents gl_MaxGeometryUniformComponents gl_MaxGeometryVaryingComponents gl_MaxImageSamples gl_MaxImageUnits gl_MaxLights gl_MaxPatchVertices gl_MaxProgramTexelOffset gl_MaxTessControlAtomicCounterBuffers gl_MaxTessControlAtomicCounters gl_MaxTessControlImageUniforms gl_MaxTessControlInputComponents gl_MaxTessControlOutputComponents gl_MaxTessControlTextureImageUnits gl_MaxTessControlTotalOutputComponents gl_MaxTessControlUniformComponents gl_MaxTessEvaluationAtomicCounterBuffers gl_MaxTessEvaluationAtomicCounters gl_MaxTessEvaluationImageUniforms gl_MaxTessEvaluationInputComponents gl_MaxTessEvaluationOutputComponents gl_MaxTessEvaluationTextureImageUnits gl_MaxTessEvaluationUniformComponents gl_MaxTessGenLevel gl_MaxTessPatchComponents gl_MaxTextureCoords gl_MaxTextureImageUnits gl_MaxTextureUnits gl_MaxVaryingComponents gl_MaxVaryingFloats gl_MaxVaryingVectors gl_MaxVertexAtomicCounterBuffers gl_MaxVertexAtomicCounters gl_MaxVertexAttribs gl_MaxVertexImageUniforms gl_MaxVertexOutputComponents gl_MaxVertexTextureImageUnits gl_MaxVertexUniformComponents gl_MaxVertexUniformVectors gl_MaxViewports gl_MinProgramTexelOffsetgl_ModelViewMatrix gl_ModelViewMatrixInverse gl_ModelViewMatrixInverseTranspose gl_ModelViewMatrixTranspose gl_ModelViewProjectionMatrix gl_ModelViewProjectionMatrixInverse gl_ModelViewProjectionMatrixInverseTranspose gl_ModelViewProjectionMatrixTranspose gl_MultiTexCoord0 gl_MultiTexCoord1 gl_MultiTexCoord2 gl_MultiTexCoord3 gl_MultiTexCoord4 gl_MultiTexCoord5 gl_MultiTexCoord6 gl_MultiTexCoord7 gl_Normal gl_NormalMatrix gl_NormalScale gl_ObjectPlaneQ gl_ObjectPlaneR gl_ObjectPlaneS gl_ObjectPlaneT gl_PatchVerticesIn gl_PerVertex gl_Point gl_PointCoord gl_PointSize gl_Position gl_PrimitiveID gl_PrimitiveIDIn gl_ProjectionMatrix gl_ProjectionMatrixInverse gl_ProjectionMatrixInverseTranspose gl_ProjectionMatrixTranspose gl_SampleID gl_SampleMask gl_SampleMaskIn gl_SamplePosition gl_SecondaryColor gl_TessCoord gl_TessLevelInner gl_TessLevelOuter gl_TexCoord gl_TextureEnvColor gl_TextureMatrixInverseTranspose gl_TextureMatrixTranspose gl_Vertex gl_VertexID gl_ViewportIndex gl_in gl_out EmitStreamVertex EmitVertex EndPrimitive EndStreamPrimitive abs acos acosh all any asin asinh atan atanh atomicCounter atomicCounterDecrement atomicCounterIncrement barrier bitCount bitfieldExtract bitfieldInsert bitfieldReverse ceil clamp cos cosh cross dFdx dFdy degrees determinant distance dot equal exp exp2 faceforward findLSB findMSB floatBitsToInt floatBitsToUint floor fma fract frexp ftransform fwidth greaterThan greaterThanEqual imageAtomicAdd imageAtomicAnd imageAtomicCompSwap imageAtomicExchange imageAtomicMax imageAtomicMin imageAtomicOr imageAtomicXor imageLoad imageStore imulExtended intBitsToFloat interpolateAtCentroid interpolateAtOffset interpolateAtSample inverse inversesqrt isinf isnan ldexp length lessThan lessThanEqual log log2 matrixCompMult max memoryBarrier min mix mod modf noise1 noise2 noise3 noise4 normalize not notEqual outerProduct packDouble2x32 packHalf2x16 packSnorm2x16 packSnorm4x8 packUnorm2x16 packUnorm4x8 pow radians reflect refract round roundEven shadow1D shadow1DLod shadow1DProj shadow1DProjLod shadow2D shadow2DLod shadow2DProj shadow2DProjLod sign sin sinh smoothstep sqrt step tan tanh texelFetch texelFetchOffset texture texture1D texture1DLod texture1DProj texture1DProjLod texture2D texture2DLod texture2DProj texture2DProjLod texture3D texture3DLod texture3DProj texture3DProjLod textureCube textureCubeLod textureGather textureGatherOffset textureGatherOffsets textureGrad textureGradOffset textureLod textureLodOffset textureOffset textureProj textureProjGrad textureProjGradOffset textureProjLod textureProjLodOffset textureProjOffset textureQueryLod textureSize transpose trunc uaddCarry uintBitsToFloat umulExtended unpackDouble2x32 unpackHalf2x16 unpackSnorm2x16 unpackSnorm4x8 unpackUnorm2x16 unpackUnorm4x8 usubBorrow gl_TextureMatrix gl_TextureMatrixInverse",literal:"true false"},i:'"',c:[e.CLCM,e.CBCM,e.CNM,{cN:"preprocessor",b:"#",e:"$"}]}});hljs.registerLanguage("http",function(t){return{aliases:["https"],i:"\\S",c:[{cN:"status",b:"^HTTP/[0-9\\.]+",e:"$",c:[{cN:"number",b:"\\b\\d{3}\\b"}]},{cN:"request",b:"^[A-Z]+ (.*?) HTTP/[0-9\\.]+$",rB:!0,e:"$",c:[{cN:"string",b:" ",e:" ",eB:!0,eE:!0}]},{cN:"attribute",b:"^\\w",e:": ",eE:!0,i:"\\n|\\s|=",starts:{cN:"string",e:"$"}},{b:"\\n\\n",starts:{sL:[],eW:!0}}]}});hljs.registerLanguage("1c",function(c){var e="[a-zA-Zа-яА-Я][a-zA-Z0-9_а-яА-Я]*",r="возврат дата для если и или иначе иначеесли исключение конецесли конецпопытки конецпроцедуры конецфункции конеццикла константа не перейти перем перечисление по пока попытка прервать продолжить процедура строка тогда фс функция цикл число экспорт",t="ansitooem oemtoansi ввестивидсубконто ввестидату ввестизначение ввестиперечисление ввестипериод ввестиплансчетов ввестистроку ввестичисло вопрос восстановитьзначение врег выбранныйплансчетов вызватьисключение датагод датамесяц датачисло добавитьмесяц завершитьработусистемы заголовоксистемы записьжурналарегистрации запуститьприложение зафиксироватьтранзакцию значениевстроку значениевстрокувнутр значениевфайл значениеизстроки значениеизстрокивнутр значениеизфайла имякомпьютера имяпользователя каталогвременныхфайлов каталогиб каталогпользователя каталогпрограммы кодсимв командасистемы конгода конецпериодаби конецрассчитанногопериодаби конецстандартногоинтервала конквартала конмесяца коннедели лев лог лог10 макс максимальноеколичествосубконто мин монопольныйрежим названиеинтерфейса названиенабораправ назначитьвид назначитьсчет найти найтипомеченныенаудаление найтиссылки началопериодаби началостандартногоинтервала начатьтранзакцию начгода начквартала начмесяца начнедели номерднягода номерднянедели номернеделигода нрег обработкаожидания окр описаниеошибки основнойжурналрасчетов основнойплансчетов основнойязык открытьформу открытьформумодально отменитьтранзакцию очиститьокносообщений периодстр полноеимяпользователя получитьвремята получитьдатута получитьдокументта получитьзначенияотбора получитьпозициюта получитьпустоезначение получитьта прав праводоступа предупреждение префиксавтонумерации пустаястрока пустоезначение рабочаядаттьпустоезначение рабочаядата разделительстраниц разделительстрок разм разобратьпозициюдокумента рассчитатьрегистрына рассчитатьрегистрыпо сигнал симв символтабуляции создатьобъект сокрл сокрлп сокрп сообщить состояние сохранитьзначение сред статусвозврата стрдлина стрзаменить стрколичествострок стрполучитьстроку стрчисловхождений сформироватьпозициюдокумента счетпокоду текущаядата текущеевремя типзначения типзначениястр удалитьобъекты установитьтана установитьтапо фиксшаблон формат цел шаблон",i={cN:"dquote",b:'""'},n={cN:"string",b:'"',e:'"|$',c:[i]},a={cN:"string",b:"\\|",e:'"|$',c:[i]};return{cI:!0,l:e,k:{keyword:r,built_in:t},c:[c.CLCM,c.NM,n,a,{cN:"function",b:"(процедура|функция)",e:"$",l:e,k:"процедура функция",c:[c.inherit(c.TM,{b:e}),{cN:"tail",eW:!0,c:[{cN:"params",b:"\\(",e:"\\)",l:e,k:"знач",c:[n,a]},{cN:"export",b:"экспорт",eW:!0,l:e,k:"экспорт",c:[c.CLCM]}]},c.CLCM]},{cN:"preprocessor",b:"#",e:"$"},{cN:"date",b:"'\\d{2}\\.\\d{2}\\.(\\d{2}|\\d{4})'"}]}});hljs.registerLanguage("json",function(e){var t={literal:"true false null"},i=[e.QSM,e.CNM],l={cN:"value",e:",",eW:!0,eE:!0,c:i,k:t},c={b:"{",e:"}",c:[{cN:"attribute",b:'\\s*"',e:'"\\s*:\\s*',eB:!0,eE:!0,c:[e.BE],i:"\\n",starts:l}],i:"\\S"},n={b:"\\[",e:"\\]",c:[e.inherit(l,{cN:null})],i:"\\S"};return i.splice(i.length,0,c,n),{c:i,k:t,i:"\\S"}});hljs.registerLanguage("actionscript",function(e){var a="[a-zA-Z_$][a-zA-Z0-9_$]*",c="([*]|[a-zA-Z_$][a-zA-Z0-9_$]*)",t={cN:"rest_arg",b:"[.]{3}",e:a,r:10};return{aliases:["as"],k:{keyword:"as break case catch class const continue default delete do dynamic each else extends final finally for function get if implements import in include instanceof interface internal is namespace native new override package private protected public return set static super switch this throw try typeof use var void while with",literal:"true false null undefined"},c:[e.ASM,e.QSM,e.CLCM,e.CBCM,e.CNM,{cN:"package",bK:"package",e:"{",c:[e.TM]},{cN:"class",bK:"class interface",e:"{",eE:!0,c:[{bK:"extends implements"},e.TM]},{cN:"preprocessor",bK:"import include",e:";"},{cN:"function",bK:"function",e:"[{;]",eE:!0,i:"\\S",c:[e.TM,{cN:"params",b:"\\(",e:"\\)",c:[e.ASM,e.QSM,e.CLCM,e.CBCM,t]},{cN:"type",b:":",e:c,r:10}]}],i:/#/}});hljs.registerLanguage("scss",function(e){var t="[a-zA-Z-][a-zA-Z0-9_-]*",i={cN:"variable",b:"(\\$"+t+")\\b"},r={cN:"function",b:t+"\\(",rB:!0,eE:!0,e:"\\("},o={cN:"hexcolor",b:"#[0-9A-Fa-f]+"};({cN:"attribute",b:"[A-Z\\_\\.\\-]+",e:":",eE:!0,i:"[^\\s]",starts:{cN:"value",eW:!0,eE:!0,c:[r,o,e.CSSNM,e.QSM,e.ASM,e.CBCM,{cN:"important",b:"!important"}]}});return{cI:!0,i:"[=/|']",c:[e.CLCM,e.CBCM,r,{cN:"id",b:"\\#[A-Za-z0-9_-]+",r:0},{cN:"class",b:"\\.[A-Za-z0-9_-]+",r:0},{cN:"attr_selector",b:"\\[",e:"\\]",i:"$"},{cN:"tag",b:"\\b(a|abbr|acronym|address|area|article|aside|audio|b|base|big|blockquote|body|br|button|canvas|caption|cite|code|col|colgroup|command|datalist|dd|del|details|dfn|div|dl|dt|em|embed|fieldset|figcaption|figure|footer|form|frame|frameset|(h[1-6])|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|keygen|label|legend|li|link|map|mark|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|rp|rt|ruby|samp|script|section|select|small|span|strike|strong|style|sub|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|tt|ul|var|video)\\b",r:0},{cN:"pseudo",b:":(visited|valid|root|right|required|read-write|read-only|out-range|optional|only-of-type|only-child|nth-of-type|nth-last-of-type|nth-last-child|nth-child|not|link|left|last-of-type|last-child|lang|invalid|indeterminate|in-range|hover|focus|first-of-type|first-line|first-letter|first-child|first|enabled|empty|disabled|default|checked|before|after|active)"},{cN:"pseudo",b:"::(after|before|choices|first-letter|first-line|repeat-index|repeat-item|selection|value)"},i,{cN:"attribute",b:"\\b(z-index|word-wrap|word-spacing|word-break|width|widows|white-space|visibility|vertical-align|unicode-bidi|transition-timing-function|transition-property|transition-duration|transition-delay|transition|transform-style|transform-origin|transform|top|text-underline-position|text-transform|text-shadow|text-rendering|text-overflow|text-indent|text-decoration-style|text-decoration-line|text-decoration-color|text-decoration|text-align-last|text-align|tab-size|table-layout|right|resize|quotes|position|pointer-events|perspective-origin|perspective|page-break-inside|page-break-before|page-break-after|padding-top|padding-right|padding-left|padding-bottom|padding|overflow-y|overflow-x|overflow-wrap|overflow|outline-width|outline-style|outline-offset|outline-color|outline|orphans|order|opacity|object-position|object-fit|normal|none|nav-up|nav-right|nav-left|nav-index|nav-down|min-width|min-height|max-width|max-height|mask|marks|margin-top|margin-right|margin-left|margin-bottom|margin|list-style-type|list-style-position|list-style-image|list-style|line-height|letter-spacing|left|justify-content|initial|inherit|ime-mode|image-orientation|image-resolution|image-rendering|icon|hyphens|height|font-weight|font-variant-ligatures|font-variant|font-style|font-stretch|font-size-adjust|font-size|font-language-override|font-kerning|font-feature-settings|font-family|font|float|flex-wrap|flex-shrink|flex-grow|flex-flow|flex-direction|flex-basis|flex|filter|empty-cells|display|direction|cursor|counter-reset|counter-increment|content|column-width|column-span|column-rule-width|column-rule-style|column-rule-color|column-rule|column-gap|column-fill|column-count|columns|color|clip-path|clip|clear|caption-side|break-inside|break-before|break-after|box-sizing|box-shadow|box-decoration-break|bottom|border-width|border-top-width|border-top-style|border-top-right-radius|border-top-left-radius|border-top-color|border-top|border-style|border-spacing|border-right-width|border-right-style|border-right-color|border-right|border-radius|border-left-width|border-left-style|border-left-color|border-left|border-image-width|border-image-source|border-image-slice|border-image-repeat|border-image-outset|border-image|border-color|border-collapse|border-bottom-width|border-bottom-style|border-bottom-right-radius|border-bottom-left-radius|border-bottom-color|border-bottom|border|background-size|background-repeat|background-position|background-origin|background-image|background-color|background-clip|background-attachment|background-blend-mode|background|backface-visibility|auto|animation-timing-function|animation-play-state|animation-name|animation-iteration-count|animation-fill-mode|animation-duration|animation-direction|animation-delay|animation|align-self|align-items|align-content)\\b",i:"[^\\s]"},{cN:"value",b:"\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\b"},{cN:"value",b:":",e:";",c:[r,i,o,e.CSSNM,e.QSM,e.ASM,{cN:"important",b:"!important"}]},{cN:"at_rule",b:"@",e:"[{;]",k:"mixin include extend for if else each while charset import debug media page content font-face namespace warn",c:[r,i,e.QSM,e.ASM,o,e.CSSNM,{cN:"preprocessor",b:"\\s[A-Za-z0-9_.-]+",r:0}]}]}});hljs.registerLanguage("cmake",function(e){return{aliases:["cmake.in"],cI:!0,k:{keyword:"add_custom_command add_custom_target add_definitions add_dependencies add_executable add_library add_subdirectory add_test aux_source_directory break build_command cmake_minimum_required cmake_policy configure_file create_test_sourcelist define_property else elseif enable_language enable_testing endforeach endfunction endif endmacro endwhile execute_process export find_file find_library find_package find_path find_program fltk_wrap_ui foreach function get_cmake_property get_directory_property get_filename_component get_property get_source_file_property get_target_property get_test_property if include include_directories include_external_msproject include_regular_expression install link_directories load_cache load_command macro mark_as_advanced message option output_required_files project qt_wrap_cpp qt_wrap_ui remove_definitions return separate_arguments set set_directory_properties set_property set_source_files_properties set_target_properties set_tests_properties site_name source_group string target_link_libraries try_compile try_run unset variable_watch while build_name exec_program export_library_dependencies install_files install_programs install_targets link_libraries make_directory remove subdir_depends subdirs use_mangled_mesa utility_source variable_requires write_file qt5_use_modules qt5_use_package qt5_wrap_cpp on off true false and or",operator:"equal less greater strless strgreater strequal matches"},c:[{cN:"envvar",b:"\\${",e:"}"},e.HCM,e.QSM,e.NM]}});hljs.registerLanguage("java",function(e){var a=e.UIR+"(<"+e.UIR+">)?",t="false synchronized int abstract float private char boolean static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private",c="\\b(0[bB]([01]+[01_]+[01]+|[01]+)|0[xX]([a-fA-F0-9]+[a-fA-F0-9_]+[a-fA-F0-9]+|[a-fA-F0-9]+)|(([\\d]+[\\d_]+[\\d]+|[\\d]+)(\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))?|\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))([eE][-+]?\\d+)?)[lLfF]?",r={cN:"number",b:c,r:0};return{aliases:["jsp"],k:t,i:/<\/|#/,c:[e.C("/\\*\\*","\\*/",{r:0,c:[{cN:"doctag",b:"@[A-Za-z]+"}]}),e.CLCM,e.CBCM,e.ASM,e.QSM,{cN:"class",bK:"class interface",e:/[{;=]/,eE:!0,k:"class interface",i:/[:"\[\]]/,c:[{bK:"extends implements"},e.UTM]},{bK:"new throw return else",r:0},{cN:"function",b:"("+a+"\\s+)+"+e.UIR+"\\s*\\(",rB:!0,e:/[{;=]/,eE:!0,k:t,c:[{b:e.UIR+"\\s*\\(",rB:!0,r:0,c:[e.UTM]},{cN:"params",b:/\(/,e:/\)/,k:t,r:0,c:[e.ASM,e.QSM,e.CNM,e.CBCM]},e.CLCM,e.CBCM]},r,{cN:"annotation",b:"@[A-Za-z]+"}]}});hljs.registerLanguage("vala",function(e){return{k:{keyword:"char uchar unichar int uint long ulong short ushort int8 int16 int32 int64 uint8 uint16 uint32 uint64 float double bool struct enum string void weak unowned owned async signal static abstract interface override while do for foreach else switch case break default return try catch public private protected internal using new this get set const stdout stdin stderr var",built_in:"DBus GLib CCode Gee Object",literal:"false true null"},c:[{cN:"class",bK:"class interface delegate namespace",e:"{",eE:!0,i:"[^,:\\n\\s\\.]",c:[e.UTM]},e.CLCM,e.CBCM,{cN:"string",b:'"""',e:'"""',r:5},e.ASM,e.QSM,e.CNM,{cN:"preprocessor",b:"^#",e:"$",r:2},{cN:"constant",b:" [A-Z_]+ ",r:0}]}});hljs.registerLanguage("axapta",function(e){return{k:"false int abstract private char boolean static null if for true while long throw finally protected final return void enum else break new catch byte super case short default double public try this switch continue reverse firstfast firstonly forupdate nofetch sum avg minof maxof count order group by asc desc index hint like dispaly edit client server ttsbegin ttscommit str real date container anytype common div mod",c:[e.CLCM,e.CBCM,e.ASM,e.QSM,e.CNM,{cN:"preprocessor",b:"#",e:"$"},{cN:"class",bK:"class interface",e:"{",eE:!0,i:":",c:[{bK:"extends implements"},e.UTM]}]}});hljs.registerLanguage("haml",function(s){return{cI:!0,c:[{cN:"doctype",b:"^!!!( (5|1\\.1|Strict|Frameset|Basic|Mobile|RDFa|XML\\b.*))?$",r:10},s.C("^\\s*(!=#|=#|-#|/).*$",!1,{r:0}),{b:"^\\s*(-|=|!=)(?!#)",starts:{e:"\\n",sL:"ruby"}},{cN:"tag",b:"^\\s*%",c:[{cN:"title",b:"\\w+"},{cN:"value",b:"[#\\.][\\w-]+"},{b:"{\\s*",e:"\\s*}",eE:!0,c:[{b:":\\w+\\s*=>",e:",\\s+",rB:!0,eW:!0,c:[{cN:"symbol",b:":\\w+"},s.ASM,s.QSM,{b:"\\w+",r:0}]}]},{b:"\\(\\s*",e:"\\s*\\)",eE:!0,c:[{b:"\\w+\\s*=",e:"\\s+",rB:!0,eW:!0,c:[{cN:"attribute",b:"\\w+",r:0},s.ASM,s.QSM,{b:"\\w+",r:0}]}]}]},{cN:"bullet",b:"^\\s*[=~]\\s*",r:0},{b:"#{",starts:{e:"}",sL:"ruby"}}]}});hljs.registerLanguage("php",function(e){var c={cN:"variable",b:"\\$+[a-zA-Z_-ÿ][a-zA-Z0-9_-ÿ]*"},a={cN:"preprocessor",b:/<\?(php)?|\?>/},i={cN:"string",c:[e.BE,a],v:[{b:'b"',e:'"'},{b:"b'",e:"'"},e.inherit(e.ASM,{i:null}),e.inherit(e.QSM,{i:null})]},t={v:[e.BNM,e.CNM]};return{aliases:["php3","php4","php5","php6"],cI:!0,k:"and include_once list abstract global private echo interface as static endswitch array null if endwhile or const for endforeach self var while isset public protected exit foreach throw elseif include __FILE__ empty require_once do xor return parent clone use __CLASS__ __LINE__ else break print eval new catch __METHOD__ case exception default die require __FUNCTION__ enddeclare final try switch continue endfor endif declare unset true false trait goto instanceof insteadof __DIR__ __NAMESPACE__ yield finally",c:[e.CLCM,e.HCM,e.C("/\\*","\\*/",{c:[{cN:"doctag",b:"@[A-Za-z]+"},a]}),e.C("__halt_compiler.+?;",!1,{eW:!0,k:"__halt_compiler",l:e.UIR}),{cN:"string",b:/<<<['"]?\w+['"]?$/,e:/^\w+;?$/,c:[e.BE,{cN:"subst",v:[{b:/\$\w+/},{b:/\{\$/,e:/\}/}]}]},a,c,{b:/(::|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/},{cN:"function",bK:"function",e:/[;{]/,eE:!0,i:"\\$|\\[|%",c:[e.UTM,{cN:"params",b:"\\(",e:"\\)",c:["self",c,e.CBCM,i,t]}]},{cN:"class",bK:"class interface",e:"{",eE:!0,i:/[:\(\$"]/,c:[{bK:"extends implements"},e.UTM]},{bK:"namespace",e:";",i:/[\.']/,c:[e.UTM]},{bK:"use",e:";",c:[e.UTM]},{b:"=>"},i,t]}});hljs.registerLanguage("rsl",function(e){return{k:{keyword:"float color point normal vector matrix while for if do return else break extern continue",built_in:"abs acos ambient area asin atan atmosphere attribute calculatenormal ceil cellnoise clamp comp concat cos degrees depth Deriv diffuse distance Du Dv environment exp faceforward filterstep floor format fresnel incident length lightsource log match max min mod noise normalize ntransform opposite option phong pnoise pow printf ptlined radians random reflect refract renderinfo round setcomp setxcomp setycomp setzcomp shadow sign sin smoothstep specular specularbrdf spline sqrt step tan texture textureinfo trace transform vtransform xcomp ycomp zcomp"},i:"",sL:"xml",r:0}],r:10},{cN:"bullet",b:"^(\\*+|\\-+|\\.+|[^\\n]+?::)\\s+"},{cN:"label",b:"^(NOTE|TIP|IMPORTANT|WARNING|CAUTION):\\s+",r:10},{cN:"strong",b:"\\B\\*(?![\\*\\s])",e:"(\\n{2}|\\*)",c:[{b:"\\\\*\\w",r:0}]},{cN:"emphasis",b:"\\B'(?!['\\s])",e:"(\\n{2}|')",c:[{b:"\\\\'\\w",r:0}],r:0},{cN:"emphasis",b:"_(?![_\\s])",e:"(\\n{2}|_)",r:0},{cN:"smartquote",v:[{b:"``.+?''"},{b:"`.+?'"}]},{cN:"code",b:"(`.+?`|\\+.+?\\+)",r:0},{cN:"code",b:"^[ \\t]",e:"$",r:0},{cN:"horizontal_rule",b:"^'{3,}[ \\t]*$",r:10},{b:"(link:)?(http|https|ftp|file|irc|image:?):\\S+\\[.*?\\]",rB:!0,c:[{b:"(link|image:?):",r:0},{cN:"link_url",b:"\\w",e:"[^\\[]+",r:0},{cN:"link_label",b:"\\[",e:"\\]",eB:!0,eE:!0,r:0}],r:10}]}});hljs.registerLanguage("go",function(e){var t={keyword:"break default func interface select case map struct chan else goto package switch const fallthrough if range type continue for import return var go defer",constant:"true false iota nil",typename:"bool byte complex64 complex128 float32 float64 int8 int16 int32 int64 string uint8 uint16 uint32 uint64 int uint uintptr rune",built_in:"append cap close complex copy imag len make new panic print println real recover delete"};return{aliases:["golang"],k:t,i:"",c:[e.inherit(e.TM,{b:/'[a-zA-Z0-9_]+/})]};return{aliases:["fs"],k:"abstract and as assert base begin class default delegate do done downcast downto elif else end exception extern false finally for fun function global if in inherit inline interface internal lazy let match member module mutable namespace new null of open or override private public rec return sig static struct then to true try type upcast use val void when while with yield",i:/\/\*/,c:[{cN:"keyword",b:/\b(yield|return|let|do)!/},{cN:"string",b:'@"',e:'"',c:[{b:'""'}]},{cN:"string",b:'"""',e:'"""'},e.C("\\(\\*","\\*\\)"),{cN:"class",bK:"type",e:"\\(|=|$",eE:!0,c:[e.UTM,t]},{cN:"annotation",b:"\\[<",e:">\\]",r:10},{cN:"attribute",b:"\\B('[A-Za-z])\\b",c:[e.BE]},e.CLCM,e.inherit(e.QSM,{i:null}),e.CNM]}});hljs.registerLanguage("d",function(e){var r={keyword:"abstract alias align asm assert auto body break byte case cast catch class const continue debug default delete deprecated do else enum export extern final finally for foreach foreach_reverse|10 goto if immutable import in inout int interface invariant is lazy macro mixin module new nothrow out override package pragma private protected public pure ref return scope shared static struct super switch synchronized template this throw try typedef typeid typeof union unittest version void volatile while with __FILE__ __LINE__ __gshared|10 __thread __traits __DATE__ __EOF__ __TIME__ __TIMESTAMP__ __VENDOR__ __VERSION__",built_in:"bool cdouble cent cfloat char creal dchar delegate double dstring float function idouble ifloat ireal long real short string ubyte ucent uint ulong ushort wchar wstring",literal:"false null true"},t="(0|[1-9][\\d_]*)",a="(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)",i="0[bB][01_]+",n="([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*)",c="0[xX]"+n,_="([eE][+-]?"+a+")",d="("+a+"(\\.\\d*|"+_+")|\\d+\\."+a+a+"|\\."+t+_+"?)",o="(0[xX]("+n+"\\."+n+"|\\.?"+n+")[pP][+-]?"+a+")",s="("+t+"|"+i+"|"+c+")",l="("+o+"|"+d+")",u="\\\\(['\"\\?\\\\abfnrtv]|u[\\dA-Fa-f]{4}|[0-7]{1,3}|x[\\dA-Fa-f]{2}|U[\\dA-Fa-f]{8})|&[a-zA-Z\\d]{2,};",b={cN:"number",b:"\\b"+s+"(L|u|U|Lu|LU|uL|UL)?",r:0},f={cN:"number",b:"\\b("+l+"([fF]|L|i|[fF]i|Li)?|"+s+"(i|[fF]i|Li))",r:0},g={cN:"string",b:"'("+u+"|.)",e:"'",i:"."},h={b:u,r:0},p={cN:"string",b:'"',c:[h],e:'"[cwd]?'},w={cN:"string",b:'[rq]"',e:'"[cwd]?',r:5},N={cN:"string",b:"`",e:"`[cwd]?"},A={cN:"string",b:'x"[\\da-fA-F\\s\\n\\r]*"[cwd]?',r:10},F={cN:"string",b:'q"\\{',e:'\\}"'},m={cN:"shebang",b:"^#!",e:"$",r:5},y={cN:"preprocessor",b:"#(line)",e:"$",r:5},L={cN:"keyword",b:"@[a-zA-Z_][a-zA-Z_\\d]*"},v=e.C("\\/\\+","\\+\\/",{c:["self"],r:10});return{l:e.UIR,k:r,c:[e.CLCM,e.CBCM,v,A,p,w,N,F,f,b,g,m,y,L]}}); -\ No newline at end of file -+/*! highlight.js v9.5.0 | BSD3 License | git.io/hljslicense */ ! function(e) { -+ var n = "object" == typeof window && window || "object" == typeof self && self; -+ "undefined" != typeof exports ? e(exports) : n && (n.hljs = e({}), "function" == typeof define && define.amd && define([], function() { -+ return n.hljs -+ })) -+}(function(e) { -+ function n(e) { -+ return e.replace(/[&<>]/gm, function(e) { -+ return I[e] -+ }) -+ } -+ -+ function t(e) { -+ return e.nodeName.toLowerCase() -+ } -+ -+ function r(e, n) { -+ var t = e && e.exec(n); -+ return t && 0 === t.index -+ } -+ -+ function a(e) { -+ return k.test(e) -+ } -+ -+ function i(e) { -+ var n, t, r, i, o = e.className + " "; -+ if (o += e.parentNode ? e.parentNode.className : "", t = B.exec(o)) return R(t[1]) ? t[1] : "no-highlight"; -+ for (o = o.split(/\s+/), n = 0, r = o.length; r > n; n++) -+ if (i = o[n], a(i) || R(i)) return i -+ } -+ -+ function o(e, n) { -+ var t, r = {}; -+ for (t in e) r[t] = e[t]; -+ if (n) -+ for (t in n) r[t] = n[t]; -+ return r -+ } -+ -+ function u(e) { -+ var n = []; -+ return function r(e, a) { -+ for (var i = e.firstChild; i; i = i.nextSibling) 3 === i.nodeType ? a += i.nodeValue.length : 1 === i.nodeType && (n.push({ -+ event: "start", -+ offset: a, -+ node: i -+ }), a = r(i, a), t(i).match(/br|hr|img|input/) || n.push({ -+ event: "stop", -+ offset: a, -+ node: i -+ })); -+ return a -+ }(e, 0), n -+ } -+ -+ function c(e, r, a) { -+ function i() { -+ return e.length && r.length ? e[0].offset !== r[0].offset ? e[0].offset < r[0].offset ? e : r : "start" === r[0].event ? e : r : e.length ? e : r -+ } -+ -+ function o(e) { -+ function r(e) { -+ return " " + e.nodeName + '="' + n(e.value) + '"' -+ } -+ l += "<" + t(e) + w.map.call(e.attributes, r).join("") + ">" -+ } -+ -+ function u(e) { -+ l += "" -+ } -+ -+ function c(e) { -+ ("start" === e.event ? o : u)(e.node) -+ } -+ for (var s = 0, l = "", f = []; e.length || r.length;) { -+ var g = i(); -+ if (l += n(a.substr(s, g[0].offset - s)), s = g[0].offset, g === e) { -+ f.reverse().forEach(u); -+ do c(g.splice(0, 1)[0]), g = i(); while (g === e && g.length && g[0].offset === s); -+ f.reverse().forEach(o) -+ } else "start" === g[0].event ? f.push(g[0].node) : f.pop(), c(g.splice(0, 1)[0]) -+ } -+ return l + n(a.substr(s)) -+ } -+ -+ function s(e) { -+ function n(e) { -+ return e && e.source || e -+ } -+ -+ function t(t, r) { -+ return new RegExp(n(t), "m" + (e.cI ? "i" : "") + (r ? "g" : "")) -+ } -+ -+ function r(a, i) { -+ if (!a.compiled) { -+ if (a.compiled = !0, a.k = a.k || a.bK, a.k) { -+ var u = {}, -+ c = function(n, t) { -+ e.cI && (t = t.toLowerCase()), t.split(" ").forEach(function(e) { -+ var t = e.split("|"); -+ u[t[0]] = [n, t[1] ? Number(t[1]) : 1] -+ }) -+ }; -+ "string" == typeof a.k ? c("keyword", a.k) : E(a.k).forEach(function(e) { -+ c(e, a.k[e]) -+ }), a.k = u -+ } -+ a.lR = t(a.l || /\w+/, !0), i && (a.bK && (a.b = "\\b(" + a.bK.split(" ").join("|") + ")\\b"), a.b || (a.b = /\B|\b/), a.bR = t(a.b), a.e || a.eW || (a.e = /\B|\b/), a.e && (a.eR = t(a.e)), a.tE = n(a.e) || "", a.eW && i.tE && (a.tE += (a.e ? "|" : "") + i.tE)), a.i && (a.iR = t(a.i)), null == a.r && (a.r = 1), a.c || (a.c = []); -+ var s = []; -+ a.c.forEach(function(e) { -+ e.v ? e.v.forEach(function(n) { -+ s.push(o(e, n)) -+ }) : s.push("self" === e ? a : e) -+ }), a.c = s, a.c.forEach(function(e) { -+ r(e, a) -+ }), a.starts && r(a.starts, i); -+ var l = a.c.map(function(e) { -+ return e.bK ? "\\.?(" + e.b + ")\\.?" : e.b -+ }).concat([a.tE, a.i]).map(n).filter(Boolean); -+ a.t = l.length ? t(l.join("|"), !0) : { -+ exec: function() { -+ return null -+ } -+ } -+ } -+ } -+ r(e) -+ } -+ -+ function l(e, t, a, i) { -+ function o(e, n) { -+ for (var t = 0; t < n.c.length; t++) -+ if (r(n.c[t].bR, e)) return n.c[t] -+ } -+ -+ function u(e, n) { -+ if (r(e.eR, n)) { -+ for (; e.endsParent && e.parent;) e = e.parent; -+ return e -+ } -+ return e.eW ? u(e.parent, n) : void 0 -+ } -+ -+ function c(e, n) { -+ return !a && r(n.iR, e) -+ } -+ -+ function g(e, n) { -+ var t = N.cI ? n[0].toLowerCase() : n[0]; -+ return e.k.hasOwnProperty(t) && e.k[t] -+ } -+ -+ function h(e, n, t, r) { -+ var a = r ? "" : y.classPrefix, -+ i = '', i + n + o -+ } -+ -+ function p() { -+ var e, t, r, a; -+ if (!E.k) return n(B); -+ for (a = "", t = 0, E.lR.lastIndex = 0, r = E.lR.exec(B); r;) a += n(B.substr(t, r.index - t)), e = g(E, r), e ? (M += e[1], a += h(e[0], n(r[0]))) : a += n(r[0]), t = E.lR.lastIndex, r = E.lR.exec(B); -+ return a + n(B.substr(t)) -+ } -+ -+ function d() { -+ var e = "string" == typeof E.sL; -+ if (e && !x[E.sL]) return n(B); -+ var t = e ? l(E.sL, B, !0, L[E.sL]) : f(B, E.sL.length ? E.sL : void 0); -+ return E.r > 0 && (M += t.r), e && (L[E.sL] = t.top), h(t.language, t.value, !1, !0) -+ } -+ -+ function b() { -+ k += null != E.sL ? d() : p(), B = "" -+ } -+ -+ function v(e) { -+ k += e.cN ? h(e.cN, "", !0) : "", E = Object.create(e, { -+ parent: { -+ value: E -+ } -+ }) -+ } -+ -+ function m(e, n) { -+ if (B += e, null == n) return b(), 0; -+ var t = o(n, E); -+ if (t) return t.skip ? B += n : (t.eB && (B += n), b(), t.rB || t.eB || (B = n)), v(t, n), t.rB ? 0 : n.length; -+ var r = u(E, n); -+ if (r) { -+ var a = E; -+ a.skip ? B += n : (a.rE || a.eE || (B += n), b(), a.eE && (B = n)); -+ do E.cN && (k += C), E.skip || (M += E.r), E = E.parent; while (E !== r.parent); -+ return r.starts && v(r.starts, ""), a.rE ? 0 : n.length -+ } -+ if (c(n, E)) throw new Error('Illegal lexeme "' + n + '" for mode "' + (E.cN || "") + '"'); -+ return B += n, n.length || 1 -+ } -+ var N = R(e); -+ if (!N) throw new Error('Unknown language: "' + e + '"'); -+ s(N); -+ var w, E = i || N, -+ L = {}, -+ k = ""; -+ for (w = E; w !== N; w = w.parent) w.cN && (k = h(w.cN, "", !0) + k); -+ var B = "", -+ M = 0; -+ try { -+ for (var I, j, O = 0;;) { -+ if (E.t.lastIndex = O, I = E.t.exec(t), !I) break; -+ j = m(t.substr(O, I.index - O), I[0]), O = I.index + j -+ } -+ for (m(t.substr(O)), w = E; w.parent; w = w.parent) w.cN && (k += C); -+ return { -+ r: M, -+ value: k, -+ language: e, -+ top: E -+ } -+ } catch (T) { -+ if (T.message && -1 !== T.message.indexOf("Illegal")) return { -+ r: 0, -+ value: n(t) -+ }; -+ throw T -+ } -+ } -+ -+ function f(e, t) { -+ t = t || y.languages || E(x); -+ var r = { -+ r: 0, -+ value: n(e) -+ }, -+ a = r; -+ return t.filter(R).forEach(function(n) { -+ var t = l(n, e, !1); -+ t.language = n, t.r > a.r && (a = t), t.r > r.r && (a = r, r = t) -+ }), a.language && (r.second_best = a), r -+ } -+ -+ function g(e) { -+ return y.tabReplace || y.useBR ? e.replace(M, function(e, n) { -+ return y.useBR && "\n" === e ? "
" : y.tabReplace ? n.replace(/\t/g, y.tabReplace) : void 0 -+ }) : e -+ } -+ -+ function h(e, n, t) { -+ var r = n ? L[n] : t, -+ a = [e.trim()]; -+ return e.match(/\bhljs\b/) || a.push("hljs"), -1 === e.indexOf(r) && a.push(r), a.join(" ").trim() -+ } -+ -+ function p(e) { -+ var n, t, r, o, s, p = i(e); -+ a(p) || (y.useBR ? (n = document.createElementNS("http://www.w3.org/1999/xhtml", "div"), n.innerHTML = e.innerHTML.replace(/\n/g, "").replace(//g, "\n")) : n = e, s = n.textContent, r = p ? l(p, s, !0) : f(s), t = u(n), t.length && (o = document.createElementNS("http://www.w3.org/1999/xhtml", "div"), o.innerHTML = r.value, r.value = c(t, u(o), s)), r.value = g(r.value), e.innerHTML = r.value, e.className = h(e.className, p, r.language), e.result = { -+ language: r.language, -+ re: r.r -+ }, r.second_best && (e.second_best = { -+ language: r.second_best.language, -+ re: r.second_best.r -+ })) -+ } -+ -+ function d(e) { -+ y = o(y, e) -+ } -+ -+ function b() { -+ if (!b.called) { -+ b.called = !0; -+ var e = document.querySelectorAll("pre code"); -+ w.forEach.call(e, p) -+ } -+ } -+ -+ function v() { -+ addEventListener("DOMContentLoaded", b, !1), addEventListener("load", b, !1) -+ } -+ -+ function m(n, t) { -+ var r = x[n] = t(e); -+ r.aliases && r.aliases.forEach(function(e) { -+ L[e] = n -+ }) -+ } -+ -+ function N() { -+ return E(x) -+ } -+ -+ function R(e) { -+ return e = (e || "").toLowerCase(), x[e] || x[L[e]] -+ } -+ var w = [], -+ E = Object.keys, -+ x = {}, -+ L = {}, -+ k = /^(no-?highlight|plain|text)$/i, -+ B = /\blang(?:uage)?-([\w-]+)\b/i, -+ M = /((^(<[^>]+>|\t|)+|(?:\n)))/gm, -+ C = "
", -+ y = { -+ classPrefix: "hljs-", -+ tabReplace: null, -+ useBR: !1, -+ languages: void 0 -+ }, -+ I = { -+ "&": "&", -+ "<": "<", -+ ">": ">" -+ }; -+ return e.highlight = l, e.highlightAuto = f, e.fixMarkup = g, e.highlightBlock = p, e.configure = d, e.initHighlighting = b, e.initHighlightingOnLoad = v, e.registerLanguage = m, e.listLanguages = N, e.getLanguage = R, e.inherit = o, e.IR = "[a-zA-Z]\\w*", e.UIR = "[a-zA-Z_]\\w*", e.NR = "\\b\\d+(\\.\\d+)?", e.CNR = "(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)", e.BNR = "\\b(0b[01]+)", e.RSR = "!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~", e.BE = { -+ b: "\\\\[\\s\\S]", -+ r: 0 -+ }, e.ASM = { -+ cN: "string", -+ b: "'", -+ e: "'", -+ i: "\\n", -+ c: [e.BE] -+ }, e.QSM = { -+ cN: "string", -+ b: '"', -+ e: '"', -+ i: "\\n", -+ c: [e.BE] -+ }, e.PWM = { -+ b: /\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|like)\b/ -+ }, e.C = function(n, t, r) { -+ var a = e.inherit({ -+ cN: "comment", -+ b: n, -+ e: t, -+ c: [] -+ }, r || {}); -+ return a.c.push(e.PWM), a.c.push({ -+ cN: "doctag", -+ b: "(?:TODO|FIXME|NOTE|BUG|XXX):", -+ r: 0 -+ }), a -+ }, e.CLCM = e.C("//", "$"), e.CBCM = e.C("/\\*", "\\*/"), e.HCM = e.C("#", "$"), e.NM = { -+ cN: "number", -+ b: e.NR, -+ r: 0 -+ }, e.CNM = { -+ cN: "number", -+ b: e.CNR, -+ r: 0 -+ }, e.BNM = { -+ cN: "number", -+ b: e.BNR, -+ r: 0 -+ }, e.CSSNM = { -+ cN: "number", -+ b: e.NR + "(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?", -+ r: 0 -+ }, e.RM = { -+ cN: "regexp", -+ b: /\//, -+ e: /\/[gimuy]*/, -+ i: /\n/, -+ c: [e.BE, { -+ b: /\[/, -+ e: /\]/, -+ r: 0, -+ c: [e.BE] -+ }] -+ }, e.TM = { -+ cN: "title", -+ b: e.IR, -+ r: 0 -+ }, e.UTM = { -+ cN: "title", -+ b: e.UIR, -+ r: 0 -+ }, e.METHOD_GUARD = { -+ b: "\\.\\s*" + e.UIR, -+ r: 0 -+ }, e -+}); -+hljs.registerLanguage("glsl", function(e) { -+ return { -+ k: { -+ keyword: "break continue discard do else for if return whileattribute binding buffer ccw centroid centroid varying coherent column_major const cw depth_any depth_greater depth_less depth_unchanged early_fragment_tests equal_spacing flat fractional_even_spacing fractional_odd_spacing highp in index inout invariant invocations isolines layout line_strip lines lines_adjacency local_size_x local_size_y local_size_z location lowp max_vertices mediump noperspective offset origin_upper_left out packed patch pixel_center_integer point_mode points precise precision quads r11f_g11f_b10f r16 r16_snorm r16f r16i r16ui r32f r32i r32ui r8 r8_snorm r8i r8ui readonly restrict rg16 rg16_snorm rg16f rg16i rg16ui rg32f rg32i rg32ui rg8 rg8_snorm rg8i rg8ui rgb10_a2 rgb10_a2ui rgba16 rgba16_snorm rgba16f rgba16i rgba16ui rgba32f rgba32i rgba32ui rgba8 rgba8_snorm rgba8i rgba8ui row_major sample shared smooth std140 std430 stream triangle_strip triangles triangles_adjacency uniform varying vertices volatile writeonly", -+ type: "atomic_uint bool bvec2 bvec3 bvec4 dmat2 dmat2x2 dmat2x3 dmat2x4 dmat3 dmat3x2 dmat3x3 dmat3x4 dmat4 dmat4x2 dmat4x3 dmat4x4 double dvec2 dvec3 dvec4 float iimage1D iimage1DArray iimage2D iimage2DArray iimage2DMS iimage2DMSArray iimage2DRect iimage3D iimageBufferiimageCube iimageCubeArray image1D image1DArray image2D image2DArray image2DMS image2DMSArray image2DRect image3D imageBuffer imageCube imageCubeArray int isampler1D isampler1DArray isampler2D isampler2DArray isampler2DMS isampler2DMSArray isampler2DRect isampler3D isamplerBuffer isamplerCube isamplerCubeArray ivec2 ivec3 ivec4 mat2 mat2x2 mat2x3 mat2x4 mat3 mat3x2 mat3x3 mat3x4 mat4 mat4x2 mat4x3 mat4x4 sampler1D sampler1DArray sampler1DArrayShadow sampler1DShadow sampler2D sampler2DArray sampler2DArrayShadow sampler2DMS sampler2DMSArray sampler2DRect sampler2DRectShadow sampler2DShadow sampler3D samplerBuffer samplerCube samplerCubeArray samplerCubeArrayShadow samplerCubeShadow image1D uimage1DArray uimage2D uimage2DArray uimage2DMS uimage2DMSArray uimage2DRect uimage3D uimageBuffer uimageCube uimageCubeArray uint usampler1D usampler1DArray usampler2D usampler2DArray usampler2DMS usampler2DMSArray usampler2DRect usampler3D samplerBuffer usamplerCube usamplerCubeArray uvec2 uvec3 uvec4 vec2 vec3 vec4 void", -+ built_in: "gl_MaxAtomicCounterBindings gl_MaxAtomicCounterBufferSize gl_MaxClipDistances gl_MaxClipPlanes gl_MaxCombinedAtomicCounterBuffers gl_MaxCombinedAtomicCounters gl_MaxCombinedImageUniforms gl_MaxCombinedImageUnitsAndFragmentOutputs gl_MaxCombinedTextureImageUnits gl_MaxComputeAtomicCounterBuffers gl_MaxComputeAtomicCounters gl_MaxComputeImageUniforms gl_MaxComputeTextureImageUnits gl_MaxComputeUniformComponents gl_MaxComputeWorkGroupCount gl_MaxComputeWorkGroupSize gl_MaxDrawBuffers gl_MaxFragmentAtomicCounterBuffers gl_MaxFragmentAtomicCounters gl_MaxFragmentImageUniforms gl_MaxFragmentInputComponents gl_MaxFragmentInputVectors gl_MaxFragmentUniformComponents gl_MaxFragmentUniformVectors gl_MaxGeometryAtomicCounterBuffers gl_MaxGeometryAtomicCounters gl_MaxGeometryImageUniforms gl_MaxGeometryInputComponents gl_MaxGeometryOutputComponents gl_MaxGeometryOutputVertices gl_MaxGeometryTextureImageUnits gl_MaxGeometryTotalOutputComponents gl_MaxGeometryUniformComponents gl_MaxGeometryVaryingComponents gl_MaxImageSamples gl_MaxImageUnits gl_MaxLights gl_MaxPatchVertices gl_MaxProgramTexelOffset gl_MaxTessControlAtomicCounterBuffers gl_MaxTessControlAtomicCounters gl_MaxTessControlImageUniforms gl_MaxTessControlInputComponents gl_MaxTessControlOutputComponents gl_MaxTessControlTextureImageUnits gl_MaxTessControlTotalOutputComponents gl_MaxTessControlUniformComponents gl_MaxTessEvaluationAtomicCounterBuffers gl_MaxTessEvaluationAtomicCounters gl_MaxTessEvaluationImageUniforms gl_MaxTessEvaluationInputComponents gl_MaxTessEvaluationOutputComponents gl_MaxTessEvaluationTextureImageUnits gl_MaxTessEvaluationUniformComponents gl_MaxTessGenLevel gl_MaxTessPatchComponents gl_MaxTextureCoords gl_MaxTextureImageUnits gl_MaxTextureUnits gl_MaxVaryingComponents gl_MaxVaryingFloats gl_MaxVaryingVectors gl_MaxVertexAtomicCounterBuffers gl_MaxVertexAtomicCounters gl_MaxVertexAttribs gl_MaxVertexImageUniforms gl_MaxVertexOutputComponents gl_MaxVertexOutputVectors gl_MaxVertexTextureImageUnits gl_MaxVertexUniformComponents gl_MaxVertexUniformVectors gl_MaxViewports gl_MinProgramTexelOffset gl_BackColor gl_BackLightModelProduct gl_BackLightProduct gl_BackMaterial gl_BackSecondaryColor gl_ClipDistance gl_ClipPlane gl_ClipVertex gl_Color gl_DepthRange gl_EyePlaneQ gl_EyePlaneR gl_EyePlaneS gl_EyePlaneT gl_Fog gl_FogCoord gl_FogFragCoord gl_FragColor gl_FragCoord gl_FragData gl_FragDepth gl_FrontColor gl_FrontFacing gl_FrontLightModelProduct gl_FrontLightProduct gl_FrontMaterial gl_FrontSecondaryColor gl_GlobalInvocationID gl_InstanceID gl_InvocationID gl_Layer gl_LightModel gl_LightSource gl_LocalInvocationID gl_LocalInvocationIndex gl_ModelViewMatrix gl_ModelViewMatrixInverse gl_ModelViewMatrixInverseTranspose gl_ModelViewMatrixTranspose gl_ModelViewProjectionMatrix gl_ModelViewProjectionMatrixInverse gl_ModelViewProjectionMatrixInverseTranspose gl_ModelViewProjectionMatrixTranspose gl_MultiTexCoord0 gl_MultiTexCoord1 gl_MultiTexCoord2 gl_MultiTexCoord3 gl_MultiTexCoord4 gl_MultiTexCoord5 gl_MultiTexCoord6 gl_MultiTexCoord7 gl_Normal gl_NormalMatrix gl_NormalScale gl_NumSamples gl_NumWorkGroups gl_ObjectPlaneQ gl_ObjectPlaneR gl_ObjectPlaneS gl_ObjectPlaneT gl_PatchVerticesIn gl_Point gl_PointCoord gl_PointSize gl_Position gl_PrimitiveID gl_PrimitiveIDIn gl_ProjectionMatrix gl_ProjectionMatrixInverse gl_ProjectionMatrixInverseTranspose gl_ProjectionMatrixTranspose gl_SampleID gl_SampleMask gl_SampleMaskIn gl_SamplePosition gl_SecondaryColor gl_TessCoord gl_TessLevelInner gl_TessLevelOuter gl_TexCoord gl_TextureEnvColor gl_TextureMatrix gl_TextureMatrixInverse gl_TextureMatrixInverseTranspose gl_TextureMatrixTranspose gl_Vertex gl_VertexID gl_ViewportIndex gl_WorkGroupID gl_WorkGroupSize gl_in gl_out EmitStreamVertex EmitVertex EndPrimitive EndStreamPrimitive abs acos acosh all any asin asinh atan atanh atomicAdd atomicAnd atomicCompSwap atomicCounter atomicCounterDecrement atomicCounterIncrement atomicExchange atomicMax atomicMin atomicOr atomicXor barrier bitCount bitfieldExtract bitfieldInsert bitfieldReverse ceil clamp cos cosh cross dFdx dFdy degrees determinant distance dot equal exp exp2 faceforward findLSB findMSB floatBitsToInt floatBitsToUint floor fma fract frexp ftransform fwidth greaterThan greaterThanEqual groupMemoryBarrier imageAtomicAdd imageAtomicAnd imageAtomicCompSwap imageAtomicExchange imageAtomicMax imageAtomicMin imageAtomicOr imageAtomicXor imageLoad imageSize imageStore imulExtended intBitsToFloat interpolateAtCentroid interpolateAtOffset interpolateAtSample inverse inversesqrt isinf isnan ldexp length lessThan lessThanEqual log log2 matrixCompMult max memoryBarrier memoryBarrierAtomicCounter memoryBarrierBuffer memoryBarrierImage memoryBarrierShared min mix mod modf noise1 noise2 noise3 noise4 normalize not notEqual outerProduct packDouble2x32 packHalf2x16 packSnorm2x16 packSnorm4x8 packUnorm2x16 packUnorm4x8 pow radians reflect refract round roundEven shadow1D shadow1DLod shadow1DProj shadow1DProjLod shadow2D shadow2DLod shadow2DProj shadow2DProjLod sign sin sinh smoothstep sqrt step tan tanh texelFetch texelFetchOffset texture texture1D texture1DLod texture1DProj texture1DProjLod texture2D texture2DLod texture2DProj texture2DProjLod texture3D texture3DLod texture3DProj texture3DProjLod textureCube textureCubeLod textureGather textureGatherOffset textureGatherOffsets textureGrad textureGradOffset textureLod textureLodOffset textureOffset textureProj textureProjGrad textureProjGradOffset textureProjLod textureProjLodOffset textureProjOffset textureQueryLevels textureQueryLod textureSize transpose trunc uaddCarry uintBitsToFloat umulExtended unpackDouble2x32 unpackHalf2x16 unpackSnorm2x16 unpackSnorm4x8 unpackUnorm2x16 unpackUnorm4x8 usubBorrow", -+ literal: "true false" -+ }, -+ i: '"', -+ c: [e.CLCM, e.CBCM, e.CNM, { -+ cN: "meta", -+ b: "#", -+ e: "$" -+ }] -+ } -+}); -+hljs.registerLanguage("makefile", function(e) { -+ var a = { -+ cN: "variable", -+ b: /\$\(/, -+ e: /\)/, -+ c: [e.BE] -+ }; -+ return { -+ aliases: ["mk", "mak"], -+ c: [e.HCM, { -+ b: /^\w+\s*\W*=/, -+ rB: !0, -+ r: 0, -+ starts: { -+ e: /\s*\W*=/, -+ eE: !0, -+ starts: { -+ e: /$/, -+ r: 0, -+ c: [a] -+ } -+ } -+ }, { -+ cN: "section", -+ b: /^[\w]+:\s*$/ -+ }, { -+ cN: "meta", -+ b: /^\.PHONY:/, -+ e: /$/, -+ k: { -+ "meta-keyword": ".PHONY" -+ }, -+ l: /[\.\w]+/ -+ }, { -+ b: /^\t+/, -+ e: /$/, -+ r: 0, -+ c: [e.QSM, a] -+ }] -+ } -+}); -+hljs.registerLanguage("cpp", function(t) { -+ var e = { -+ cN: "keyword", -+ b: "\\b[a-z\\d_]*_t\\b" -+ }, -+ foo = { -+ cN: "type", -+ b: "Vk[A-Za-z0-9]+" -+ }, -+ bar = { -+ cN: "function", -+ b: "vk[A-Z][A-Za-z0-9]+" -+ }, -+ baz = { -+ cN: "literal", -+ b: "VK_[A-Z_0-9]+" -+ }, -+ r = { -+ cN: "string", -+ v: [{ -+ b: '(u8?|U)?L?"', -+ e: '"', -+ i: "\\n", -+ c: [t.BE] -+ }, { -+ b: '(u8?|U)?R"', -+ e: '"', -+ c: [t.BE] -+ }, { -+ b: "'\\\\?.", -+ e: "'", -+ i: "." -+ }] -+ }, -+ s = { -+ cN: "number", -+ v: [{ -+ b: "\\b(0b[01'_]+)" -+ }, { -+ b: "\\b([\\d'_]+(\\.[\\d'_]*)?|\\.[\\d'_]+)(u|U|l|L|ul|UL|f|F|b|B)" -+ }, { -+ b: "(-?)(\\b0[xX][a-fA-F0-9'_]+|(\\b[\\d'_]+(\\.[\\d'_]*)?|\\.[\\d'_]+)([eE][-+]?[\\d'_]+)?)" -+ }], -+ r: 0 -+ }, -+ i = { -+ cN: "meta", -+ b: /#\s*[a-z]+\b/, -+ e: /$/, -+ k: { -+ "meta-keyword": "if else elif endif define undef warning error line pragma ifdef ifndef include" -+ }, -+ c: [{ -+ b: /\\\n/, -+ r: 0 -+ }, t.inherit(r, { -+ cN: "meta-string" -+ }), { -+ cN: "meta-string", -+ b: "<", -+ e: ">", -+ i: "\\n" -+ }, t.CLCM, t.CBCM] -+ }, -+ a = t.IR + "\\s*\\(", -+ c = { -+ keyword: "int float while private char catch export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const struct for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using class asm case typeid short reinterpret_cast|10 default double register explicit signed typename try this switch continue inline delete alignof constexpr decltype noexcept static_assert thread_local restrict _Bool complex _Complex _Imaginary atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong new throw return", -+ built_in: "std string cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap array shared_ptr abort abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr", -+ literal: "true false nullptr NULL", -+ type: "QueueFamilyIndices VDeleter Vertex" -+ }, -+ n = [e, foo, bar, baz, t.CLCM, t.CBCM, s, r]; -+ return { -+ aliases: ["c", "cc", "h", "c++", "h++", "hpp"], -+ k: c, -+ i: "", -+ k: c, -+ c: ["self", e] -+ }, { -+ b: t.IR + "::", -+ k: c -+ }, { -+ v: [{ -+ b: /=/, -+ e: /;/ -+ }, { -+ b: /\(/, -+ e: /\)/ -+ }, { -+ bK: "new throw return else", -+ e: /;/ -+ }], -+ k: c, -+ c: n.concat([{ -+ b: /\(/, -+ e: /\)/, -+ k: c, -+ c: n.concat(["self"]), -+ r: 0 -+ }]), -+ r: 0 -+ }, { -+ cN: "function", -+ b: "(" + t.IR + "[\\*&\\s]+)+" + a, -+ rB: !0, -+ e: /[{;=]/, -+ eE: !0, -+ k: c, -+ i: /[^\w\s\*&]/, -+ c: [{ -+ b: a, -+ rB: !0, -+ c: [t.TM], -+ r: 0 -+ }, { -+ cN: "params", -+ b: /\(/, -+ e: /\)/, -+ k: c, -+ r: 0, -+ c: [t.CLCM, t.CBCM, r, s, e] -+ }, t.CLCM, t.CBCM, i] -+ }]), -+ exports: { -+ preprocessor: i, -+ strings: r, -+ k: c -+ } -+ } -+}); -\ No newline at end of file -diff --git a/themes/daux/less/components.less b/themes/daux/less/components.less -index 45d17ba..940e461 100644 ---- a/themes/daux/less/components.less -+++ b/themes/daux/less/components.less -@@ -1,8 +1,32 @@ - /* =========================================================================================== - Componenets - ============================================================================================== */ -+ -+body { -+ font-family: 'Roboto Slab', @font-family-sans-serif; -+} -+ - a { - color: @light; -+ border-bottom: 1px dotted @light; -+ -+ &:hover { -+ border-bottom-style: solid; -+ border-bottom-color: @dark; -+ text-decoration: none; -+ } -+} -+ -+a > code { -+ border-bottom: 1px dotted @light; -+ -+ &:hover { -+ border-bottom-style: solid; -+ } -+} -+ -+a.folder { -+ border-bottom: none; - } - - .btn { -@@ -38,7 +62,22 @@ a { - } - - code { -- color: @light; -+ color: #666; -+ border: 1px solid #ddd; -+} -+ -+a { -+ code { -+ color: @light; -+ } -+} -+ -+.nav-logo { -+ background: #A41E22; -+ padding: 20px; -+ -+ color: white; -+ font-size: 40px; - } - - //Navbar -@@ -56,6 +95,8 @@ code { - color: @light; - text-shadow: none; - .roboto-slab.bold; -+ -+ border-bottom: none; - } - - .navbar-text { -@@ -77,6 +118,10 @@ code { - } - - //Sidebar Nav List -+.nav { -+ background: #272525; -+} -+ - .nav.nav-list { - padding-left: 0; - padding-right: 0; -@@ -86,10 +131,11 @@ code { - margin: 0; - padding: 6px 15px 6px 20px; - .roboto-slab.regular; -- color: @dark; -+ color: #B3B3B3; - font-size: 15px; - text-shadow: none; - border-color: @lines; -+ border-bottom: none; - - .arrow { - display: inline-block; -@@ -120,6 +166,7 @@ code { - color: @dark; - text-shadow: none; - background-color: @sidebar-hover; -+ border-bottom: none; - } - } - -@@ -157,7 +204,7 @@ code { - margin-left: -15px; - padding: 3px 30px; - border: none; -- color: @text; -+ color: #B3B3B3; - .opacity(0.70); - - &:hover { -@@ -167,7 +214,7 @@ code { - } - - &.active a { -- color: @dark; -+ color: #B3B3B3; - } - } - } -@@ -180,6 +227,10 @@ code { - - h1 { - margin-top: 0px; -+ -+ a { -+ border-bottom: none; -+ } - } - - sub-heading { -@@ -189,7 +240,7 @@ code { - - pre { - border: none; -- background-color: @light; -+ background-color: #343131; - border-radius: 0; - padding: 10px; - margin-left: -20px; -@@ -200,6 +251,7 @@ pre { - code { - background: transparent; - border: none; -+ color: white; - } - } - -@@ -349,8 +401,13 @@ table { - a { - font-size: 13px; - .roboto-slab.regular; -- color: @light; -+ color: white; - line-height: 28px; -+ border-bottom-color: white; -+ -+ &:hover { -+ border-bottom-color: white; -+ } - } - - .twitter { -diff --git a/themes/daux/less/highlight.less b/themes/daux/less/highlight.less -index ca34840..6da76fa 100644 ---- a/themes/daux/less/highlight.less -+++ b/themes/daux/less/highlight.less -@@ -15,121 +15,158 @@ Code Highlighting - .@{hljs-css-prefix}-lisp .@{hljs-css-prefix}-title, - .@{hljs-css-prefix}-clojure .@{hljs-css-prefix}-built_in, - .@{hljs-css-prefix}-nginx .@{hljs-css-prefix}-title { -- color: @dark; -+ color: #F8F8F2; - } - --.@{hljs-css-prefix}-string, --.@{hljs-css-prefix}-title, --.@{hljs-css-prefix}-constant, --.@{hljs-css-prefix}-parent, --.@{hljs-css-prefix}-tag .@{hljs-css-prefix}-value, --.@{hljs-css-prefix}-rules .@{hljs-css-prefix}-value, --.@{hljs-css-prefix}-rules .@{hljs-css-prefix}-value .@{hljs-css-prefix}-number, --.@{hljs-css-prefix}-preprocessor, --.@{hljs-css-prefix}-ruby .@{hljs-css-prefix}-symbol, --.@{hljs-css-prefix}-ruby .@{hljs-css-prefix}-symbol .@{hljs-css-prefix}-string, --.@{hljs-css-prefix}-aggregate, --.@{hljs-css-prefix}-template_tag, --.@{hljs-css-prefix}-django .@{hljs-css-prefix}-variable, --.@{hljs-css-prefix}-smalltalk .@{hljs-css-prefix}-class, --.@{hljs-css-prefix}-addition, --.@{hljs-css-prefix}-flow, --.@{hljs-css-prefix}-stream, --.@{hljs-css-prefix}-bash .@{hljs-css-prefix}-variable, --.@{hljs-css-prefix}-apache .@{hljs-css-prefix}-tag, --.@{hljs-css-prefix}-apache .@{hljs-css-prefix}-cbracket, --.@{hljs-css-prefix}-tex .@{hljs-css-prefix}-command, --.@{hljs-css-prefix}-tex .@{hljs-css-prefix}-special, --.@{hljs-css-prefix}-erlang_repl .@{hljs-css-prefix}-function_or_atom, --.@{hljs-css-prefix}-markdown .@{hljs-css-prefix}-header { -- color: @syntax-string; -+.@{hljs-css-prefix}-meta { -+ color: #75715E; - } - --.@{hljs-css-prefix}-comment, --.@{hljs-css-prefix}-annotation, --.@{hljs-css-prefix}-template_comment, --.@{hljs-css-prefix}-diff .@{hljs-css-prefix}-header, --.@{hljs-css-prefix}-chunk, --.@{hljs-css-prefix}-markdown .@{hljs-css-prefix}-blockquote { -- color: @syntax-comment; -+.@{hljs-css-prefix}-keyword { -+ color: #F92672; - } - --.@{hljs-css-prefix}-number, --.@{hljs-css-prefix}-date, --.@{hljs-css-prefix}-regexp, --.@{hljs-css-prefix}-literal, --.@{hljs-css-prefix}-smalltalk .@{hljs-css-prefix}-symbol, --.@{hljs-css-prefix}-smalltalk .@{hljs-css-prefix}-char, --.@{hljs-css-prefix}-go .@{hljs-css-prefix}-constant, --.@{hljs-css-prefix}-change, --.@{hljs-css-prefix}-markdown .@{hljs-css-prefix}-bullet, --.@{hljs-css-prefix}-markdown .@{hljs-css-prefix}-link_url { -- color: @syntax-number; --} -+.@{hljs-css-prefix}-function { -+ color: #89E229; - --.@{hljs-css-prefix}-label, --.@{hljs-css-prefix}-javadoc, --.@{hljs-css-prefix}-ruby .@{hljs-css-prefix}-string, --.@{hljs-css-prefix}-decorator, --.@{hljs-css-prefix}-filter .@{hljs-css-prefix}-argument, --.@{hljs-css-prefix}-localvars, --.@{hljs-css-prefix}-array, --.@{hljs-css-prefix}-attr_selector, --.@{hljs-css-prefix}-important, --.@{hljs-css-prefix}-pseudo, --.@{hljs-css-prefix}-pi, --.@{hljs-css-prefix}-doctype, --.@{hljs-css-prefix}-deletion, --.@{hljs-css-prefix}-envvar, --.@{hljs-css-prefix}-shebang, --.@{hljs-css-prefix}-apache .@{hljs-css-prefix}-sqbracket, --.@{hljs-css-prefix}-nginx .@{hljs-css-prefix}-built_in, --.@{hljs-css-prefix}-tex .@{hljs-css-prefix}-formula, --.@{hljs-css-prefix}-erlang_repl .@{hljs-css-prefix}-reserved, --.@{hljs-css-prefix}-prompt, --.@{hljs-css-prefix}-markdown .@{hljs-css-prefix}-link_label, --.@{hljs-css-prefix}-vhdl .@{hljs-css-prefix}-attribute, --.@{hljs-css-prefix}-clojure .@{hljs-css-prefix}-attribute, --.@{hljs-css-prefix}-coffeescript .@{hljs-css-prefix}-property { -- color: @syntax-label; -+ .@{hljs-css-prefix}-params { -+ color: white; -+ } - } - --.@{hljs-css-prefix}-keyword, --.@{hljs-css-prefix}-id, --.@{hljs-css-prefix}-phpdoc, --.@{hljs-css-prefix}-title, --.@{hljs-css-prefix}-built_in, --.@{hljs-css-prefix}-aggregate, --.@{hljs-css-prefix}-css .@{hljs-css-prefix}-tag, --.@{hljs-css-prefix}-javadoctag, --.@{hljs-css-prefix}-phpdoc, --.@{hljs-css-prefix}-yardoctag, --.@{hljs-css-prefix}-smalltalk .@{hljs-css-prefix}-class, --.@{hljs-css-prefix}-winutils, --.@{hljs-css-prefix}-bash .@{hljs-css-prefix}-variable, --.@{hljs-css-prefix}-apache .@{hljs-css-prefix}-tag, --.@{hljs-css-prefix}-go .@{hljs-css-prefix}-typename, --.@{hljs-css-prefix}-tex .@{hljs-css-prefix}-command, --.@{hljs-css-prefix}-markdown .@{hljs-css-prefix}-strong, --.@{hljs-css-prefix}-request, --.@{hljs-css-prefix}-status { -- font-weight: bold; -+.@{hljs-css-prefix}-literal, -+.@{hljs-css-prefix}-number { -+ color: #AE81FF; - } - --.@{hljs-css-prefix}-markdown .@{hljs-css-prefix}-emphasis { -- font-style: italic; -+.@{hljs-css-prefix}-string { -+ color: #E6DB74; - } - --.@{hljs-css-prefix}-nginx .@{hljs-css-prefix}-built_in { -- font-weight: normal; -+.@{hljs-css-prefix}-comment { -+ color: #75715E; - } - --.@{hljs-css-prefix}-coffeescript .@{hljs-css-prefix}-javascript, --.@{hljs-css-prefix}-javascript .@{hljs-css-prefix}-xml, --.@{hljs-css-prefix}-tex .@{hljs-css-prefix}-formula, --.@{hljs-css-prefix}-xml .@{hljs-css-prefix}-javascript, --.@{hljs-css-prefix}-xml .@{hljs-css-prefix}-vbscript, --.@{hljs-css-prefix}-xml .@{hljs-css-prefix}-css, --.@{hljs-css-prefix}-xml .@{hljs-css-prefix}-cdata { -- opacity: 0.5; -+.@{hljs-css-prefix}-type { -+ color: #66D8EE; - } -+ -+// .@{hljs-css-prefix}-string, -+// .@{hljs-css-prefix}-preprocessor { -+// color: #75715E; -+// } -+ -+// .@{hljs-css-prefix}-title, -+// .@{hljs-css-prefix}-constant, -+// .@{hljs-css-prefix}-parent, -+// .@{hljs-css-prefix}-tag .@{hljs-css-prefix}-value, -+// .@{hljs-css-prefix}-rules .@{hljs-css-prefix}-value, -+// .@{hljs-css-prefix}-rules .@{hljs-css-prefix}-value .@{hljs-css-prefix}-number, -+// .@{hljs-css-prefix}-ruby .@{hljs-css-prefix}-symbol, -+// .@{hljs-css-prefix}-ruby .@{hljs-css-prefix}-symbol .@{hljs-css-prefix}-string, -+// .@{hljs-css-prefix}-aggregate, -+// .@{hljs-css-prefix}-template_tag, -+// .@{hljs-css-prefix}-django .@{hljs-css-prefix}-variable, -+// .@{hljs-css-prefix}-smalltalk .@{hljs-css-prefix}-class, -+// .@{hljs-css-prefix}-addition, -+// .@{hljs-css-prefix}-flow, -+// .@{hljs-css-prefix}-stream, -+// .@{hljs-css-prefix}-bash .@{hljs-css-prefix}-variable, -+// .@{hljs-css-prefix}-apache .@{hljs-css-prefix}-tag, -+// .@{hljs-css-prefix}-apache .@{hljs-css-prefix}-cbracket, -+// .@{hljs-css-prefix}-tex .@{hljs-css-prefix}-command, -+// .@{hljs-css-prefix}-tex .@{hljs-css-prefix}-special, -+// .@{hljs-css-prefix}-erlang_repl .@{hljs-css-prefix}-function_or_atom, -+// .@{hljs-css-prefix}-markdown .@{hljs-css-prefix}-header { -+// color: @syntax-string; -+// } -+ -+// .@{hljs-css-prefix}-comment, -+// .@{hljs-css-prefix}-annotation, -+// .@{hljs-css-prefix}-template_comment, -+// .@{hljs-css-prefix}-diff .@{hljs-css-prefix}-header, -+// .@{hljs-css-prefix}-chunk, -+// .@{hljs-css-prefix}-markdown .@{hljs-css-prefix}-blockquote { -+// color: @syntax-comment; -+// } -+ -+// .@{hljs-css-prefix}-number, -+// .@{hljs-css-prefix}-date, -+// .@{hljs-css-prefix}-regexp, -+// .@{hljs-css-prefix}-literal, -+// .@{hljs-css-prefix}-smalltalk .@{hljs-css-prefix}-symbol, -+// .@{hljs-css-prefix}-smalltalk .@{hljs-css-prefix}-char, -+// .@{hljs-css-prefix}-go .@{hljs-css-prefix}-constant, -+// .@{hljs-css-prefix}-change, -+// .@{hljs-css-prefix}-markdown .@{hljs-css-prefix}-bullet, -+// .@{hljs-css-prefix}-markdown .@{hljs-css-prefix}-link_url { -+// color: @syntax-number; -+// } -+ -+// .@{hljs-css-prefix}-label, -+// .@{hljs-css-prefix}-javadoc, -+// .@{hljs-css-prefix}-ruby .@{hljs-css-prefix}-string, -+// .@{hljs-css-prefix}-decorator, -+// .@{hljs-css-prefix}-filter .@{hljs-css-prefix}-argument, -+// .@{hljs-css-prefix}-localvars, -+// .@{hljs-css-prefix}-array, -+// .@{hljs-css-prefix}-attr_selector, -+// .@{hljs-css-prefix}-important, -+// .@{hljs-css-prefix}-pseudo, -+// .@{hljs-css-prefix}-pi, -+// .@{hljs-css-prefix}-doctype, -+// .@{hljs-css-prefix}-deletion, -+// .@{hljs-css-prefix}-envvar, -+// .@{hljs-css-prefix}-shebang, -+// .@{hljs-css-prefix}-apache .@{hljs-css-prefix}-sqbracket, -+// .@{hljs-css-prefix}-nginx .@{hljs-css-prefix}-built_in, -+// .@{hljs-css-prefix}-tex .@{hljs-css-prefix}-formula, -+// .@{hljs-css-prefix}-erlang_repl .@{hljs-css-prefix}-reserved, -+// .@{hljs-css-prefix}-prompt, -+// .@{hljs-css-prefix}-markdown .@{hljs-css-prefix}-link_label, -+// .@{hljs-css-prefix}-vhdl .@{hljs-css-prefix}-attribute, -+// .@{hljs-css-prefix}-clojure .@{hljs-css-prefix}-attribute, -+// .@{hljs-css-prefix}-coffeescript .@{hljs-css-prefix}-property { -+// color: @syntax-label; -+// } -+ -+// .@{hljs-css-prefix}-keyword, -+// .@{hljs-css-prefix}-id, -+// .@{hljs-css-prefix}-phpdoc, -+// .@{hljs-css-prefix}-title, -+// .@{hljs-css-prefix}-built_in, -+// .@{hljs-css-prefix}-aggregate, -+// .@{hljs-css-prefix}-css .@{hljs-css-prefix}-tag, -+// .@{hljs-css-prefix}-javadoctag, -+// .@{hljs-css-prefix}-phpdoc, -+// .@{hljs-css-prefix}-yardoctag, -+// .@{hljs-css-prefix}-smalltalk .@{hljs-css-prefix}-class, -+// .@{hljs-css-prefix}-winutils, -+// .@{hljs-css-prefix}-bash .@{hljs-css-prefix}-variable, -+// .@{hljs-css-prefix}-apache .@{hljs-css-prefix}-tag, -+// .@{hljs-css-prefix}-go .@{hljs-css-prefix}-typename, -+// .@{hljs-css-prefix}-tex .@{hljs-css-prefix}-command, -+// .@{hljs-css-prefix}-markdown .@{hljs-css-prefix}-strong, -+// .@{hljs-css-prefix}-request, -+// .@{hljs-css-prefix}-status { -+// font-weight: bold; -+// color: #F92672; -+// } -+ -+// .@{hljs-css-prefix}-markdown .@{hljs-css-prefix}-emphasis { -+// font-style: italic; -+// } -+ -+// .@{hljs-css-prefix}-nginx .@{hljs-css-prefix}-built_in { -+// font-weight: normal; -+// } -+ -+// .@{hljs-css-prefix}-coffeescript .@{hljs-css-prefix}-javascript, -+// .@{hljs-css-prefix}-javascript .@{hljs-css-prefix}-xml, -+// .@{hljs-css-prefix}-tex .@{hljs-css-prefix}-formula, -+// .@{hljs-css-prefix}-xml .@{hljs-css-prefix}-javascript, -+// .@{hljs-css-prefix}-xml .@{hljs-css-prefix}-vbscript, -+// .@{hljs-css-prefix}-xml .@{hljs-css-prefix}-css, -+// .@{hljs-css-prefix}-xml .@{hljs-css-prefix}-cdata { -+// opacity: 0.5; -+// } -diff --git a/themes/daux/less/structure.less b/themes/daux/less/structure.less -index 5fce019..185988b 100644 ---- a/themes/daux/less/structure.less -+++ b/themes/daux/less/structure.less -@@ -159,7 +159,9 @@ Homepage - - &:hover { - color: @light; -- text-decoration: underline; -+ border-bottom-style: solid; -+ border-bottom-color: @light; -+ text-decoration: none; - } - } - } -@@ -184,6 +186,10 @@ html, body { - color: @text; - } - -+.content { -+ background: #F0F0F0; -+} -+ - .columns { - .left-column { - background-color:@sidebar-background; -@@ -223,7 +229,7 @@ html, body { - - body { - //Needed only for floating code blocks -- background-color:@light; -+ background-color: white; - } - - .navbar-static-top { -@@ -257,15 +263,22 @@ html, body { - - .columns { - height:100%; -- padding-top:@navbar-height; -+ padding-top:0px; - - .left-column { -+ max-width: 400px; -+ - border-right:1px solid @lines; - overflow-x:hidden; - } - - .right-column { - .content-page { -+ margin: auto; -+ position: relative; -+ -+ max-width: 800px; -+ - padding:20px; - min-height:100%; - } -@@ -273,6 +286,14 @@ html, body { - } - } - -+@media screen and (min-width: 1200px) { -+ .columns { -+ .right-column { -+ width: ~"calc(100% - 400px)"; -+ } -+ } -+} -+ - //CSS For Fluid Tables - @media only screen and (max-width: 800px) { - -diff --git a/themes/daux/less/theme-blue.less b/themes/daux/less/theme-blue.less -index 6044fb9..006524f 100644 ---- a/themes/daux/less/theme-blue.less -+++ b/themes/daux/less/theme-blue.less -@@ -1,10 +1,10 @@ - - //Daux.io Blue -- @sidebar-background: #f7f7f7; -- @sidebar-hover: #c5c5cb; -- @lines: #e7e7e9; -- @dark: #3f4657; -- @light: #82becd; -+ @sidebar-background: #343131; -+ @sidebar-hover: #4E4A4A; -+ @lines: #606060; -+ @dark: #B3B3B3; -+ @light: #A41E22; - @text: #2d2d2d; - @syntax-string: #022e99; - @syntax-comment: #84989b; -diff --git a/themes/daux_singlepage/css/main.min.css b/themes/daux_singlepage/css/main.min.css -index 457a809..20a1f39 100755 ---- a/themes/daux_singlepage/css/main.min.css -+++ b/themes/daux_singlepage/css/main.min.css -@@ -1 +1 @@ --/*! normalize.css v2.1.0 | MIT License | git.io/normalize */article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,video{display:inline-block}audio:not([controls]){display:none;height:0}[hidden]{display:none}html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:focus{outline:thin dotted}a:active,a:hover{outline:0}h1{font-size:2em;margin:.67em 0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}hr{box-sizing:content-box;height:0}mark{background:#ff0;color:#000}code,kbd,pre,samp{font-family:monospace;font-size:1em}pre{white-space:pre-wrap}q{quotes:"\201C" "\201D" "\2018" "\2019"}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:0}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}button,input{line-height:normal}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=search]{-webkit-appearance:textfield;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top}table{border-collapse:collapse;border-spacing:0}*,:after,:before{box-sizing:border-box}html{font-size:62.5%;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:Merriweather,EB Garamond,Georgia,serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}button,input,select[multiple],textarea{background-image:none}a{color:#428bca;text-decoration:none}a:focus,a:hover{color:#2a6496;text-decoration:underline}a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}img{vertical-align:middle}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);border:0}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16.1px;font-weight:200;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}small{font-size:85%}cite{font-style:normal}.text-muted{color:#999}.text-primary{color:#428bca}.text-warning{color:#c09853}.text-danger{color:#b94a48}.text-success{color:#468847}.text-info{color:#3a87ad}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}h1,h2,h3,h4,h5,h6{font-family:Merriweather,EB Garamond,Georgia,serif;font-weight:500;line-height:1.1}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:400;line-height:1;color:#999}h1,h2,h3{margin-top:20px}h1,h2,h3,h4,h5,h6{margin-bottom:10px}h4,h5,h6{margin-top:10px}h1{font-size:36px}h2{font-size:30px}h3{font-size:24px}h4{font-size:18px}h5{font-size:14px}h6{font-size:12px}h1 small{font-size:24px}h2 small{font-size:18px}h3 small,h4 small{font-size:14px}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-inline,.list-unstyled{padding-left:0;list-style:none}.list-inline>li{display:inline-block;padding-left:5px;padding-right:5px}dl{margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}.dl-horizontal dd:after,.dl-horizontal dd:before{content:" ";display:table}.dl-horizontal dd:after{clear:both}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #999}abbr.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;border-left:5px solid #eee}blockquote p{font-size:17.5px;font-weight:300;line-height:1.25}blockquote p:last-child{margin-bottom:0}blockquote small{display:block;line-height:1.42857143;color:#999}blockquote small:before{content:'\2014 \00A0'}blockquote.pull-right{padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0}blockquote.pull-right p,blockquote.pull-right small{text-align:right}blockquote.pull-right small:before{content:''}blockquote.pull-right small:after{content:'\00A0 \2014'}blockquote:after,blockquote:before,q:after,q:before{content:""}address{display:block;margin-bottom:20px;font-style:normal;line-height:1.42857143}@font-face{font-family:EB Garamond;font-style:normal;font-weight:400;src:local('EB Garamond 12 Regular'),url(//brick.a.ssl.fastly.net/fonts/ebgaramond/400.woff) format('woff')}@font-face{font-family:EB Garamond;font-style:italic;font-weight:400i;src:local('EB Garamond 12 Italic'),url(//brick.a.ssl.fastly.net/fonts/ebgaramond/400i.woff) format('woff')}@font-face{font-family:Merriweather;font-style:normal;font-weight:250;src:local('Merriweather Light'),url(//brick.a.ssl.fastly.net/fonts/merriweather/250.woff) format('woff')}@font-face{font-family:Merriweather;font-style:italic;font-weight:250i;src:local('Merriweather Light Italic'),url(//brick.a.ssl.fastly.net/fonts/merriweather/250i.woff) format('woff')}@font-face{font-family:Merriweather;font-style:normal;font-weight:400;src:local('Merriweather'),url(//brick.a.ssl.fastly.net/fonts/merriweather/400.woff) format('woff')}@font-face{font-family:Merriweather;font-style:italic;font-weight:400i;src:local('Merriweather Italic'),url(//brick.a.ssl.fastly.net/fonts/merriweather/400i.woff) format('woff')}@font-face{font-family:Merriweather;font-style:normal;font-weight:600;src:local(''),url(//brick.a.ssl.fastly.net/fonts/merriweather/600.woff) format('woff')}@font-face{font-family:Merriweather;font-style:italic;font-weight:600i;src:local(''),url(//brick.a.ssl.fastly.net/fonts/merriweather/600i.woff) format('woff')}@font-face{font-family:Merriweather;font-style:normal;font-weight:700;src:local('Merriweather Bold'),url(//brick.a.ssl.fastly.net/fonts/merriweather/700.woff) format('woff')}@font-face{font-family:Merriweather;font-style:italic;font-weight:700i;src:local('Merriweather Bold Italic'),url(//brick.a.ssl.fastly.net/fonts/merriweather/700i.woff) format('woff')}@font-face{font-family:Merriweather;font-style:normal;font-weight:900;src:local('Merriweather Heavy'),url(//brick.a.ssl.fastly.net/fonts/merriweather/900.woff) format('woff')}@font-face{font-family:Merriweather;font-style:italic;font-weight:900i;src:local('Merriweather Heavy Italic'),url(//brick.a.ssl.fastly.net/fonts/merriweather/900i.woff) format('woff')}@font-face{font-family:Anonymous Pro;font-style:normal;font-weight:400;src:local('Anonymous Pro'),url(//brick.a.ssl.fastly.net/fonts/anonymouspro/400.woff) format('woff')}@font-face{font-family:Anonymous Pro;font-style:italic;font-weight:400i;src:local('Anonymous Pro Italic'),url(//brick.a.ssl.fastly.net/fonts/anonymouspro/400i.woff) format('woff')}@font-face{font-family:Anonymous Pro;font-style:normal;font-weight:700;src:local('Anonymous Pro Bold'),url(//brick.a.ssl.fastly.net/fonts/anonymouspro/700.woff) format('woff')}@font-face{font-family:Anonymous Pro;font-style:italic;font-weight:700i;src:local('Anonymous Pro Bold Italic'),url(//brick.a.ssl.fastly.net/fonts/anonymouspro/700i.woff) format('woff')}@font-face{font-family:Open Sans;font-style:normal;font-weight:300;src:local('Open Sans Light'),url(//brick.a.ssl.fastly.net/fonts/opensans/300.woff) format('woff')}@font-face{font-family:Open Sans;font-style:italic;font-weight:300i;src:local('Open Sans Light Italic'),url(//brick.a.ssl.fastly.net/fonts/opensans/300i.woff) format('woff')}@font-face{font-family:Open Sans;font-style:normal;font-weight:400;src:local('Open Sans Regular'),url(//brick.a.ssl.fastly.net/fonts/opensans/400.woff) format('woff')}@font-face{font-family:Open Sans;font-style:italic;font-weight:400i;src:local('Open Sans Italic'),url(//brick.a.ssl.fastly.net/fonts/opensans/400i.woff) format('woff')}@font-face{font-family:Open Sans;font-style:normal;font-weight:600;src:local('Open Sans Semibold'),url(//brick.a.ssl.fastly.net/fonts/opensans/600.woff) format('woff')}@font-face{font-family:Open Sans;font-style:italic;font-weight:600i;src:local('Open Sans Semibold Italic'),url(//brick.a.ssl.fastly.net/fonts/opensans/600i.woff) format('woff')}@font-face{font-family:Open Sans;font-style:normal;font-weight:700;src:local('Open Sans Bold'),url(//brick.a.ssl.fastly.net/fonts/opensans/700.woff) format('woff')}@font-face{font-family:Open Sans;font-style:italic;font-weight:700i;src:local('Open Sans Bold Italic'),url(//brick.a.ssl.fastly.net/fonts/opensans/700i.woff) format('woff')}.hljs-comment{color:#3a5c78}.css .hljs-class,.css .hljs-id,.css .hljs-pseudo,.hljs-attribute,.hljs-regexp,.hljs-tag,.hljs-variable,.html .hljs-doctype,.ruby .hljs-constant,.xml .hljs-doctype,.xml .hljs-pi,.xml .hljs-tag .hljs-title{color:#c82829}.hljs-built_in,.hljs-constant,.hljs-function .hljs-title,.hljs-literal,.hljs-number,.hljs-pragma,.hljs-preprocessor{color:#fd3}.css .hljs-rules .hljs-attribute,.ruby .hljs-class .hljs-title{color:#eab700}.hljs-header,.hljs-inheritance,.hljs-string,.hljs-value,.ruby .hljs-symbol,.xml .hljs-cdata{color:#f66}.css .hljs-hexcolor{color:#3e999f}.coffeescript .hljs-title,.hljs-function .keyword,.javascript .hljs-title,.perl .hljs-sub,.python .hljs-decorator,.python .hljs-title,.ruby .hljs-function .hljs-title,.ruby .hljs-title .hljs-keyword{color:#52a0e0}.hljs-keyword,.javascript .hljs-function{color:#6abafb}.hljs{display:block;background:#fff;color:#4d4d4c;padding:.5em;font-family:Anonymous Pro,Inconsolata,Monaco,monospace}.coffeescript .javascript,.javascript .xml,.tex .hljs-formula,.xml .css,.xml .hljs-cdata,.xml .javascript,.xml .vbscript{opacity:.5}section.content{padding:25px;padding-top:15px;background-color:#fff}section.content>:first-child{margin-top:0!important}section.content>:last-child{margin-bottom:0!important}section.content a{color:#4183c4}section.content a.absent{color:#c00}section.content a.anchor{display:block;padding-left:30px;margin-left:-30px;cursor:pointer;position:absolute;top:0;left:0;bottom:0}section.content h1,section.content h2,section.content h3,section.content h4,section.content h5,section.content h6{line-height:1.7;margin:20px 0 10px;padding:0;font-weight:700;-webkit-font-smoothing:antialiased;cursor:text;position:relative}section.content h1 code,section.content h1 tt,section.content h2 code,section.content h2 tt,section.content h3 code,section.content h3 tt,section.content h4 code,section.content h4 tt,section.content h5 code,section.content h5 tt,section.content h6 code,section.content h6 tt{font-size:inherit}section.content h1{font-size:28px;color:#000}section.content h2{font-size:24px;border-bottom:1px solid #eee;color:#000}section.content h3{font-size:18px}section.content h4{font-size:16px}section.content h5{font-size:14px}section.content h6{color:#777;font-size:14px}section.content blockquote,section.content dl,section.content ol,section.content p,section.content pre,section.content table,section.content ul{margin:15px 0}section.content a:first-child h1,section.content a:first-child h2,section.content a:first-child h3,section.content a:first-child h4,section.content a:first-child h5,section.content a:first-child h6,section.content body>h1:first-child,section.content body>h1:first-child+h2,section.content body>h2:first-child,section.content body>h3:first-child,section.content body>h4:first-child,section.content body>h5:first-child,section.content body>h6:first-child{margin-top:0;padding-top:0}section.content h1 p,section.content h2 p,section.content h3 p,section.content h4 p,section.content h5 p,section.content h6 p{margin-top:0}section.content li p.first{display:inline-block}section.content ol,section.content ul{padding-left:30px}section.content ol :first-child,section.content ul :first-child{margin-top:0}section.content ol :last-child,section.content ul :last-child{margin-bottom:0}section.content ul p,section.content ul ul{margin:0}section.content dl{padding:0}section.content dl dt{font-size:14px;font-weight:700;font-style:italic;padding:0;margin:15px 0 5px}section.content dl dt:first-child{padding:0}section.content dl dt>:first-child{margin-top:0}section.content dl dt>:last-child{margin-bottom:0}section.content dl dd{margin:0 0 15px;padding:0 15px}section.content dl dd>:first-child{margin-top:0}section.content dl dd>:last-child{margin-bottom:0}section.content blockquote{border-left:4px solid #ddd;padding:0 15px;color:#777}section.content blockquote p{font-size:inherit}section.content blockquote>:first-child{margin-top:0}section.content blockquote>:last-child{margin-bottom:0}section.content table{width:100%;padding:0}section.content table tr{border-top:1px solid #ccc;background-color:#fff;margin:0;padding:0}section.content table tr:nth-child(2n){background-color:#f8f8f8}section.content table tr th{font-weight:700}section.content table tr td,section.content table tr th{border:1px solid #ccc;margin:0;padding:6px 13px}section.content table tr td :first-child,section.content table tr th :first-child{margin-top:0}section.content table tr td :last-child,section.content table tr th :last-child{margin-bottom:0}section.content img{max-width:100%;display:block;margin:0 auto}section.content span.frame{display:block;overflow:hidden}section.content span.frame>span{border:1px solid #ddd;display:block;float:left;overflow:hidden;margin:13px 0 0;padding:7px;width:auto}section.content span.frame span img{display:block;float:left}section.content span.frame span span{clear:both;color:#333;display:block;padding:5px 0 0}section.content span.align-center{display:block;overflow:hidden;clear:both}section.content span.align-center>span{display:block;overflow:hidden;margin:13px auto 0;text-align:center}section.content span.align-center span img{margin:0 auto;text-align:center}section.content span.align-right{display:block;overflow:hidden;clear:both}section.content span.align-right>span{display:block;overflow:hidden;margin:13px 0 0;text-align:right}section.content span.align-right span img{margin:0;text-align:right}section.content span.float-left{display:block;margin-right:13px;overflow:hidden;float:left}section.content span.float-left span{margin:13px 0 0}section.content span.float-right{display:block;margin-left:13px;overflow:hidden;float:right}section.content span.float-right>span{display:block;overflow:hidden;margin:13px auto 0;text-align:right}section.content code,section.content tt{margin:0 2px;padding:0 5px;white-space:nowrap;border:1px solid #eaeaea;background-color:#f8f8f8;border-radius:3px}section.content pre code{margin:0;padding:0;white-space:pre;border:none;background:transparent}section.content .highlight pre,section.content pre{color:#b8d0e0;background-color:#121b21;border:1px solid #121b21;font-size:16px;line-height:1.5em;overflow:auto;padding:20px;margin:0 -20px;border-radius:3px}section.content pre code,section.content pre tt{background-color:transparent;border:none}*{-webkit-overflow-scrolling:touch;-webkit-tap-highlight-color:transparent;-webkit-text-size-adjust:none;-webkit-touch-callout:none;-webkit-font-smoothing:antialiased}body,html{height:100%}body{text-rendering:optimizeLegibility;font-smoothing:antialiased;font-family:Merriweather,EB Garamond,Georgia,serif}img{max-width:100%!important}.page-break{display:none}@media screen{body{margin:1em}}@media print{*{text-shadow:none!important;color:#000!important;background:transparent!important;box-shadow:none!important}.page-break{display:block;page-break-before:always}h1,h2{page-break-after:avoid;page-break-before:auto}blockquote,pre{border:1px solid #999}blockquote,img,pre{page-break-inside:avoid}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}} -\ No newline at end of file -+/*! normalize.css v2.1.0 | MIT License | git.io/normalize */article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,video{display:inline-block}audio:not([controls]){display:none;height:0}[hidden]{display:none}html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:focus{outline:thin dotted}a:active,a:hover{outline:0}h1{font-size:2em;margin:.67em 0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}hr{box-sizing:content-box;height:0}mark{background:#ff0;color:#000}code,kbd,pre,samp{font-family:monospace,serif;font-size:1em}pre{white-space:pre-wrap}q{quotes:"\201C" "\201D" "\2018" "\2019"}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:0}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}button,input{line-height:normal}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=search]{-webkit-appearance:textfield;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top}table{border-collapse:collapse;border-spacing:0}*,:after,:before{box-sizing:border-box}html{font-size:62.5%;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:Merriweather,EB Garamond,Georgia,serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}button,input,select[multiple],textarea{background-image:none}a{color:#428bca;text-decoration:none}a:focus,a:hover{color:#2a6496;text-decoration:underline}a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}img{vertical-align:middle}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);border:0}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16.1px;font-weight:200;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}small{font-size:85%}cite{font-style:normal}.text-muted{color:#999}.text-primary{color:#428bca}.text-warning{color:#c09853}.text-danger{color:#b94a48}.text-success{color:#468847}.text-info{color:#3a87ad}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}h1,h2,h3,h4,h5,h6{font-family:Merriweather,EB Garamond,Georgia,serif;font-weight:500;line-height:1.1}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:400;line-height:1;color:#999}h1,h2,h3{margin-top:20px}h1,h2,h3,h4,h5,h6{margin-bottom:10px}h4,h5,h6{margin-top:10px}h1{font-size:36px}h2{font-size:30px}h3{font-size:24px}h4{font-size:18px}h5{font-size:14px}h6{font-size:12px}h1 small{font-size:24px}h2 small{font-size:18px}h3 small,h4 small{font-size:14px}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-inline,.list-unstyled{padding-left:0;list-style:none}.list-inline>li{display:inline-block;padding-left:5px;padding-right:5px}dl{margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}.dl-horizontal dd:after,.dl-horizontal dd:before{content:" ";display:table}.dl-horizontal dd:after{clear:both}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #999}abbr.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;border-left:5px solid #eee}blockquote p{font-size:17.5px;font-weight:300;line-height:1.25}blockquote p:last-child{margin-bottom:0}blockquote small{display:block;line-height:1.42857143;color:#999}blockquote small:before{content:'\2014 \00A0'}blockquote.pull-right{padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0}blockquote.pull-right p,blockquote.pull-right small{text-align:right}blockquote.pull-right small:before{content:''}blockquote.pull-right small:after{content:'\00A0 \2014'}blockquote:after,blockquote:before,q:after,q:before{content:""}address{display:block;margin-bottom:20px;font-style:normal;line-height:1.42857143}@font-face{font-family:EB Garamond;font-style:normal;font-weight:400;src:local('EB Garamond 12 Regular'),url(//brick.a.ssl.fastly.net/fonts/ebgaramond/400.woff) format('woff')}@font-face{font-family:EB Garamond;font-style:italic;font-weight:400i;src:local('EB Garamond 12 Italic'),url(//brick.a.ssl.fastly.net/fonts/ebgaramond/400i.woff) format('woff')}@font-face{font-family:Merriweather;font-style:normal;font-weight:250;src:local('Merriweather Light'),url(//brick.a.ssl.fastly.net/fonts/merriweather/250.woff) format('woff')}@font-face{font-family:Merriweather;font-style:italic;font-weight:250i;src:local('Merriweather Light Italic'),url(//brick.a.ssl.fastly.net/fonts/merriweather/250i.woff) format('woff')}@font-face{font-family:Merriweather;font-style:normal;font-weight:400;src:local('Merriweather'),url(//brick.a.ssl.fastly.net/fonts/merriweather/400.woff) format('woff')}@font-face{font-family:Merriweather;font-style:italic;font-weight:400i;src:local('Merriweather Italic'),url(//brick.a.ssl.fastly.net/fonts/merriweather/400i.woff) format('woff')}@font-face{font-family:Merriweather;font-style:normal;font-weight:600;src:local(''),url(//brick.a.ssl.fastly.net/fonts/merriweather/600.woff) format('woff')}@font-face{font-family:Merriweather;font-style:italic;font-weight:600i;src:local(''),url(//brick.a.ssl.fastly.net/fonts/merriweather/600i.woff) format('woff')}@font-face{font-family:Merriweather;font-style:normal;font-weight:700;src:local('Merriweather Bold'),url(//brick.a.ssl.fastly.net/fonts/merriweather/700.woff) format('woff')}@font-face{font-family:Merriweather;font-style:italic;font-weight:700i;src:local('Merriweather Bold Italic'),url(//brick.a.ssl.fastly.net/fonts/merriweather/700i.woff) format('woff')}@font-face{font-family:Merriweather;font-style:normal;font-weight:900;src:local('Merriweather Heavy'),url(//brick.a.ssl.fastly.net/fonts/merriweather/900.woff) format('woff')}@font-face{font-family:Merriweather;font-style:italic;font-weight:900i;src:local('Merriweather Heavy Italic'),url(//brick.a.ssl.fastly.net/fonts/merriweather/900i.woff) format('woff')}@font-face{font-family:Anonymous Pro;font-style:normal;font-weight:400;src:local('Anonymous Pro'),url(//brick.a.ssl.fastly.net/fonts/anonymouspro/400.woff) format('woff')}@font-face{font-family:Anonymous Pro;font-style:italic;font-weight:400i;src:local('Anonymous Pro Italic'),url(//brick.a.ssl.fastly.net/fonts/anonymouspro/400i.woff) format('woff')}@font-face{font-family:Anonymous Pro;font-style:normal;font-weight:700;src:local('Anonymous Pro Bold'),url(//brick.a.ssl.fastly.net/fonts/anonymouspro/700.woff) format('woff')}@font-face{font-family:Anonymous Pro;font-style:italic;font-weight:700i;src:local('Anonymous Pro Bold Italic'),url(//brick.a.ssl.fastly.net/fonts/anonymouspro/700i.woff) format('woff')}@font-face{font-family:Open Sans;font-style:normal;font-weight:300;src:local('Open Sans Light'),url(//brick.a.ssl.fastly.net/fonts/opensans/300.woff) format('woff')}@font-face{font-family:Open Sans;font-style:italic;font-weight:300i;src:local('Open Sans Light Italic'),url(//brick.a.ssl.fastly.net/fonts/opensans/300i.woff) format('woff')}@font-face{font-family:Open Sans;font-style:normal;font-weight:400;src:local('Open Sans Regular'),url(//brick.a.ssl.fastly.net/fonts/opensans/400.woff) format('woff')}@font-face{font-family:Open Sans;font-style:italic;font-weight:400i;src:local('Open Sans Italic'),url(//brick.a.ssl.fastly.net/fonts/opensans/400i.woff) format('woff')}@font-face{font-family:Open Sans;font-style:normal;font-weight:600;src:local('Open Sans Semibold'),url(//brick.a.ssl.fastly.net/fonts/opensans/600.woff) format('woff')}@font-face{font-family:Open Sans;font-style:italic;font-weight:600i;src:local('Open Sans Semibold Italic'),url(//brick.a.ssl.fastly.net/fonts/opensans/600i.woff) format('woff')}@font-face{font-family:Open Sans;font-style:normal;font-weight:700;src:local('Open Sans Bold'),url(//brick.a.ssl.fastly.net/fonts/opensans/700.woff) format('woff')}@font-face{font-family:Open Sans;font-style:italic;font-weight:700i;src:local('Open Sans Bold Italic'),url(//brick.a.ssl.fastly.net/fonts/opensans/700i.woff) format('woff')}.hljs-comment{color:#3a5c78}.css .hljs-class,.css .hljs-id,.css .hljs-pseudo,.hljs-attribute,.hljs-regexp,.hljs-tag,.hljs-variable,.html .hljs-doctype,.ruby .hljs-constant,.xml .hljs-doctype,.xml .hljs-pi,.xml .hljs-tag .hljs-title{color:#c82829}.hljs-built_in,.hljs-constant,.hljs-function .hljs-title,.hljs-literal,.hljs-number,.hljs-pragma,.hljs-preprocessor{color:#fd3}.css .hljs-rules .hljs-attribute,.ruby .hljs-class .hljs-title{color:#eab700}.hljs-header,.hljs-inheritance,.hljs-string,.hljs-value,.ruby .hljs-symbol,.xml .hljs-cdata{color:#f66}.css .hljs-hexcolor{color:#3e999f}.coffeescript .hljs-title,.hljs-function .keyword,.javascript .hljs-title,.perl .hljs-sub,.python .hljs-decorator,.python .hljs-title,.ruby .hljs-function .hljs-title,.ruby .hljs-title .hljs-keyword{color:#52a0e0}.hljs-keyword,.javascript .hljs-function{color:#6abafb}.hljs{display:block;background:#fff;color:#4d4d4c;padding:.5em;font-family:Anonymous Pro,Inconsolata,Monaco,monospace}.coffeescript .javascript,.javascript .xml,.tex .hljs-formula,.xml .css,.xml .hljs-cdata,.xml .javascript,.xml .vbscript{opacity:.5}section.content{padding:25px;padding-top:15px;background-color:#fff}section.content>:first-child{margin-top:0!important}section.content>:last-child{margin-bottom:0!important}section.content a{color:#4183c4}section.content a.absent{color:#c00}section.content a.anchor{display:block;padding-left:30px;margin-left:-30px;cursor:pointer;position:absolute;top:0;left:0;bottom:0}section.content h1,section.content h2,section.content h3,section.content h4,section.content h5,section.content h6{line-height:1.7;margin:20px 0 10px;padding:0;font-weight:700;-webkit-font-smoothing:antialiased;cursor:text;position:relative}section.content h1 code,section.content h1 tt,section.content h2 code,section.content h2 tt,section.content h3 code,section.content h3 tt,section.content h4 code,section.content h4 tt,section.content h5 code,section.content h5 tt,section.content h6 code,section.content h6 tt{font-size:inherit}section.content h1{font-size:28px;color:#000}section.content h2{font-size:24px;border-bottom:1px solid #eee;color:#000}section.content h3{font-size:18px}section.content h4{font-size:16px}section.content h5{font-size:14px}section.content h6{color:#777;font-size:14px}section.content blockquote,section.content dl,section.content ol,section.content p,section.content pre,section.content table,section.content ul{margin:15px 0}section.content a:first-child h1,section.content a:first-child h2,section.content a:first-child h3,section.content a:first-child h4,section.content a:first-child h5,section.content a:first-child h6,section.content body>h1:first-child,section.content body>h1:first-child+h2,section.content body>h2:first-child,section.content body>h3:first-child,section.content body>h4:first-child,section.content body>h5:first-child,section.content body>h6:first-child{margin-top:0;padding-top:0}section.content h1 p,section.content h2 p,section.content h3 p,section.content h4 p,section.content h5 p,section.content h6 p{margin-top:0}section.content li p.first{display:inline-block}section.content ol,section.content ul{padding-left:30px}section.content ol :first-child,section.content ul :first-child{margin-top:0}section.content ol :last-child,section.content ul :last-child{margin-bottom:0}section.content ul p,section.content ul ul{margin:0}section.content dl{padding:0}section.content dl dt{font-size:14px;font-weight:700;font-style:italic;padding:0;margin:15px 0 5px}section.content dl dt:first-child{padding:0}section.content dl dt>:first-child{margin-top:0}section.content dl dt>:last-child{margin-bottom:0}section.content dl dd{margin:0 0 15px;padding:0 15px}section.content dl dd>:first-child{margin-top:0}section.content dl dd>:last-child{margin-bottom:0}section.content blockquote{border-left:4px solid #ddd;padding:0 15px;color:#777}section.content blockquote p{font-size:inherit}section.content blockquote>:first-child{margin-top:0}section.content blockquote>:last-child{margin-bottom:0}section.content table{width:100%;padding:0}section.content table tr{border-top:1px solid #ccc;background-color:#fff;margin:0;padding:0}section.content table tr:nth-child(2n){background-color:#f8f8f8}section.content table tr th{font-weight:700}section.content table tr td,section.content table tr th{border:1px solid #ccc;margin:0;padding:6px 13px}section.content table tr td :first-child,section.content table tr th :first-child{margin-top:0}section.content table tr td :last-child,section.content table tr th :last-child{margin-bottom:0}section.content img{max-width:100%;display:block;margin:0 auto}section.content span.frame{display:block;overflow:hidden}section.content span.frame>span{border:1px solid #ddd;display:block;float:left;overflow:hidden;margin:13px 0 0;padding:7px;width:auto}section.content span.frame span img{display:block;float:left}section.content span.frame span span{clear:both;color:#333;display:block;padding:5px 0 0}section.content span.align-center{display:block;overflow:hidden;clear:both}section.content span.align-center>span{display:block;overflow:hidden;margin:13px auto 0;text-align:center}section.content span.align-center span img{margin:0 auto;text-align:center}section.content span.align-right{display:block;overflow:hidden;clear:both}section.content span.align-right>span{display:block;overflow:hidden;margin:13px 0 0;text-align:right}section.content span.align-right span img{margin:0;text-align:right}section.content span.float-left{display:block;margin-right:13px;overflow:hidden;float:left}section.content span.float-left span{margin:13px 0 0}section.content span.float-right{display:block;margin-left:13px;overflow:hidden;float:right}section.content span.float-right>span{display:block;overflow:hidden;margin:13px auto 0;text-align:right}section.content code,section.content tt{margin:0 2px;padding:0 5px;white-space:nowrap;border:1px solid #eaeaea;background-color:#f8f8f8;border-radius:3px}section.content pre code{margin:0;padding:0;white-space:pre;border:none;background:transparent}section.content .highlight pre,section.content pre{color:#b8d0e0;background-color:#121b21;border:1px solid #121b21;font-size:16px;line-height:1.5em;overflow:auto;padding:20px;margin:0 -20px;border-radius:3px}section.content pre code,section.content pre tt{background-color:transparent;border:none}*{-webkit-overflow-scrolling:touch;-webkit-tap-highlight-color:transparent;-webkit-text-size-adjust:none;-webkit-touch-callout:none;-webkit-font-smoothing:antialiased}body,html{height:100%}body{text-rendering:optimizeLegibility;font-smoothing:antialiased;font-family:Merriweather,EB Garamond,Georgia,serif}img{max-width:100%!important}.page-break{display:none}@media screen{body{margin:1em}}@media print{*{text-shadow:none!important;color:#000!important;background:transparent!important;box-shadow:none!important}.page-break{display:block;page-break-before:always}h1,h2{page-break-after:avoid;page-break-before:auto}blockquote,pre{border:1px solid #999}blockquote,img,pre{page-break-inside:avoid}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}} -\ No newline at end of file --- -2.5.0 - diff --git a/ebook/Vulkan Tutorial en.epub b/ebook/Vulkan Tutorial en.epub new file mode 100644 index 00000000..df2a5082 Binary files /dev/null and b/ebook/Vulkan Tutorial en.epub differ diff --git a/ebook/Vulkan Tutorial en.pdf b/ebook/Vulkan Tutorial en.pdf new file mode 100644 index 00000000..2b1cbdd3 Binary files /dev/null and b/ebook/Vulkan Tutorial en.pdf differ diff --git a/ebook/Vulkan Tutorial fr.epub b/ebook/Vulkan Tutorial fr.epub new file mode 100644 index 00000000..59c0e697 Binary files /dev/null and b/ebook/Vulkan Tutorial fr.epub differ diff --git a/ebook/Vulkan Tutorial fr.pdf b/ebook/Vulkan Tutorial fr.pdf new file mode 100644 index 00000000..7f76e94e Binary files /dev/null and b/ebook/Vulkan Tutorial fr.pdf differ diff --git a/ebook/cover.kra b/ebook/cover.kra new file mode 100644 index 00000000..4e684aeb Binary files /dev/null and b/ebook/cover.kra differ diff --git a/ebook/cover.png b/ebook/cover.png new file mode 100644 index 00000000..17b4f57b Binary files /dev/null and b/ebook/cover.png differ diff --git a/ebook/listings-setup.tex b/ebook/listings-setup.tex new file mode 100644 index 00000000..0236f1bf --- /dev/null +++ b/ebook/listings-setup.tex @@ -0,0 +1,25 @@ +% Contents of listings-setup.tex +\usepackage{xcolor} + +\lstset{ + basicstyle=\ttfamily, + numbers=left, + keywordstyle=\color[rgb]{0.13,0.29,0.53}\bfseries, + stringstyle=\color[rgb]{0.31,0.60,0.02}, + commentstyle=\color[rgb]{0.56,0.35,0.01}\itshape, + numberstyle=\footnotesize, + stepnumber=1, + numbersep=5pt, + backgroundcolor=\color[RGB]{248,248,248}, + showspaces=false, + showstringspaces=false, + showtabs=false, + tabsize=2, + captionpos=b, + breaklines=true, + breakatwhitespace=true, + breakautoindent=true, + escapeinside={\%*}{*)}, + linewidth=\textwidth, + basewidth=0.5em, +} \ No newline at end of file diff --git a/00_Introduction.md b/en/00_Introduction.md similarity index 77% rename from 00_Introduction.md rename to en/00_Introduction.md index d507ce21..377497b8 100644 --- a/00_Introduction.md +++ b/en/00_Introduction.md @@ -31,20 +31,34 @@ able to use Vulkan while exposing a much higher level API to you. With that out of the way, let's cover some prerequisites for following this tutorial: -* A graphics card and driver compatible with Vulkan ([NVIDIA](https://developer.nvidia.com/vulkan-driver), [AMD](http://www.amd.com/en-us/innovations/software-technologies/technologies-gaming/vulkan), [Intel](https://software.intel.com/en-us/blogs/2016/03/14/new-intel-vulkan-beta-1540204404-graphics-driver-for-windows-78110-1540)) +* A graphics card and driver compatible with Vulkan ([NVIDIA](https://developer.nvidia.com/vulkan-driver), [AMD](http://www.amd.com/en-us/innovations/software-technologies/technologies-gaming/vulkan), [Intel](https://software.intel.com/en-us/blogs/2016/03/14/new-intel-vulkan-beta-1540204404-graphics-driver-for-windows-78110-1540), [Apple Silicon (Or the Apple M1)](https://www.phoronix.com/scan.php?page=news_item&px=Apple-Silicon-Vulkan-MoltenVK)) * Experience with C++ (familiarity with RAII, initializer lists) -* A compiler compatible with C++11 (Visual Studio 2013+, GCC 4.8+) +* A compiler with decent support of C++17 features (Visual Studio 2017+, GCC 7+, Or Clang 5+) * Some existing experience with 3D computer graphics 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) -for a great introduction of computer graphics concepts. +the math behind perspective projection, for example. See [this online book](https://paroj.github.io/gltut/) +for a great introduction of computer graphics concepts. Some other great computer graphics resources are: + +* [Ray tracing in one weekend](https://github.com/RayTracing/raytracing.github.io) +* [Physically Based Rendering book](http://www.pbr-book.org/) +* Vulkan being used in a real engine in the open-source [Quake](https://github.com/Novum/vkQuake) and [DOOM 3](https://github.com/DustinHLand/vkDOOM3) You can use C instead of C++ if you want, but you will have to use a different 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. +lifetimes. There is also an [alternative version](https://github.com/bwasty/vulkan-tutorial-rs) of this tutorial available for Rust developers. + +To make it easier to follow along for developers using other programming languages, and to get some experience with the base API we'll be using the original C API to work with Vulkan. If you are using C++, however, you may prefer using the newer [Vulkan-Hpp](https://github.com/KhronosGroup/Vulkan-Hpp) bindings that abstract some of the dirty work and help prevent certain classes of errors. + +## 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%20en.epub) +* [PDF](https://raw.githubusercontent.com/Overv/VulkanTutorial/master/ebook/Vulkan%20Tutorial%20en.pdf) ## Tutorial structure @@ -91,6 +105,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 +119,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. -Ready to dive into the future of high performance graphics APIs? [Let's go!](!Overview) +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!](!en/Overview) diff --git a/01_Overview.md b/en/01_Overview.md similarity index 96% rename from 01_Overview.md rename to en/01_Overview.md index f118bcb1..306815b6 100644 --- a/01_Overview.md +++ b/en/01_Overview.md @@ -54,11 +54,11 @@ An instance is created by describing your application and any API extensions you will be using. After creating the instance, you can query for Vulkan supported hardware and select one or more `VkPhysicalDevice`s to use for operations. You can query for properties like VRAM size and device capabilities to select -desired devices, for example to prefer using dedicated graphics cards. +desired devices, for example to prefer using dedicated graphics cards. ### Step 2 - Logical device and queue families -After selecting the right hardware device to use, you need to create a VkDevice +After selecting the right hardware device to use, you need to create a VkDevice (logical device), where you describe more specifically which VkPhysicalDeviceFeatures you will be using, like multi viewport rendering and 64 bit floats. You also need to specify which queue families you would like to @@ -79,8 +79,8 @@ window to present rendered images to. Windows can be created with the native platform APIs or libraries like [GLFW](http://www.glfw.org/) and [SDL](https://www.libsdl.org/). We will be using GLFW in this tutorial, but more about that in the next chapter. -We need two more components to actually render to a window: a window surface -(VkSurfaceKHR) and a swap chain (VkSwapChainKHR). Note the `KHR` postfix, which +We need two more components to actually render to a window: a window surface +(VkSurfaceKHR) and a swap chain (VkSwapchainKHR). Note the `KHR` postfix, which means that these objects are part of a Vulkan extension. The Vulkan API itself is completely platform agnostic, which is why we need to use the standardized WSI (Window System Interface) extension to interact with the window manager. The @@ -100,6 +100,8 @@ finished images to the screen depends on the present mode. Common present modes are double buffering (vsync) and triple buffering. We'll look into these in the swap chain creation chapter. +Some platforms allow you to render directly to a display without interacting with any window manager through the `VK_KHR_display` and `VK_KHR_display_swapchain` extensions. These allow you to create a surface that represents the entire screen and could be used to implement your own window manager, for example. + ### Step 4 - Image views and framebuffers To draw to an image acquired from the swap chain, we have to wrap it into a @@ -129,7 +131,7 @@ driver also needs to know which render targets will be used in the pipeline, which we specify by referencing the render pass. One of the most distinctive features of Vulkan compared to existing APIs, is -that almost all configuration of the graphics pipeline needs to be in advance. +that almost all configuration of the graphics pipeline needs to be set in advance. That means that if you want to switch to a different shader or slightly change your vertex layout, then you need to entirely recreate the graphics pipeline. That means that you will have to create many VkPipeline objects in @@ -227,7 +229,7 @@ uses structs to provide parameters to functions. For example, object creation generally follows this pattern: ```c++ -VkXXXCreateInfo createInfo = {}; +VkXXXCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_XXX_CREATE_INFO; createInfo.pNext = nullptr; createInfo.foo = ...; @@ -274,4 +276,4 @@ are so extensive, it can actually be a lot easier to find out why your screen is black compared to OpenGL and Direct3D! There's only one more step before we'll start writing code and that's [setting -up the development environment](!Development_environment). \ No newline at end of file +up the development environment](!en/Development_environment). diff --git a/en/02_Development_environment.md b/en/02_Development_environment.md new file mode 100644 index 00000000..d651febc --- /dev/null +++ b/en/02_Development_environment.md @@ -0,0 +1,539 @@ +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 Windows, Linux and MacOS, but the +steps for installing them differ a bit, which is why they're described +separately here. + +## Windows + +If you're developing for Windows, then I will assume that you are using Visual +Studio to compile your code. For complete C++17 support, you need to use either +Visual Studio 2017 or 2019. The steps outlined below were written for VS 2017. + +### 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) + +Proceed through the installation and pay attention to the install location of +the SDK. The first thing we'll do is verify that your graphics card and driver +properly support Vulkan. Go to the directory where you installed the SDK, open +the `Bin` directory and run the `vkcube.exe` demo. You should see the following: + +![](/images/cube_demo.png) + +If you receive an error message then ensure that your drivers are up-to-date, +include the Vulkan runtime and that your graphics card is supported. See the +[introduction chapter](!en/Introduction) for links to drivers from the major +vendors. + +There is another program in this directory that will be useful for development. The `glslangValidator.exe` and `glslc.exe` programs will be used to compile shaders from the +human-readable [GLSL](https://en.wikipedia.org/wiki/OpenGL_Shading_Language) to +bytecode. We'll cover this in depth in the [shader modules](!en/Drawing_a_triangle/Graphics_pipeline_basics/Shader_modules) +chapter. The `Bin` directory also contains the binaries of the Vulkan loader +and the validation layers, while the `Lib` directory contains the libraries. + +Lastly, there's the `Include` directory that contains the Vulkan headers. Feel free to explore the other files, but we won't need them for this tutorial. + +### GLFW + +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 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. + +You can find the latest release of GLFW on the [official website](http://www.glfw.org/download.html). +In this tutorial we'll be using the 64-bit binaries, but you can of course also +choose to build in 32 bit mode. In that case make sure to link with the Vulkan +SDK binaries in the `Lib32` directory instead of `Lib`. After downloading it, extract the archive +to a convenient location. I've chosen to create a `Libraries` directory in the +Visual Studio directory under documents. + +![](/images/glfw_directory.png) + +### GLM + +Unlike DirectX 12, 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. + +GLM is a header-only library, so just download the [latest version](https://github.com/g-truc/glm/releases) +and store it in a convenient location. You should have a directory structure +similar to the following now: + +![](/images/library_directory.png) + +### Setting up Visual Studio + +Now that you've installed all of the dependencies we can set up a basic Visual +Studio project for Vulkan and write a little bit of code to make sure that +everything works. + +Start Visual Studio and create a new `Windows Desktop Wizard` project by entering a name and pressing `OK`. + +![](/images/vs_new_cpp_project.png) + +Make sure that `Console Application (.exe)` is selected as application type so that we have a place to print debug messages to, and check `Empty Project` to prevent Visual Studio from adding boilerplate code. + +![](/images/vs_application_settings.png) + +Press `OK` to create the project and add a C++ source file. You should +already know how to do that, but the steps are included here for completeness. + +![](/images/vs_new_item.png) + +![](/images/vs_new_source_file.png) + +Now add the following code to the file. Don't worry about trying to +understand it right now; we're just making sure that you can compile and run +Vulkan applications. We'll start from scratch in the next chapter. + +```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\n"; + + glm::mat4 matrix; + glm::vec4 vec; + auto test = matrix * vec; + + while(!glfwWindowShouldClose(window)) { + glfwPollEvents(); + } + + glfwDestroyWindow(window); + + glfwTerminate(); + + return 0; +} +``` + +Let's now configure the project to get rid of the errors. Open the project +properties dialog and ensure that `All Configurations` is selected, because most +of the settings apply to both `Debug` and `Release` mode. + +![](/images/vs_open_project_properties.png) + +![](/images/vs_all_configs.png) + +Go to `C++ -> General -> Additional Include Directories` and press `` +in the dropdown box. + +![](/images/vs_cpp_general.png) + +Add the header directories for Vulkan, GLFW and GLM: + +![](/images/vs_include_dirs.png) + +Next, open the editor for library directories under `Linker -> General`: + +![](/images/vs_link_settings.png) + +And add the locations of the object files for Vulkan and GLFW: + +![](/images/vs_link_dirs.png) + +Go to `Linker -> Input` and press `` in the `Additional Dependencies` +dropdown box. + +![](/images/vs_link_input.png) + +Enter the names of the Vulkan and GLFW object files: + +![](/images/vs_dependencies.png) + +And finally change the compiler to support C++17 features: + +![](/images/vs_cpp17.png) + +You can now close the project properties dialog. If you did everything right +then you should no longer see any more errors being highlighted in the code. + +Finally, ensure that you are actually compiling in 64 bit mode: + +![](/images/vs_build_mode.png) + +Press `F5` to compile and run the project and you should see a command prompt +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](!en/Drawing_a_triangle/Setup/Base_code)! + +## Linux + +These instructions will be aimed at Ubuntu, Fedora and Arch Linux users, but you may be able to follow +along by changing the package manager-specific commands to the ones that are appropriate for you. You should have a compiler that supports C++17 (GCC 7+ or Clang 5+). You'll also need `make`. + +### Vulkan Packages + +The most important components you'll need for developing Vulkan applications on Linux are the Vulkan loader, validation layers, and a couple of command-line utilities to test whether your machine is Vulkan-capable: + +* `sudo apt install vulkan-tools` or `sudo dnf install vulkan-tools`: Command-line utilities, most importantly `vulkaninfo` and `vkcube`. Run these to confirm your machine supports Vulkan. +* `sudo apt install libvulkan-dev` or `sudo dnf install vulkan-loader-devel` : Installs Vulkan loader. The loader looks up the functions in the driver at runtime, similarly to GLEW for OpenGL - if you're familiar with that. +* `sudo apt install vulkan-validationlayers-dev spirv-tools` or `sudo dnf install mesa-vulkan-devel vulkan-validation-layers-devel`: Installs the standard validation layers and required SPIR-V tools. These are crucial when debugging Vulkan applications, and we'll discuss them in the upcoming chapter. + +On Arch Linux, you can run `sudo pacman -S vulkan-devel` to install all the +required tools above. + +If installation was successful, you should be all set with the Vulkan portion. Remember to run + `vkcube` and ensure you see the following pop up in a window: + +![](/images/cube_demo_nowindow.png) + +If you receive an error message then ensure that your drivers are up-to-date, +include the Vulkan runtime and that your graphics card is supported. See the +[introduction chapter](!en/Introduction) for links to drivers from the major +vendors. + +### 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. 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 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. + +We'll be installing GLFW from the following command: + +```bash +sudo apt install libglfw3-dev +``` +or +```bash +sudo dnf install glfw-devel +``` +or +```bash +sudo pacman -S glfw-wayland # glfw-x11 for X11 users +``` + +### GLM + +Unlike DirectX 12, 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 `libglm-dev` or +`glm-devel` package: + +```bash +sudo apt install libglm-dev +``` +or +```bash +sudo dnf install glm-devel +``` +or +```bash +sudo pacman -S glm +``` + +### Shader Compiler + +We have just about all we need, except we'll want a program to compile shaders from the human-readable [GLSL](https://en.wikipedia.org/wiki/OpenGL_Shading_Language) to bytecode. + +Two popular shader compilers are Khronos Group's `glslangValidator` and Google's `glslc`. The latter has a familiar GCC- and Clang-like usage, so we'll go with that: on Ubuntu, download Google's [unofficial binaries](https://github.com/google/shaderc/blob/main/downloads.md) and copy `glslc` to your `/usr/local/bin`. Note you may need to `sudo` depending on your permissions. On Fedora use `sudo dnf install glslc`, while on Arch Linux run `sudo pacman -S shaderc`. To test, run `glslc` and it should rightfully complain we didn't pass any shaders to compile: + +`glslc: error: no input files` + +We'll cover `glslc` in depth in the [shader modules](!en/Drawing_a_triangle/Graphics_pipeline_basics/Shader_modules) chapter. + +### Setting up a makefile project + +Now that you have installed all of the dependencies, we can set up a basic +makefile project for Vulkan and write a little bit of code to make sure that +everything works. + +Create a new directory at a convenient location with a name like `VulkanTest`. +Create a source file called `main.cpp` and insert the following code. Don't +worry about trying to understand it right now; we're just making sure that you +can compile and run Vulkan applications. We'll start from scratch in the next +chapter. + +```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\n"; + + glm::mat4 matrix; + glm::vec4 vec; + auto test = matrix * vec; + + while(!glfwWindowShouldClose(window)) { + glfwPollEvents(); + } + + glfwDestroyWindow(window); + + glfwTerminate(); + + return 0; +} +``` + +Next, we'll write a makefile to compile and run this basic Vulkan code. Create a +new empty file called `Makefile`. I will assume that you already have some basic +experience with makefiles, like how variables and rules work. If not, you can +get up to speed very quickly with [this tutorial](https://makefiletutorial.com/). + +We'll first define a couple of variables to simplify the remainder of the file. +Define a `CFLAGS` variable that will specify the basic compiler flags: + +```make +CFLAGS = -std=c++17 -O2 +``` + +We're going to use modern C++ (`-std=c++17`), and we'll set optimization level to O2. We can remove -O2 to compile programs faster, but we should remember to place it back for release builds. + +Similarly, define the linker flags in a `LDFLAGS` variable: + +```make +LDFLAGS = -lglfw -lvulkan -ldl -lpthread -lX11 -lXxf86vm -lXrandr -lXi +``` + +The flag `-lglfw` is for GLFW, `-lvulkan` links with the Vulkan function loader and the remaining flags are low-level system libraries that GLFW needs. The remaining flags are dependencies of GLFW itself: the threading and window management. + +It is possible that the `Xxf68vm` and `Xi` libraries are not yet installed on your system. You can find them in the following packages: + +```bash +sudo apt install libxxf86vm-dev libxi-dev +``` +or +```bash +sudo dnf install libXi libXxf86vm +``` +or +```bash +sudo pacman -S libxi libxxf86vm +``` + +Specifying the rule to compile `VulkanTest` is straightforward now. Make sure to +use tabs for indentation instead of spaces. + +```make +VulkanTest: main.cpp + g++ $(CFLAGS) -o VulkanTest main.cpp $(LDFLAGS) +``` + +Verify that this rule works by saving the makefile and running `make` in the +directory with `main.cpp` and `Makefile`. This should result in a `VulkanTest` +executable. + +We'll now define two more rules, `test` and `clean`, where the former will +run the executable and the latter will remove a built executable: + +```make +.PHONY: test clean + +test: VulkanTest + ./VulkanTest + +clean: + rm -f VulkanTest +``` + +Running `make test` should show the program running successfully, and displaying the number of Vulkan extensions. The application should exit with the success return code (`0`) when you close the empty window. You should now have a complete makefile that resembles the following: + +```make +CFLAGS = -std=c++17 -O2 +LDFLAGS = -lglfw -lvulkan -ldl -lpthread -lX11 -lXxf86vm -lXrandr -lXi + +VulkanTest: main.cpp + g++ $(CFLAGS) -o VulkanTest main.cpp $(LDFLAGS) + +.PHONY: test clean + +test: VulkanTest + ./VulkanTest + +clean: + rm -f VulkanTest +``` + +You can now use this directory as a template for your Vulkan projects. Make a copy, rename it to something like `HelloTriangle` and remove all of the code in `main.cpp`. + +You are now all set for [the real adventure](!en/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 `vkcube` 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 to get the `glfw` package: + +```bash +brew install glfw +``` + +### 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\n"; + + 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 the 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 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/share/vulkan/icd.d/MoltenVK_icd.json` +* VK_LAYER_PATH = `vulkansdk/macOS/share/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](!en/Drawing_a_triangle/Setup/Base_code). diff --git a/en/03_Drawing_a_triangle/00_Setup/00_Base_code.md b/en/03_Drawing_a_triangle/00_Setup/00_Base_code.md new file mode 100644 index 00000000..df26c6ac --- /dev/null +++ b/en/03_Drawing_a_triangle/00_Setup/00_Base_code.md @@ -0,0 +1,217 @@ +## General structure + +In the previous chapter you've created a Vulkan project with all of the proper +configuration and tested it with the sample code. In this chapter we're starting +from scratch with the following code: + +```c++ +#include + +#include +#include +#include + +class HelloTriangleApplication { +public: + void run() { + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + void initVulkan() { + + } + + void mainLoop() { + + } + + void cleanup() { + + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} +``` + +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 `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. 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 that need to be freed at the end in `cleanup`. + +## Resource management + +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 C++ it is possible to perform automatic resource +management using [RAII](https://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization) +or smart pointers provided in the `` header. However, 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 writing C++ classes that acquire Vulkan objects in their constructor and +release them in their destructor, or by providing a custom deleter to either +`std::unique_ptr` or `std::shared_ptr`, depending on your ownership requirements. +RAII is the recommended model 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 + +Vulkan works perfectly fine without creating a window if you want to use it for +off-screen rendering, but it's a lot more exciting to actually show something! +First replace the `#include ` line with + +```c++ +#define GLFW_INCLUDE_VULKAN +#include +``` + +That way GLFW will include its own definitions and automatically load the Vulkan +header with it. Add a `initWindow` function and add a call to it from the `run` +function before the other calls. We'll use that function to initialize GLFW and +create a window. + +```c++ +void run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); +} + +private: + void initWindow() { + + } +``` + +The very first call in `initWindow` should be `glfwInit()`, which initializes +the GLFW library. Because GLFW was originally designed to create an OpenGL +context, we need to tell it to not create an OpenGL context with a subsequent +call: + +```c++ +glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); +``` + +Because handling resized windows takes special care that we'll look into later, +disable it for now with another window hint call: + +```c++ +glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); +``` + +All that's left now is creating the actual window. Add a `GLFWwindow* window;` +private class member to store a reference to it and initialize the window with: + +```c++ +window = glfwCreateWindow(800, 600, "Vulkan", nullptr, nullptr); +``` + +The first three parameters specify the width, height and title of the window. +The fourth parameter allows you to optionally specify a monitor to open the +window on and the last parameter is only relevant to OpenGL. + +It's a good idea to use constants instead of hardcoded width and height numbers +because we'll be referring to these values a couple of times in the future. I've +added the following lines above the `HelloTriangleApplication` class definition: + +```c++ +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; +``` + +and replaced the window creation call with + +```c++ +window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); +``` + +You should now have a `initWindow` function that looks like this: + +```c++ +void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); +} +``` + +To keep the application running until either an error occurs or the window is +closed, we need to add an event loop to the `mainLoop` function as follows: + +```c++ +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(); +} +``` + +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](!en/Drawing_a_triangle/Setup/Instance)! + +[C++ code](/code/00_base_code.cpp) diff --git a/03_Drawing_a_triangle/00_Setup/01_Instance.md b/en/03_Drawing_a_triangle/00_Setup/01_Instance.md similarity index 69% rename from 03_Drawing_a_triangle/00_Setup/01_Instance.md rename to en/03_Drawing_a_triangle/00_Setup/01_Instance.md index 19c8a7e5..0cac4e39 100644 --- a/03_Drawing_a_triangle/00_Setup/01_Instance.md +++ b/en/03_Drawing_a_triangle/00_Setup/01_Instance.md @@ -5,7 +5,7 @@ an *instance*. The instance is the connection between your application and the Vulkan library and creating it involves specifying some details about your application to the driver. -Start by adding a `createInstance` function and add a call to it in the +Start by adding a `createInstance` function and invoking it in the `initVulkan` function. ```c++ @@ -14,40 +14,35 @@ 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 data 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 -application, for example because it uses a well-known graphics engine with -certain special behavior. This struct is called `VkApplicationInfo`: +provide some useful information to the driver in order to optimize our specific +application (e.g. because it uses a well-known graphics engine with +certain special behavior). This struct is called `VkApplicationInfo`: ```c++ -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; +void createInstance() { + 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; +} ``` As mentioned before, many structs in Vulkan require you to explicitly specify the type in the `sType` member. This is also one of the many structs with a `pNext` member that can point to extension information in the future. We're -using default initialization here to leave it as `nullptr`. +using value initialization here to leave it as `nullptr`. A lot of information in Vulkan is passed through structs instead of function parameters and we'll have to fill in one more struct to provide sufficient @@ -57,7 +52,7 @@ Global here means that they apply to the entire program and not a specific device, which will become clear in the next few chapters. ```c++ -VkInstanceCreateInfo createInfo = {}; +VkInstanceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; createInfo.pApplicationInfo = &appInfo; ``` @@ -69,7 +64,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 +85,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 +96,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!"); } ``` @@ -154,10 +150,10 @@ extension. We can list them with a simple for loop (`\t` is a tab for indentation): ```c++ -std::cout << "available extensions:" << std::endl; +std::cout << "available extensions:\n"; for (const auto& extension : extensions) { - std::cout << "\t" << extension.extensionName << std::endl; + std::cout << '\t' << extension.extensionName << '\n'; } ``` @@ -167,7 +163,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). +to evaluate our debugging options by checking out [validation layers](!en/Drawing_a_triangle/Setup/Validation_layers). -[C++ code](/code/instance_creation.cpp) +[C++ code](/code/01_instance_creation.cpp) diff --git a/en/03_Drawing_a_triangle/00_Setup/02_Validation_layers.md b/en/03_Drawing_a_triangle/00_Setup/02_Validation_layers.md new file mode 100644 index 00000000..f2afa61f --- /dev/null +++ b/en/03_Drawing_a_triangle/00_Setup/02_Validation_layers.md @@ -0,0 +1,458 @@ +## What are validation layers? + +The Vulkan API is designed around the idea of minimal driver overhead and one of +the manifestations of that goal is that there is very limited error checking in +the API by default. Even mistakes as simple as setting enumerations to incorrect +values or passing null pointers to required parameters are generally not +explicitly handled and will simply result in crashes or undefined behavior. +Because Vulkan requires you to be very explicit about everything you're doing, +it's easy to make many small mistakes like using a new GPU feature and +forgetting to request it at logical device creation time. + +However, that doesn't mean that these checks can't be added to the API. Vulkan +introduces an elegant system for this known as *validation layers*. Validation +layers are optional components that hook into Vulkan function calls to apply +additional operations. Common operations in validation layers are: + +* Checking the values of parameters against the specification to detect misuse +* Tracking creation and destruction of objects to find resource leaks +* Checking thread safety by tracking the threads that calls originate from +* Logging every call and its parameters to the standard output +* Tracing Vulkan calls for profiling and replaying + +Here's an example of what the implementation of a function in a diagnostics +validation layer could look like: + +```c++ +VkResult vkCreateInstance( + const VkInstanceCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkInstance* instance) { + + if (pCreateInfo == nullptr || instance == nullptr) { + log("Null pointer passed to required parameter!"); + return VK_ERROR_INITIALIZATION_FAILED; + } + + return real_vkCreateInstance(pCreateInfo, pAllocator, instance); +} +``` + +These validation layers can be freely stacked to include all the debugging +functionality that you're interested in. You can simply enable validation layers +for debug builds and completely disable them for release builds, which gives you +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/KhronosGroup/Vulkan-ValidationLayers), +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. + +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. 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 +implementations. We'll simply specify the same layers as the instance at logical +device level, which we'll see [later on](!en/Drawing_a_triangle/Setup/Logical_device_and_queues). + +## Using validation layers + +In this section we'll see how to enable the standard diagnostics layers provided +by the Vulkan SDK. Just like extensions, validation layers need to be enabled by +specifying their name. All of the useful standard validation is bundled into a layer included in the SDK that is known as `VK_LAYER_KHRONOS_validation`. + +Let's first add two configuration variables to the program to specify the layers +to enable and whether to enable them or not. I've chosen to base that value on +whether the program is being compiled in debug mode or not. The `NDEBUG` macro +is part of the C++ standard and means "not debug". + +```c++ +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +#ifdef NDEBUG + const bool enableValidationLayers = false; +#else + const bool enableValidationLayers = true; +#endif +``` + +We'll add a new function `checkValidationLayerSupport` that checks if all of +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. + +```c++ +bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + return false; +} +``` + +Next, check if all of the layers in `validationLayers` exist in the +`availableLayers` list. You may need to include `` for `strcmp`. + +```c++ +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; +``` + +We can now use this function in `createInstance`: + +```c++ +void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + ... +} +``` + +Now run the program in debug mode and ensure that the error does not occur. If +it does, then have a look at the FAQ. + +Finally, modify the `VkInstanceCreateInfo` struct instantiation to include the +validation layer names if they are enabled: + +```c++ +if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); +} else { + createInfo.enabledLayerCount = 0; +} +``` + +If the check was successful then `vkCreateInstance` should not ever return a +`VK_ERROR_LAYER_NOT_PRESENT` error, but you should run the program to make sure. + +## Message callback + +The validation layers will print debug messages to the standard output by default, but we can also handle them ourselves by providing an explicit callback in our program. This will also allow you to decide which kind of messages you would like to see, because not all are necessarily (fatal) errors. If you don't want to do that right now then you may skip to the last section in this chapter. + +To set up a callback in the program to handle messages and the associated details, we have to set up a debug messenger with a callback using the `VK_EXT_debug_utils` extension. + +We'll first create a `getRequiredExtensions` function that will return the +required list of extensions based on whether validation layers are enabled or +not: + +```c++ +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_UTILS_EXTENSION_NAME); + } + + return extensions; +} +``` + +The extensions specified by GLFW are always required, but the debug messenger +extension is conditionally added. Note that I've used the +`VK_EXT_DEBUG_UTILS_EXTENSION_NAME` macro here which is equal to the literal +string "VK_EXT_debug_utils". Using this macro lets you avoid typos. + +We can now use this function in `createInstance`: + +```c++ +auto extensions = getRequiredExtensions(); +createInfo.enabledExtensionCount = static_cast(extensions.size()); +createInfo.ppEnabledExtensionNames = extensions.data(); +``` + +Run the program to make sure you don't receive a +`VK_ERROR_EXTENSION_NOT_PRESENT` error. We don't really need to check for the +existence of this extension, because it should be implied by the availability of +the validation layers. + +Now let's see what a debug callback function looks like. Add a new static member +function called `debugCallback` with the `PFN_vkDebugUtilsMessengerCallbackEXT` +prototype. The `VKAPI_ATTR` and `VKAPI_CALL` ensure that the function has the +right signature for Vulkan to call it. + +```c++ +static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback( + VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, + VkDebugUtilsMessageTypeFlagsEXT messageType, + const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, + void* pUserData) { + + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; +} +``` + +The first parameter specifies the severity of the message, which is one of the following flags: + +* `VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT`: Diagnostic message +* `VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT`: Informational message like the creation of a resource +* `VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT`: Message about behavior that is not necessarily an error, but very likely a bug in your application +* `VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT`: Message about behavior that is invalid and may cause crashes + +The values of this enumeration are set up in such a way that you can use a comparison operation to check if a message is equal or worse compared to some level of severity, for example: + +```c++ +if (messageSeverity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) { + // Message is important enough to show +} +``` + +The `messageType` parameter can have the following values: + +* `VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT`: Some event has happened that is unrelated to the specification or performance +* `VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT`: Something has happened that violates the specification or indicates a possible mistake +* `VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT`: Potential non-optimal use of Vulkan + +The `pCallbackData` parameter refers to a `VkDebugUtilsMessengerCallbackDataEXT` struct containing the details of the message itself, with the most important members being: + +* `pMessage`: The debug message as a null-terminated string +* `pObjects`: Array of Vulkan object handles related to the message +* `objectCount`: Number of objects in array + +Finally, the `pUserData` parameter contains a pointer that was specified during the setup of the callback and allows you to pass your own data to it. + +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 +handle that needs to be explicitly created and destroyed. Such a callback is part of a *debug messenger* and you can have as many of them as you want. Add a class member for +this handle right under `instance`: + +```c++ +VkDebugUtilsMessengerEXT debugMessenger; +``` + +Now add a function `setupDebugMessenger` to be called from `initVulkan` right +after `createInstance`: + +```c++ +void initVulkan() { + createInstance(); + setupDebugMessenger(); +} + +void setupDebugMessenger() { + if (!enableValidationLayers) return; + +} +``` + +We'll need to fill in a structure with details about the messenger and its callback: + +```c++ +VkDebugUtilsMessengerCreateInfoEXT createInfo{}; +createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; +createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; +createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; +createInfo.pfnUserCallback = debugCallback; +createInfo.pUserData = nullptr; // Optional +``` + +The `messageSeverity` field allows you to specify all the types of severities you would like your callback to be called for. I've specified all types except for `VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT` here to receive notifications about possible problems while leaving out verbose general debug info. + +Similarly the `messageType` field lets you filter which types of messages your callback is notified about. I've simply enabled all types here. You can always disable some if they're not useful to you. + +Finally, the `pfnUserCallback` field specifies the pointer to the callback function. You can optionally pass a pointer to the `pUserData` field which will be passed along to the callback function via the `pUserData` parameter. You could use this to pass a pointer to the `HelloTriangleApplication` class, for example. + +Note that there are many more ways to configure validation layer messages and debug callbacks, but this is a good setup to get started with for this tutorial. See the [extension specification](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap50.html#VK_EXT_debug_utils) for more info about the possibilities. + +This struct should be passed to the `vkCreateDebugUtilsMessengerEXT` function to +create the `VkDebugUtilsMessengerEXT` 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 `HelloTriangleApplication` class definition. + +```c++ +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} +``` + +The `vkGetInstanceProcAddr` function will return `nullptr` if the function +couldn't be loaded. We can now call this function to create the extension +object if it's available: + +```c++ +if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); +} +``` + +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 messenger 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. + +The `VkDebugUtilsMessengerEXT` object also needs to be cleaned up with a call to +`vkDestroyDebugUtilsMessengerEXT`. Similarly to `vkCreateDebugUtilsMessengerEXT` +the function needs to be explicitly loaded. + +Create another proxy function right below `CreateDebugUtilsMessengerEXT`: + +```c++ +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} +``` + +Make sure that this function is either a static class function or a function +outside the class. We can then call it in the `cleanup` function: + +```c++ +void cleanup() { + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); +} +``` + +## Debugging instance creation and destruction + +Although we've now added debugging with validation layers to the program we're not covering everything quite yet. The `vkCreateDebugUtilsMessengerEXT` call requires a valid instance to have been created and `vkDestroyDebugUtilsMessengerEXT` must be called before the instance is destroyed. This currently leaves us unable to debug any issues in the `vkCreateInstance` and `vkDestroyInstance` calls. + +However, if you closely read the [extension documentation](https://github.com/KhronosGroup/Vulkan-Docs/blob/master/appendices/VK_EXT_debug_utils.txt#L120), you'll see that there is a way to create a separate debug utils messenger specifically for those two function calls. It requires you to simply pass a pointer to a `VkDebugUtilsMessengerCreateInfoEXT` struct in the `pNext` extension field of `VkInstanceCreateInfo`. First extract population of the messenger create info into a separate function: + +```c++ +void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; +} + +... + +void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } +} +``` + +We can now re-use this in the `createInstance` function: + +```c++ +void createInstance() { + ... + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + ... + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; + } else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } +} +``` + +The `debugCreateInfo` variable is placed outside the if statement to ensure that it is not destroyed before the `vkCreateInstance` call. By creating an additional debug messenger this way it will automatically be used during `vkCreateInstance` and `vkDestroyInstance` and cleaned up after that. + +## Testing + +Now let's intentionally make a mistake to see the validation layers in action. Temporarily remove the call to `DestroyDebugUtilsMessengerEXT` in the `cleanup` function and run your program. Once it exits you should see something like this: + +![](/images/validation_layer_test.png) + +>If you don't see any messages then [check your installation](https://vulkan.lunarg.com/doc/view/1.2.131.1/windows/getting_started.html#user-content-verify-the-installation). + +If you want to see which call triggered a message, you can add a breakpoint to the message callback and look at the stack trace. + +## Configuration + +There are a lot more settings for the behavior of validation layers than just +the flags specified in the `VkDebugUtilsMessengerCreateInfoEXT` struct. Browse +to the Vulkan SDK and go to the `Config` directory. There you will find a +`vk_layer_settings.txt` file that explains how to configure the layers. + +To configure the layer settings for your own application, copy the file to the +`Debug` and `Release` directories of your project and follow the instructions to +set the desired behavior. However, for the remainder of this tutorial I'll +assume that you're using the default settings. + +Throughout this tutorial I'll be making a couple of intentional mistakes to show +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](!en/Drawing_a_triangle/Setup/Physical_devices_and_queue_families). + +[C++ code](/code/02_validation_layers.cpp) diff --git a/03_Drawing_a_triangle/00_Setup/03_Physical_devices_and_queue_families.md b/en/03_Drawing_a_triangle/00_Setup/03_Physical_devices_and_queue_families.md similarity index 72% rename from 03_Drawing_a_triangle/00_Setup/03_Physical_devices_and_queue_families.md rename to en/03_Drawing_a_triangle/00_Setup/03_Physical_devices_and_queue_families.md index 4f090b7f..5761b9bc 100644 --- a/03_Drawing_a_triangle/00_Setup/03_Physical_devices_and_queue_families.md +++ b/en/03_Drawing_a_triangle/00_Setup/03_Physical_devices_and_queue_families.md @@ -11,7 +11,7 @@ We'll add a function `pickPhysicalDevice` and add a call to it in the ```c++ void initVulkan() { createInstance(); - setupDebugCallback(); + setupDebugMessenger(); pickPhysicalDevice(); } @@ -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; @@ -201,24 +201,77 @@ commands or one that only allows memory transfer related commands. We need to check which queue families are supported by the device and which one of these supports the commands that we want to use. For that purpose we'll add a new function `findQueueFamilies` that looks for all the queue families we need. -Right now we'll only look for a queue that supports graphics commands, but we -may extend this function to look for more at a later point in time. -This function will return the indices of the queue families that satisfy certain -desired properties. The best way to do that is using a structure, where an -index of `-1` will denote "not found": +Right now we are only going to look for a queue that supports graphics commands, +so the function could look like this: + +```c++ +uint32_t findQueueFamilies(VkPhysicalDevice device) { + // Logic to find graphics queue family +} +``` + +However, in one of the next chapters we're already going to look for yet another +queue, so it's better to prepare for that and bundle the indices into a struct: ```c++ struct QueueFamilyIndices { - int graphicsFamily = -1; + uint32_t graphicsFamily; +}; - bool isComplete() { - return graphicsFamily >= 0; - } +QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + // Logic to find queue family indices to populate struct with + return indices; +} +``` + +But what if a queue family is not available? We could throw an exception in +`findQueueFamilies`, but this function is not really the right place to make +decisions about device suitability. For example, we may *prefer* devices with a +dedicated transfer queue family, but not require it. Therefore we need some way +of indicating whether a particular queue family was found. + +It's not really possible to use a magic value to indicate the nonexistence of a +queue family, since any value of `uint32_t` could in theory be a valid queue +family index including `0`. Luckily C++17 introduced a data structure to +distinguish between the case of a value existing or not: + +```c++ +#include + +... + +std::optional graphicsFamily; + +std::cout << std::boolalpha << graphicsFamily.has_value() << std::endl; // false + +graphicsFamily = 0; + +std::cout << std::boolalpha << graphicsFamily.has_value() << std::endl; // true +``` + +`std::optional` is a wrapper that contains no value until you assign something +to it. At any point you can query if it contains a value or not by calling its +`has_value()` member function. That means that we can change the logic to: + +```c++ +#include + +... + +struct QueueFamilyIndices { + std::optional graphicsFamily; }; + +QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + // Assign index to queue families that could be found + return indices; +} ``` -We can now begin implementing `findQueueFamilies`: +We can now begin to actually implement `findQueueFamilies`: ```c++ QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { @@ -249,14 +302,10 @@ family that supports `VK_QUEUE_GRAPHICS_BIT`. ```c++ int i = 0; for (const auto& queueFamily : queueFamilies) { - if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { indices.graphicsFamily = i; } - if (indices.isComplete()) { - break; - } - i++; } ``` @@ -266,6 +315,27 @@ check in the `isDeviceSuitable` function to ensure that the device can process the commands we want to use: ```c++ +bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + return indices.graphicsFamily.has_value(); +} +``` + +To make this a little bit more convenient, we'll also add a generic check to the +struct itself: + +```c++ +struct QueueFamilyIndices { + std::optional graphicsFamily; + + bool isComplete() { + return graphicsFamily.has_value(); + } +}; + +... + bool isDeviceSuitable(VkPhysicalDevice device) { QueueFamilyIndices indices = findQueueFamilies(device); @@ -273,8 +343,22 @@ bool isDeviceSuitable(VkPhysicalDevice device) { } ``` +We can now also use this for an early exit from `findQueueFamilies`: + +```c++ +for (const auto& queueFamily : queueFamilies) { + ... + + if (indices.isComplete()) { + break; + } + + i++; +} +``` + 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) +step is to [create a logical device](!en/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/en/03_Drawing_a_triangle/00_Setup/04_Logical_device_and_queues.md similarity index 76% rename from 03_Drawing_a_triangle/00_Setup/04_Logical_device_and_queues.md rename to en/03_Drawing_a_triangle/00_Setup/04_Logical_device_and_queues.md index 9bfd17c7..f2677d08 100644 --- a/03_Drawing_a_triangle/00_Setup/04_Logical_device_and_queues.md +++ b/en/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`. @@ -21,7 +18,7 @@ Next, add a `createLogicalDevice` function that is called from `initVulkan`. ```c++ void initVulkan() { createInstance(); - setupDebugCallback(); + setupDebugMessenger(); pickPhysicalDevice(); createLogicalDevice(); } @@ -41,14 +38,14 @@ Right now we're only interested in a queue with graphics capabilities. ```c++ QueueFamilyIndices indices = findQueueFamilies(physicalDevice); -VkDeviceQueueCreateInfo queueCreateInfo = {}; +VkDeviceQueueCreateInfo queueCreateInfo{}; queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; -queueCreateInfo.queueFamilyIndex = indices.graphicsFamily; +queueCreateInfo.queueFamilyIndex = indices.graphicsFamily.value(); 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. @@ -71,7 +68,7 @@ everything to `VK_FALSE`. We'll come back to this structure once we're about to start doing more interesting things with Vulkan. ```c++ -VkPhysicalDeviceFeatures deviceFeatures = {}; +VkPhysicalDeviceFeatures deviceFeatures{}; ``` ## Creating the logical device @@ -80,7 +77,7 @@ With the previous two structures in place, we can start filling in the main `VkDeviceCreateInfo` structure. ```c++ -VkDeviceCreateInfo createInfo = {}; +VkDeviceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; ``` @@ -103,26 +100,26 @@ there are Vulkan devices in the system that lack this ability, for example because they only support compute operations. We will come back to this extension in the swap chain chapter. -As mentioned in the validation layers chapter, we will enable the same -validation layers for devices as we did for the instance. We won't need any -device specific extensions for now. +Previous implementations of Vulkan made a distinction between instance and device specific validation layers, but this is [no longer the case](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap40.html#extendingvulkan-layers-devicelayerdeprecation). That means that the `enabledLayerCount` and `ppEnabledLayerNames` fields of `VkDeviceCreateInfo` are ignored by up-to-date implementations. However, it is still a good idea to set them anyway to be compatible with older implementations: ```c++ createInfo.enabledExtensionCount = 0; if (enableValidationLayers) { - createInfo.enabledLayerCount = validationLayers.size(); + createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } ``` +We won't need any device specific extensions for now. + 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 @@ -152,11 +161,11 @@ and a pointer to the variable to store the queue handle in. Because we're only creating a single queue from this family, we'll simply use index `0`. ```c++ -vkGetDeviceQueue(device, indices.graphicsFamily, 0, &graphicsQueue); +vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); ``` 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/en/03_Drawing_a_triangle/01_Presentation/00_Window_surface.md similarity index 79% rename from 03_Drawing_a_triangle/01_Presentation/00_Window_surface.md rename to en/03_Drawing_a_triangle/01_Presentation/00_Window_surface.md index b0480c57..966a8946 100644 --- a/03_Drawing_a_triangle/01_Presentation/00_Window_surface.md +++ b/en/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 @@ -43,13 +42,23 @@ platform-specific code anyway. GLFW actually has `glfwCreateWindowSurface` that handles the platform differences for us. Still, it's good to see what it does behind the scenes before we start relying on it. +To access native platform functions, you need to update the includes at the top: + +```c++ +#define VK_USE_PLATFORM_WIN32_KHR +#define GLFW_INCLUDE_VULKAN +#include +#define GLFW_EXPOSE_NATIVE_WIN32 +#include +``` + Because a window surface is a Vulkan object, it comes with a `VkWin32SurfaceCreateInfoKHR` struct that needs to be filled in. It has two 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); @@ -59,16 +68,10 @@ The `glfwGetWin32Window` function is used to get the raw `HWND` from the GLFW window object. The `GetModuleHandle` call returns the `HINSTANCE` handle of the current process. -After that the surface can be created with `vkCreateWin32SurfaceKHR`, which -needs to be explicitly loaded again. Other than that the call is trivial and -includes a parameter for the instance, surface creation details, custom -allocators and the variable for the surface handle to be stored in. +After that the surface can be created with `vkCreateWin32SurfaceKHR`, which includes a parameter for the instance, surface creation details, custom allocators and the variable for the surface handle to be stored in. Technically this is a WSI extension function, but it is so commonly used that the standard Vulkan loader includes it, so unlike other extensions you don't need to explicitly load it. ```c++ -auto CreateWin32SurfaceKHR = (PFN_vkCreateWin32SurfaceKHR) vkGetInstanceProcAddr(instance, "vkCreateWin32SurfaceKHR"); - -if (!CreateWin32SurfaceKHR || CreateWin32SurfaceKHR(instance, &createInfo, - nullptr, surface.replace()) != VK_SUCCESS) { +if (vkCreateWin32SurfaceKHR(instance, &createInfo, nullptr, &surface) != VK_SUCCESS) { throw std::runtime_error("failed to create window surface!"); } ``` @@ -80,12 +83,12 @@ with X11. The `glfwCreateWindowSurface` function performs exactly this operation with a different implementation for each platform. We'll now integrate it into our program. Add a function `createSurface` to be called from `initVulkan` right -after instance creation and `setupDebugCallback`. +after instance creation and `setupDebugMessenger`. ```c++ void initVulkan() { createInstance(); - setupDebugCallback(); + setupDebugMessenger(); createSurface(); pickPhysicalDevice(); createLogicalDevice(); @@ -101,7 +104,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 +112,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 @@ -127,11 +142,11 @@ account that there could be a distinct presentation queue by modifying the ```c++ struct QueueFamilyIndices { - int graphicsFamily = -1; - int presentFamily = -1; + std::optional graphicsFamily; + std::optional presentFamily; bool isComplete() { - return graphicsFamily >= 0 && presentFamily >= 0; + return graphicsFamily.has_value() && presentFamily.has_value(); } }; ``` @@ -151,7 +166,7 @@ Then simply check the value of the boolean and store the presentation family queue index: ```c++ -if (queueFamily.queueCount > 0 && presentSupport) { +if (presentSupport) { indices.presentFamily = i; } ``` @@ -184,11 +199,11 @@ unique queue families that are necessary for the required queues: QueueFamilyIndices indices = findQueueFamilies(physicalDevice); std::vector queueCreateInfos; -std::set uniqueQueueFamilies = {indices.graphicsFamily, indices.presentFamily}; +std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; float queuePriority = 1.0f; -for (int queueFamily : uniqueQueueFamilies) { - VkDeviceQueueCreateInfo queueCreateInfo = {}; +for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; queueCreateInfo.queueFamilyIndex = queueFamily; queueCreateInfo.queueCount = 1; @@ -200,19 +215,19 @@ 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. Finally, add a call to retrieve the queue handle: ```c++ -vkGetDeviceQueue(device, indices.presentFamily, 0, &presentQueue); +vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); ``` 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/en/03_Drawing_a_triangle/01_Presentation/01_Swap_chain.md similarity index 72% rename from 03_Drawing_a_triangle/01_Presentation/01_Swap_chain.md rename to en/03_Drawing_a_triangle/01_Presentation/01_Swap_chain.md index 67675070..ef1ceabd 100644 --- a/03_Drawing_a_triangle/01_Presentation/01_Swap_chain.md +++ b/en/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 @@ -81,14 +80,19 @@ as we checked in the previous chapter, implies that the swap chain extension must be supported. However, it's still good to be explicit about things, and the extension does have to be explicitly enabled. +## Enabling device extensions + +Using a swapchain requires enabling the `VK_KHR_swapchain` extension first. 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(); ``` +Make sure to replace the existing line `createInfo.enabledExtensionCount = 0;` when you do so. + ## Querying details of swap chain support Just checking if a swap chain is available is not sufficient, because it may not @@ -216,35 +220,22 @@ 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 +`VK_FORMAT_B8G8R8A8_SRGB` 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 `colorSpace` member indicates if the SRGB color space is supported or not using the `VK_COLOR_SPACE_SRGB_NONLINEAR_KHR` flag. Note that this flag used to be called `VK_COLORSPACE_SRGB_NONLINEAR_KHR` in old versions of the specification. -For the color space we'll use SRGB if it is available, because it [results in more accurate perceived colors](http://stackoverflow.com/questions/12524623/). -Working directly with SRGB colors is a little bit challenging, so we'll use -standard RGB for the color format, of which one of the most common ones is -`VK_FORMAT_B8G8R8A8_UNORM`. - -The best case scenario is that the surface has no preferred format, which Vulkan -indicates by only returning one `VkSurfaceFormatKHR` entry which has its -`format` member set to `VK_FORMAT_UNDEFINED`. - -```c++ -if (availableFormats.size() == 1 && availableFormats[0].format == VK_FORMAT_UNDEFINED) { - return {VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}; -} -``` +For the color space we'll use SRGB if it is available, because it [results in more accurate perceived colors](http://stackoverflow.com/questions/12524623/). It is also pretty much the standard color space for images, like the textures we'll use later on. +Because of that we should also use an SRGB color format, of which one of the most common ones is `VK_FORMAT_B8G8R8A8_SRGB`. -If we're not free to choose any format, then we'll go through the list and see -if the preferred combination is available: +Let's go through the list and see if the preferred combination is available: ```c++ for (const auto& availableFormat : availableFormats) { - if (availableFormat.format == VK_FORMAT_B8G8R8A8_UNORM && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { return availableFormat; } } @@ -256,12 +247,8 @@ format that is specified. ```c++ 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) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { return availableFormat; } } @@ -279,35 +266,33 @@ 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 -less latency issues than standard vertical sync that uses double buffering. +render frames as fast as possible while still avoiding tearing, resulting in fewer latency issues than standard vertical sync. This is commonly known as "triple buffering", although the existence of three buffers alone does not necessarily mean that the framerate is unlocked. Only the `VK_PRESENT_MODE_FIFO_KHR` mode is guaranteed to be available, so we'll again have to write a function that looks for the best mode that is available: ```c++ -VkPresentModeKHR chooseSwapPresentMode(const std::vector availablePresentModes) { +VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { return VK_PRESENT_MODE_FIFO_KHR; } ``` -I personally think that triple buffering is a very nice trade-off. It allows us -to avoid tearing while still maintaining a fairly low latency by rendering new -images that are as up-to-date as possible right until the vertical blank. So, -let's look through the list to see if it's available: +I personally think that `VK_PRESENT_MODE_MAILBOX_KHR` is a very nice trade-off if energy usage is not a concern. It allows us to avoid tearing while still maintaining a fairly low latency by rendering new images that are as up-to-date as possible right until the vertical blank. On mobile devices, where energy usage is more important, you will probably want to use `VK_PRESENT_MODE_FIFO_KHR` instead. Now, let's look through the list to see if `VK_PRESENT_MODE_MAILBOX_KHR` is available: ```c++ -VkPresentModeKHR chooseSwapPresentMode(const std::vector availablePresentModes) { +VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { for (const auto& availablePresentMode : availablePresentModes) { if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { return availablePresentMode; @@ -318,26 +303,6 @@ VkPresentModeKHR chooseSwapPresentMode(const std::vector avail } ``` -Unfortunately some drivers currently don't properly support -`VK_PRESENT_MODE_FIFO_KHR`, so we should prefer `VK_PRESENT_MODE_IMMEDIATE_KHR` -if `VK_PRESENT_MODE_MAILBOX_KHR` is not available: - -```c++ -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; -} -``` - ### Swap extent That leaves only one major property, for which we'll add one last function: @@ -349,33 +314,57 @@ VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { ``` The swap extent is the resolution of the swap chain images and it's almost -always exactly equal to the resolution of the window that we're drawing to. The -range of the possible resolutions is defined in the `VkSurfaceCapabilitiesKHR` -structure. Vulkan tells us to match the resolution of the window by setting the -width and height in the `currentExtent` member. However, some window managers do -allow us to differ here and this is indicated by setting the width and height in -`currentExtent` to a special value: the maximum value of `uint32_t`. In that -case we'll pick the resolution that best matches the window within the -`minImageExtent` and `maxImageExtent` bounds. +always exactly equal to the resolution of the window that we're drawing to _in +pixels_ (more on that in a moment). The range of the possible resolutions is +defined in the `VkSurfaceCapabilitiesKHR` structure. Vulkan tells us to match +the resolution of the window by setting the width and height in the +`currentExtent` member. However, some window managers do allow us to differ here +and this is indicated by setting the width and height in `currentExtent` to a +special value: the maximum value of `uint32_t`. In that case we'll pick the +resolution that best matches the window within the `minImageExtent` and +`maxImageExtent` bounds. But we must specify the resolution in the correct unit. + +GLFW uses two units when measuring sizes: pixels and +[screen coordinates](https://www.glfw.org/docs/latest/intro_guide.html#coordinate_systems). +For example, the resolution `{WIDTH, HEIGHT}` that we specified earlier when +creating the window is measured in screen coordinates. But Vulkan works with +pixels, so the swap chain extent must be specified in pixels as well. +Unfortunately, if you are using a high DPI display (like Apple's Retina +display), screen coordinates don't correspond to pixels. Instead, due to the +higher pixel density, the resolution of the window in pixel will be larger than +the resolution in screen coordinates. So if Vulkan doesn't fix the swap extent +for us, we can't just use the original `{WIDTH, HEIGHT}`. Instead, we must use +`glfwGetFramebufferSize` to query the resolution of the window in pixel before +matching it against the minimum and maximum image extent. + +```c++ +#include // Necessary for uint32_t +#include // Necessary for std::numeric_limits +#include // Necessary for std::clamp + +... -```c++ VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { if (capabilities.currentExtent.width != std::numeric_limits::max()) { return capabilities.currentExtent; } else { - VkExtent2D actualExtent = {WIDTH, HEIGHT}; + 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)); + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); return actualExtent; } } ``` -The `max` and `min` functions are used here to clamp the value of `WIDTH` and -`HEIGHT` between the allowed minimum and maximum extents that are supported by -the implementation. Make sure to include the `` header to use them. +The `clamp` function is used here to bound the values of `width` and `height` between the allowed minimum and maximum extents that are supported by the implementation. ## Creating the swap chain @@ -389,7 +378,7 @@ calls and make sure to call it from `initVulkan` after logical device creation. ```c++ void initVulkan() { createInstance(); - setupDebugCallback(); + setupDebugMessenger(); createSurface(); pickPhysicalDevice(); createLogicalDevice(); @@ -405,28 +394,31 @@ void createSwapChain() { } ``` -There is actually one more small things that need to be decided upon, but it's -so simple that it's not really worth creating separate functions for them. The -first one is the number of images in the swap chain, essentially the queue -length. The implementation specifies the minimum amount of images to function -properly and we'll try to have one more than that to properly implement triple -buffering. +Aside from these properties we also have to decide how many images we would like to have in the swap chain. The implementation specifies the minimum number that it requires to function: + +```c++ +uint32_t imageCount = swapChainSupport.capabilities.minImageCount; +``` + +However, simply sticking to this minimum means that we may sometimes have to wait on the driver to complete internal operations before we can acquire another image to render to. Therefore it is recommended to request at least one more image than the minimum: ```c++ uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; +``` + +We should also make sure to not exceed the maximum number of images while doing this, where `0` is a special value that means that there is no maximum: + +```c++ if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { imageCount = swapChainSupport.capabilities.maxImageCount; } ``` -A value of `0` for `maxImageCount` means that there is no limit besides memory -requirements, which is why we need to check for that. - As is tradition with Vulkan objects, creating the swap chain object requires filling in a large structure. It starts out very familiarly: ```c++ -VkSwapchainCreateInfoKHR createInfo = {}; +VkSwapchainCreateInfoKHR createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; createInfo.surface = surface; ``` @@ -455,7 +447,7 @@ the rendered image to a swap chain image. ```c++ QueueFamilyIndices indices = findQueueFamilies(physicalDevice); -uint32_t queueFamilyIndices[] = {(uint32_t) indices.graphicsFamily, (uint32_t) indices.presentFamily}; +uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; if (indices.graphicsFamily != indices.presentFamily) { createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; @@ -476,7 +468,7 @@ on the presentation queue. There are two ways to handle images that are accessed from multiple queues: * `VK_SHARING_MODE_EXCLUSIVE`: An image is owned by one queue family at a time -and ownership must be explicitly transfered before using it in another queue +and ownership must be explicitly transferred before using it in another queue family. This option offers the best performance. * `VK_SHARING_MODE_CONCURRENT`: Images can be used across multiple queue families without explicit ownership transfers. @@ -523,34 +515,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 +in [a future chapter](!en/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! If at this point you get an access violation error in `vkCreateSwapchainKHR` or see a message like `Failed to find 'vkGetInstanceProcAddress' in layer SteamOverlayVulkanLayer.dll`, then see the [FAQ entry](!en/FAQ) about the Steam overlay layer. Try removing the `createInfo.imageExtent = extent;` line with validation layers enabled. You'll see that one of the validation layers immediately catches the @@ -570,13 +567,11 @@ 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 -similar to the other times where we retrieved an array of objects from Vulkan. -First query the number of images in the swap chain with a call to -`vkGetSwapchainImagesKHR`, then resize the container and finally call it again +similar to the other times where we retrieved an array of objects from Vulkan. Remember that we only specified a minimum number of images in the swap chain, so the implementation is allowed to create a swap chain with more. That's why we'll first query the final number of images with `vkGetSwapchainImagesKHR`, then resize the container and finally call it again to retrieve the handles. ```c++ @@ -585,15 +580,11 @@ swapChainImages.resize(imageCount); vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); ``` -Note that when we created the swap chain, we passed the number of desired images -to a field called `minImageCount`. The implementation is allowed to create more -images, which is why we need to explicitly query the amount again. - 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 +596,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/en/03_Drawing_a_triangle/01_Presentation/02_Image_views.md similarity index 76% rename from 03_Drawing_a_triangle/01_Presentation/02_Image_views.md rename to en/03_Drawing_a_triangle/01_Presentation/02_Image_views.md index ede9da42..5988468a 100644 --- a/03_Drawing_a_triangle/01_Presentation/02_Image_views.md +++ b/en/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 @@ -22,7 +20,7 @@ creation. ```c++ void initVulkan() { createInstance(); - setupDebugCallback(); + setupDebugMessenger(); createSurface(); pickPhysicalDevice(); createLogicalDevice(); @@ -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++) { } ``` @@ -59,7 +55,7 @@ The parameters for image view creation are specified in a `VkImageViewCreateInfo` structure. The first few parameters are straightforward. ```c++ -VkImageViewCreateInfo createInfo = {}; +VkImageViewCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; createInfo.image = swapChainImages[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/en/03_Drawing_a_triangle/02_Graphics_pipeline_basics/00_Introduction.md similarity index 98% rename from 03_Drawing_a_triangle/02_Graphics_pipeline_basics/00_Introduction.md rename to en/03_Drawing_a_triangle/02_Graphics_pipeline_basics/00_Introduction.md index cc95308d..9ee7f739 100644 --- a/03_Drawing_a_triangle/02_Graphics_pipeline_basics/00_Introduction.md +++ b/en/03_Drawing_a_triangle/02_Graphics_pipeline_basics/00_Introduction.md @@ -80,7 +80,7 @@ following chapters. ```c++ void initVulkan() { createInstance(); - setupDebugCallback(); + setupDebugMessenger(); createSurface(); pickPhysicalDevice(); createLogicalDevice(); @@ -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/en/03_Drawing_a_triangle/02_Graphics_pipeline_basics/01_Shader_modules.md similarity index 74% rename from 03_Drawing_a_triangle/02_Graphics_pipeline_basics/01_Shader_modules.md rename to en/03_Drawing_a_triangle/02_Graphics_pipeline_basics/01_Shader_modules.md index 66440e6d..4bcaf34e 100644 --- a/03_Drawing_a_triangle/02_Graphics_pipeline_basics/01_Shader_modules.md +++ b/en/03_Drawing_a_triangle/02_Graphics_pipeline_basics/01_Shader_modules.md @@ -20,9 +20,7 @@ has released their own vendor-independent compiler that compiles GLSL to SPIR-V. This compiler is designed to verify that your shader code is fully standards compliant and produces one SPIR-V binary that you can ship with your program. You can also include this compiler as a library to produce SPIR-V at runtime, -but we won't be doing that in this tutorial. The compiler is already included -with the LunarG SDK as `glslangValidator.exe`, so you don't need to download -anything extra. +but we won't be doing that in this tutorial. Although we can use this compiler directly via `glslangValidator.exe`, we will be using `glslc.exe` by Google instead. The advantage of `glslc` is that it uses the same parameter format as well-known compilers like GCC and Clang and includes some extra functionality like *includes*. Both of them are already included in the Vulkan SDK, so you don't need to download anything extra. GLSL is a shading language with a C-style syntax. Programs written in it have a `main` function that is invoked for every object. Instead of using parameters @@ -53,23 +51,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 @@ -79,11 +85,6 @@ code looks like this: ```glsl #version 450 -#extension GL_ARB_separate_shader_objects : enable - -out gl_PerVertex { - vec4 gl_Position; -}; vec2 positions[3] = vec2[]( vec2(0.0, -0.5), @@ -102,8 +103,7 @@ the vertex buffer, but in our case it will be an index into a hardcoded array of vertex data. The position of each vertex is accessed from the constant array in the shader and combined with dummy `z` and `w` components to produce a position in clip coordinates. The built-in variable `gl_Position` functions as -the output. The `GL_ARB_separate_shader_objects` extension is required for -Vulkan shaders to work. +the output. ## Fragment shader @@ -115,7 +115,6 @@ like this: ```glsl #version 450 -#extension GL_ARB_separate_shader_objects : enable layout(location = 0) out vec4 outColor; @@ -194,11 +193,6 @@ The contents of `shader.vert` should be: ```glsl #version 450 -#extension GL_ARB_separate_shader_objects : enable - -out gl_PerVertex { - vec4 gl_Position; -}; layout(location = 0) out vec3 fragColor; @@ -224,7 +218,6 @@ And the contents of `shader.frag` should be: ```glsl #version 450 -#extension GL_ARB_separate_shader_objects : enable layout(location = 0) in vec3 fragColor; @@ -236,19 +229,19 @@ void main() { ``` We're now going to compile these into SPIR-V bytecode using the -`glslangValidator` program. +`glslc` program. **Windows** Create a `compile.bat` file with the following contents: ```bash -C:/VulkanSDK/1.0.17.0/Bin32/glslangValidator.exe -V shader.vert -C:/VulkanSDK/1.0.17.0/Bin32/glslangValidator.exe -V shader.frag +C:/VulkanSDK/x.x.x.x/Bin32/glslc.exe shader.vert -o vert.spv +C:/VulkanSDK/x.x.x.x/Bin32/glslc.exe shader.frag -o frag.spv pause ``` -Replace the path to `glslangValidator.exe` with the path to where you installed +Replace the path to `glslc.exe` with the path to where you installed the Vulkan SDK. Double click the file to run it. **Linux** @@ -256,21 +249,16 @@ the Vulkan SDK. Double click the file to run it. Create a `compile.sh` file with the following contents: ```bash -/home/user/VulkanSDK/x.x.x.x/x86_64/bin/glslangValidator -V shader.vert -/home/user/VulkanSDK/x.x.x.x/x86_64/bin/glslangValidator -V shader.frag +/home/user/VulkanSDK/x.x.x.x/x86_64/bin/glslc shader.vert -o vert.spv +/home/user/VulkanSDK/x.x.x.x/x86_64/bin/glslc shader.frag -o frag.spv ``` -Replace the path to `glslangValidator` with the path to where you installed the +Replace the path to `glslc` with the path to where you installed the Vulkan SDK. Make the script executable with `chmod +x compile.sh` and run it. **End of platform-specific instructions** -These two commands invoke the compiler with the `-V` flag, which tells it to -compile the GLSL source files to SPIR-V bytecode. When you run the compile -script, you'll see that two SPIR-V binaries are created: `vert.spv` and -`frag.spv`. The names are automatically derived from the type of shader, but you -can rename them to anything you like. You may get a warning about some missing -features when compiling your shaders, but you can safely ignore that. +These two commands tell the compiler to read the GLSL source file and output a SPIR-V bytecode file using the `-o` (output) flag. If your shader contains a syntax error then the compiler will tell you the line number and problem, as you would expect. Try leaving out a semicolon for example @@ -279,6 +267,8 @@ arguments to see what kinds of flags it supports. It can, for example, also output the bytecode into a human-readable format so you can see exactly what your shader is doing and any optimizations that have been applied at this stage. +Compiling shaders on the commandline is one of the most straightforward options and it's the one that we'll use in this tutorial, but it's also possible to compile shaders directly from your own code. The Vulkan SDK includes [libshaderc](https://github.com/google/shaderc), which is a library to compile GLSL code to SPIR-V from within your program. + ## Loading a shader Now that we have a way of producing SPIR-V shaders, it's time to load them into @@ -341,7 +331,7 @@ void createGraphicsPipeline() { ``` Make sure that the shaders are loaded correctly by printing the size of the -buffers and checking if they match the actual file size in bytes. +buffers and checking if they match the actual file size in bytes. Note that the code doesn't need to be null terminated since it's binary code and we will later be explicit about its size. ## Creating shader modules @@ -350,37 +340,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 = {}; +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,39 +377,42 @@ 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: -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++ +return shaderModule; +``` + +Shader modules are just a thin wrapper around the shader bytecode that we've previously loaded from a file and the functions defined in it. The compilation and linking of the SPIR-V bytecode to machine code for execution by the GPU doesn't happen until the graphics pipeline is created. That means that we're allowed to destroy the shader modules again as soon as pipeline creation is finished, which is why we'll make them local variables in the `createGraphicsPipeline` function instead of class members: ```c++ -VDeleter vertShaderModule{device, vkDestroyShaderModule}; -VDeleter fragShaderModule{device, vkDestroyShaderModule}; +void createGraphicsPipeline() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); + + VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); ``` -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: +The cleanup should then happen at the end of the function by adding two calls to `vkDestroyShaderModule`. All of the remaining code in this chapter will be inserted before these lines. ```c++ -createShaderModule(vertShaderCode, vertShaderModule); -createShaderModule(fragShaderCode, fragShaderModule); + ... + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); +} ``` ## Shader stage creation -The `VkShaderModule` object is just a dumb wrapper around the bytecode buffer. -The shaders aren't linked to each other yet and they haven't even been given a -purpose yet. Assigning a shader module to either the vertex or fragment shader -stage in the pipeline happens through a `VkPipelineShaderStageCreateInfo` -structure, which is part of the actual pipeline creation process. +To actually use the shaders we'll need to assign them to a specific pipeline stage through `VkPipelineShaderStageCreateInfo` structures as part of the actual pipeline creation process. We'll start by filling in the structure for the vertex shader, again in the `createGraphicsPipeline` function. ```c++ -VkPipelineShaderStageCreateInfo vertShaderStageInfo = {}; +VkPipelineShaderStageCreateInfo vertShaderStageInfo{}; vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; ``` @@ -435,7 +427,7 @@ vertShaderStageInfo.pName = "main"; ``` The next two members specify the shader module containing the code, and the -function to invoke. That means that it's possible to combine multiple fragment +function to invoke, known as the *entrypoint*. That means that it's possible to combine multiple fragment shaders into a single shader module and use different entry points to differentiate between their behaviors. In this case we'll stick to the standard `main`, however. @@ -453,7 +445,7 @@ does automatically. Modifying the structure to suit the fragment shader is easy: ```c++ -VkPipelineShaderStageCreateInfo fragShaderStageInfo = {}; +VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; fragShaderStageInfo.module = fragShaderModule; @@ -470,6 +462,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/en/03_Drawing_a_triangle/02_Graphics_pipeline_basics/02_Fixed_functions.md similarity index 75% rename from 03_Drawing_a_triangle/02_Graphics_pipeline_basics/02_Fixed_functions.md rename to en/03_Drawing_a_triangle/02_Graphics_pipeline_basics/02_Fixed_functions.md index f5df4293..c3c3ad46 100644 --- a/03_Drawing_a_triangle/02_Graphics_pipeline_basics/02_Fixed_functions.md +++ b/en/03_Drawing_a_triangle/02_Graphics_pipeline_basics/02_Fixed_functions.md @@ -1,7 +1,33 @@ + The older graphics APIs provided default state for most of the stages of the -graphics pipeline. In Vulkan you have to be explicit about everything, from -viewport size to color blending function. In this chapter we'll fill in all of -the structures to configure these fixed-function operations. +graphics pipeline. In Vulkan you have to be explicit about most pipeline states as +it'll be baked into an immutable pipeline state object. In this chapter we'll fill +in all of the structures to configure these fixed-function operations. + +## Dynamic state + +While *most* of the pipeline state needs to be baked into the pipeline state, +a limited amount of the state *can* actually be changed without recreating the +pipeline at draw time. Examples are the size of the viewport, line width +and blend constants. If you want to use dynamic state and keep these properties out, +then you'll have to fill in a `VkPipelineDynamicStateCreateInfo` structure like this: + +```c++ +std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSROR +}; + +VkPipelineDynamicStateCreateInfo dynamicState{}; +dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; +dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); +dynamicState.pDynamicStates = dynamicStates.data(); +``` + +This will cause the configuration of these values to be ignored and you will be +able (and required) to specify the data at drawing time. This results in a more flexible +setup and is very common for things like viewport and scissor state, which would +result in a more complex setup when being baked into the pipeline state. ## Vertex input @@ -19,7 +45,7 @@ fill in this structure to specify that there is no vertex data to load for now. We'll get back to it in the vertex buffer chapter. ```c++ -VkPipelineVertexInputStateCreateInfo vertexInputInfo = {}; +VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; vertexInputInfo.vertexBindingDescriptionCount = 0; vertexInputInfo.pVertexBindingDescriptions = nullptr; // Optional @@ -41,12 +67,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. @@ -59,7 +85,7 @@ We intend to draw triangles throughout this tutorial, so we'll stick to the following data for the structure: ```c++ -VkPipelineInputAssemblyStateCreateInfo inputAssembly = {}; +VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; inputAssembly.primitiveRestartEnable = VK_FALSE; @@ -72,7 +98,7 @@ will be rendered to. This will almost always be `(0, 0)` to `(width, height)` and in this tutorial that will also be the case. ```c++ -VkViewport viewport = {}; +VkViewport viewport{}; viewport.x = 0.0f; viewport.y = 0.0f; viewport.width = (float) swapChainExtent.width; @@ -100,23 +126,48 @@ viewport. ![](/images/viewports_scissors.png) -In this tutorial we simply want to draw to the entire framebuffer, so we'll -specify a scissor rectangle that covers it entirely: +So if we wanted to draw to the entire framebuffer, we would specify a scissor rectangle that covers it entirely: ```c++ -VkRect2D scissor = {}; +VkRect2D scissor{}; scissor.offset = {0, 0}; scissor.extent = swapChainExtent; ``` -Now this viewport and scissor rectangle need to be combined into a viewport -state using the `VkPipelineViewportStateCreateInfo` struct. It is possible to -use multiple viewports and scissor rectangles on some graphics cards, so its -members reference an array of them. Using multiple requires enabling a GPU -feature (see logical device creation). +Viewport(s) and scissor rectangle(s) can either be specified as a static part of the pipeline or as a [dynamic state](#dynamic-state) set in the command buffer. While the former is more in line with the other states it's often convenient to make viewport and scissor state dynamic as it gives you a lot more flexibility. This is very common and all implementations can handle this dynamic state without a performance penalty. + +When opting for dynamic viewport(s) and scissor rectangle(s) you need to enable the respective dynamic states for the pipeline: + +```c++ +std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR +}; + +VkPipelineDynamicStateCreateInfo dynamicState{}; +dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; +dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); +dynamicState.pDynamicStates = dynamicStates.data(); +``` + +And then you only need to specify their count at pipeline creation time: + +```c++ +VkPipelineViewportStateCreateInfo viewportState{}; +viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; +viewportState.viewportCount = 1; +viewportState.scissorCount = 1; +``` + +The actual viewport(s) and scissor rectangle(s) will then later be set up at drawing time. + +With dynamic state it's even possible to specify different viewports and or scissor rectangles within a single command buffer. + +Without dynamic state, the viewport and scissor rectangle need to be set in the pipeline using the `VkPipelineViewportStateCreateInfo` struct. This makes the viewport and scissor rectangle for this pipeline immutable. +Any changes required to these values would require a new pipeline to created with the new values. ```c++ -VkPipelineViewportStateCreateInfo viewportState = {}; +VkPipelineViewportStateCreateInfo viewportState{}; viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; viewportState.viewportCount = 1; viewportState.pViewports = &viewport; @@ -124,6 +175,8 @@ viewportState.scissorCount = 1; viewportState.pScissors = &scissor; ``` +Independent of how you set them, it's is possible to use multiple viewports and scissor rectangles on some graphics cards, so the structure members reference an array of them. Using multiple requires enabling a GPU feature (see logical device creation). + ## Rasterizer The rasterizer takes the geometry that is shaped by the vertices from the vertex @@ -135,7 +188,7 @@ just the edges (wireframe rendering). All this is configured using the `VkPipelineRasterizationStateCreateInfo` structure. ```c++ -VkPipelineRasterizationStateCreateInfo rasterizer = {}; +VkPipelineRasterizationStateCreateInfo rasterizer{}; rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; rasterizer.depthClampEnable = VK_FALSE; ``` @@ -208,18 +261,17 @@ significantly less expensive than simply rendering to a higher resolution and then downscaling. Enabling it requires enabling a GPU feature. ```c++ -VkPipelineMultisampleStateCreateInfo multisampling = {}; +VkPipelineMultisampleStateCreateInfo multisampling{}; 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 @@ -244,7 +296,7 @@ contains the *global* color blending settings. In our case we only have one framebuffer: ```c++ -VkPipelineColorBlendAttachmentState colorBlendAttachment = {}; +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; colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; // Optional @@ -301,10 +353,10 @@ 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 = {}; +VkPipelineColorBlendStateCreateInfo colorBlending{}; colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; colorBlending.logicOpEnable = VK_FALSE; colorBlending.logicOp = VK_LOGIC_OP_COPY; // Optional @@ -325,30 +377,6 @@ determine which channels in the framebuffer will actually be affected. It is also possible to disable both modes, as we've done here, in which case the fragment colors will be written to the framebuffer unmodified. -## Dynamic state - -A limited amount of the state that we've specified in the previous structs *can* -actually be changed without recreating the pipeline. Examples are the size of -the viewport, line width and blend constants. If you want to do that, then -you'll have to fill in a `VkPipelineDynamicStateCreateInfo` structure like this: - -```c++ -VkDynamicState dynamicStates[] = { - VK_DYNAMIC_STATE_VIEWPORT, - VK_DYNAMIC_STATE_LINE_WIDTH -}; - -VkPipelineDynamicStateCreateInfo dynamicState = {}; -dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; -dynamicState.dynamicStateCount = 2; -dynamicState.pDynamicStates = dynamicStates; -``` - -This will cause the configuration of these values to be ignored and you will be -required to specify the data at drawing time. We'll get back to this in a future -chapter. This struct can be substituted by a `nullptr` later on if you don't -have any dynamic state. - ## Pipeline layout You can use `uniform` values in shaders, which are globals similar to dynamic @@ -365,27 +393,35 @@ 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: ```c++ -VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; +VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; 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 @@ -396,8 +432,8 @@ running into unexpected behavior because the default state of certain components 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). +graphics pipeline and that is a [render pass](!en/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/en/03_Drawing_a_triangle/02_Graphics_pipeline_basics/03_Render_passes.md similarity index 87% rename from 03_Drawing_a_triangle/02_Graphics_pipeline_basics/03_Render_passes.md rename to en/03_Drawing_a_triangle/02_Graphics_pipeline_basics/03_Render_passes.md index 91506657..a635d32f 100644 --- a/03_Drawing_a_triangle/02_Graphics_pipeline_basics/03_Render_passes.md +++ b/en/03_Drawing_a_triangle/02_Graphics_pipeline_basics/03_Render_passes.md @@ -11,7 +11,7 @@ which we'll create a new `createRenderPass` function. Call this function from ```c++ void initVulkan() { createInstance(); - setupDebugCallback(); + setupDebugMessenger(); createSurface(); pickPhysicalDevice(); createLogicalDevice(); @@ -35,14 +35,15 @@ of the images from the swap chain. ```c++ void createRenderPass() { - VkAttachmentDescription colorAttachment = {}; + VkAttachmentDescription colorAttachment{}; colorAttachment.format = swapChainImageFormat; colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; } ``` -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; @@ -125,7 +126,7 @@ using the structure in the previous sections. These references are themselves `VkAttachmentReference` structs that look like this: ```c++ -VkAttachmentReference colorAttachmentRef = {}; +VkAttachmentReference colorAttachmentRef{}; colorAttachmentRef.attachment = 0; colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; ``` @@ -142,7 +143,7 @@ us the best performance, as its name implies. The subpass is described using a `VkSubpassDescription` structure: ```c++ -VkSubpassDescription subpass = {}; +VkSubpassDescription subpass{}; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; ``` @@ -162,7 +163,7 @@ The following other types of attachments can be referenced by a subpass: * `pInputAttachments`: Attachments that are read from a shader * `pResolveAttachments`: Attachments used for multisampling color attachments -* `pDepthStencilAttachment`: Attachments for depth and stencil data +* `pDepthStencilAttachment`: Attachment for depth and stencil data * `pPreserveAttachments`: Attachments that are not used by this subpass, but for which the data must be preserved @@ -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 @@ -182,21 +184,32 @@ The `VkAttachmentReference` objects reference attachments using the indices of this array. ```c++ -VkRenderPassCreateInfo renderPassInfo = {}; +VkRenderPassCreateInfo renderPassInfo{}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; renderPassInfo.attachmentCount = 1; 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/en/03_Drawing_a_triangle/02_Graphics_pipeline_basics/04_Conclusion.md similarity index 77% rename from 03_Drawing_a_triangle/02_Graphics_pipeline_basics/04_Conclusion.md rename to en/03_Drawing_a_triangle/02_Graphics_pipeline_basics/04_Conclusion.md index 6ff4db9c..4a16585e 100644 --- a/03_Drawing_a_triangle/02_Graphics_pipeline_basics/04_Conclusion.md +++ b/en/03_Drawing_a_triangle/02_Graphics_pipeline_basics/04_Conclusion.md @@ -13,10 +13,11 @@ 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 = {}; +VkGraphicsPipelineCreateInfo pipelineInfo{}; pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; pipelineInfo.stageCount = 2; pipelineInfo.pStages = shaderStages; @@ -32,7 +33,7 @@ pipelineInfo.pRasterizationState = &rasterizer; pipelineInfo.pMultisampleState = &multisampling; pipelineInfo.pDepthStencilState = nullptr; // Optional pipelineInfo.pColorBlendState = &colorBlending; -pipelineInfo.pDynamicState = nullptr; // Optional +pipelineInfo.pDynamicState = &dynamicState; ``` Then we reference all of the structures describing the fixed-function stage. @@ -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.3-extensions/html/chap8.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/en/03_Drawing_a_triangle/03_Drawing/00_Framebuffers.md similarity index 78% rename from 03_Drawing_a_triangle/03_Drawing/00_Framebuffers.md rename to en/03_Drawing_a_triangle/03_Drawing/00_Framebuffers.md index cbf65363..bf7f84a7 100644 --- a/03_Drawing_a_triangle/03_Drawing/00_Framebuffers.md +++ b/en/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` @@ -23,7 +23,7 @@ that is called from `initVulkan` right after creating the graphics pipeline: ```c++ void initVulkan() { createInstance(); - setupDebugCallback(); + setupDebugMessenger(); createSurface(); pickPhysicalDevice(); createLogicalDevice(); @@ -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()); } ``` @@ -57,7 +57,7 @@ for (size_t i = 0; i < swapChainImageViews.size(); i++) { swapChainImageViews[i] }; - VkFramebufferCreateInfo framebufferInfo = {}; + VkFramebufferCreateInfo framebufferInfo{}; framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; framebufferInfo.renderPass = renderPass; framebufferInfo.attachmentCount = 1; @@ -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/en/03_Drawing_a_triangle/03_Drawing/01_Command_buffers.md similarity index 60% rename from 03_Drawing_a_triangle/03_Drawing/01_Command_buffers.md rename to en/03_Drawing_a_triangle/03_Drawing/01_Command_buffers.md index 740495b2..61a40b4f 100644 --- a/03_Drawing_a_triangle/03_Drawing/01_Command_buffers.md +++ b/en/03_Drawing_a_triangle/03_Drawing/01_Command_buffers.md @@ -1,9 +1,10 @@ Commands in Vulkan, like drawing operations and memory transfers, are not executed directly using function calls. You have to record all of the operations -you want to perform in command buffer objects. The advantage of this is that all -of the hard work of setting up the drawing commands can be done in advance and -in multiple threads. After that, you just have to tell Vulkan to execute the -commands in the main loop. +you want to perform in command buffer objects. The advantage of this is that when +we are ready to tell the Vulkan what we want to do, all of the commands are +submitted together and Vulkan can more efficiently process the commands since all +of them are available together. In addition, this allows command recording to +happen in multiple threads if so desired. ## Command pools @@ -12,7 +13,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` @@ -21,7 +22,7 @@ after the framebuffers were created. ```c++ void initVulkan() { createInstance(); - setupDebugCallback(); + setupDebugMessenger(); createSurface(); pickPhysicalDevice(); createLogicalDevice(); @@ -45,18 +46,12 @@ Command pool creation only takes two parameters: ```c++ QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); -VkCommandPoolCreateInfo poolInfo = {}; +VkCommandPoolCreateInfo poolInfo{}; poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; -poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily; -poolInfo.flags = 0; // Optional +poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; +poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); ``` -Command buffers are executed by submitting them on one of the device queues, -like the graphics and presentation queues we retrieved. Each command pool can -only allocate command buffers that are submitted on a single type of queue. -We're going to record commands for drawing, which is why we've chosen the -graphics queue family. - There are two possible flags for command pools: * `VK_COMMAND_POOL_CREATE_TRANSIENT_BIT`: Hint that command buffers are @@ -64,39 +59,55 @@ rerecorded with new commands very often (may change memory allocation behavior) * `VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT`: Allow command buffers to be rerecorded individually, without this flag they all have to be reset together -We will only record the command buffers at the beginning of the program and then -execute them many times in the main loop, so we're not going to use either of -these flags. +We will be recording a command buffer every frame, so we want to be able to +reset and rerecord over it. Thus, we need to set the +`VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT` flag bit for our command pool. + +Command buffers are executed by submitting them on one of the device queues, +like the graphics and presentation queues we retrieved. Each command pool can +only allocate command buffers that are submitted on a single type of queue. +We're going to record commands for drawing, which is why we've chosen the +graphics queue family. + ```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 -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`. +We can now start allocating command buffers. + +Create a `VkCommandBuffer` object as a class member. Command buffers +will be automatically freed when their command pool is destroyed, so we don't +need explicit cleanup. ```c++ -std::vector commandBuffers; +VkCommandBuffer commandBuffer; ``` -We'll now start working on a `createCommandBuffers` function that allocates and -records the commands for each swap chain image. +We'll now start working on a `createCommandBuffer` function to allocate a single +command buffer from the command pool. ```c++ void initVulkan() { createInstance(); - setupDebugCallback(); + setupDebugMessenger(); createSurface(); pickPhysicalDevice(); createLogicalDevice(); @@ -106,32 +117,28 @@ void initVulkan() { createGraphicsPipeline(); createFramebuffers(); createCommandPool(); - createCommandBuffers(); + createCommandBuffer(); } ... -void createCommandBuffers() { - commandBuffers.resize(swapChainFramebuffers.size()); +void createCommandBuffer() { + } ``` -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: ```c++ -VkCommandBufferAllocateInfo allocInfo = {}; +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(); +allocInfo.commandBufferCount = 1; -if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) { +if (vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer) != VK_SUCCESS) { throw std::runtime_error("failed to allocate command buffers!"); } ``` @@ -148,20 +155,34 @@ We won't make use of the secondary command buffer functionality here, but you can imagine that it's helpful to reuse common operations from primary command buffers. -## Starting command buffer recording +Since we are only allocating one command buffer, the `commandBufferCount` parameter +is just one. + +## Command buffer recording + +We'll now start working on the `recordCommandBuffer` function that writes the +commands we want to execute into a command buffer. The `VkCommandBuffer` used +will be passed in as a parameter, as well as the index of the current swapchain +image we want to write to. + +```c++ +void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + +} +``` -We begin recording a command buffer by calling `vkBeginCommandBuffer` with a -small `VkCommandBufferBeginInfo` structure as argument that specifies some -details about the usage of this specific command buffer. +We always begin recording a command buffer by calling `vkBeginCommandBuffer` +with a small `VkCommandBufferBeginInfo` structure as argument that specifies +some details about the usage of this specific command buffer. ```c++ -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; - beginInfo.pInheritanceInfo = nullptr; // Optional +VkCommandBufferBeginInfo beginInfo{}; +beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; +beginInfo.flags = 0; // Optional +beginInfo.pInheritanceInfo = nullptr; // Optional - vkBeginCommandBuffer(commandBuffers[i], &beginInfo); +if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("failed to begin recording command buffer!"); } ``` @@ -175,10 +196,10 @@ command buffer that will be entirely within a single render pass. * `VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT`: The command buffer can be resubmitted while it is also already pending execution. -We have used the last flag because we may already be scheduling the drawing -commands for the next frame while the last frame is not finished yet. The -`pInheritanceInfo` parameter is only relevant for secondary command buffers. It -specifies which state to inherit from the calling primary command buffers. +None of these flags are applicable for us right now. + +The `pInheritanceInfo` parameter is only relevant for secondary command buffers. +It specifies which state to inherit from the calling primary command buffers. If the command buffer was already recorded once, then a call to `vkBeginCommandBuffer` will implicitly reset it. It's not possible to append @@ -191,15 +212,17 @@ render pass is configured using some parameters in a `VkRenderPassBeginInfo` struct. ```c++ -VkRenderPassBeginInfo renderPassInfo = {}; +VkRenderPassBeginInfo renderPassInfo{}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; renderPassInfo.renderPass = renderPass; -renderPassInfo.framebuffer = swapChainFramebuffers[i]; +renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex]; ``` The first parameters are the render pass itself and the attachments to bind. We -created a framebuffer for each swap chain image that specifies it as color -attachment. +created a framebuffer for each swap chain image where it is specified as a color +attachment. Thus we need to bind the framebuffer for the swapchain image we want +to draw to. Using the imageIndex parameter which was passed in, we can pick the +right framebuffer for the current swapchain image. ```c++ renderPassInfo.renderArea.offset = {0, 0}; @@ -212,7 +235,7 @@ region will have undefined values. It should match the size of the attachments for best performance. ```c++ -VkClearValue clearColor = {0.0f, 0.0f, 0.0f, 1.0f}; +VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; renderPassInfo.clearValueCount = 1; renderPassInfo.pClearValues = &clearColor; ``` @@ -222,7 +245,7 @@ The last two parameters define the clear values to use for attachment. I've defined the clear color to simply be black with 100% opacity. ```c++ -vkCmdBeginRenderPass(commandBuffers[i], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); +vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); ``` The render pass can now begin. All of the functions that record commands can be @@ -248,16 +271,37 @@ option. We can now bind the graphics pipeline: ```c++ -vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); +vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); ``` The second parameter specifies if the pipeline object is a graphics or compute pipeline. We've now told Vulkan which operations to execute in the graphics -pipeline and which attachment to use in the fragment shader, so all that remains -is telling it to draw the triangle: +pipeline and which attachment to use in the fragment shader. + +As noted in the [fixed functions chapter](../02_Graphics_pipeline_basics/02_Fixed_functions.md#dynamic-state), +we did specify viewport and scissor state for this pipeline to be dynamic. +So we need to set them in the command buffer before issuing our draw command: ```c++ -vkCmdDraw(commandBuffers[i], 3, 1, 0, 0); +VkViewport viewport{}; +viewport.x = 0.0f; +viewport.y = 0.0f; +viewport.width = static_cast(swapChainExtent.width); +viewport.height = static_cast(swapChainExtent.height); +viewport.minDepth = 0.0f; +viewport.maxDepth = 1.0f; +vkCmdSetViewport(commandBuffer, 0, 1, &viewport); + +VkRect2D scissor{}; +scissor.offset = {0, 0}; +scissor.extent = swapChainExtent; +vkCmdSetScissor(commandBuffer, 0, 1, &scissor); +``` + +Now we are ready to issue the draw command for the triangle: + +```c++ +vkCmdDraw(commandBuffer, 3, 1, 0, 0); ``` The actual `vkCmdDraw` function is a bit anticlimactic, but it's so simple @@ -278,21 +322,23 @@ value of `gl_InstanceIndex`. The render pass can now be ended: ```c++ -vkCmdEndRenderPass(commandBuffers[i]); +vkCmdEndRenderPass(commandBuffer); ``` And we've finished recording the command buffer: ```c++ -if (vkEndCommandBuffer(commandBuffers[i]) != VK_SUCCESS) { +if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { throw std::runtime_error("failed to record command buffer!"); } ``` + + 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 +an image from the swap chain, record and execute a command buffer, then 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/en/03_Drawing_a_triangle/03_Drawing/02_Rendering_and_presentation.md b/en/03_Drawing_a_triangle/03_Drawing/02_Rendering_and_presentation.md new file mode 100644 index 00000000..e6197868 --- /dev/null +++ b/en/03_Drawing_a_triangle/03_Drawing/02_Rendering_and_presentation.md @@ -0,0 +1,575 @@ + +This is the chapter where everything is going to come together. We're going to +write the `drawFrame` function that will be called from the main loop to put the +triangle on the screen. Let's start by creating the function and call it from +`mainLoop`: + +```c++ +void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + drawFrame(); + } +} + +... + +void drawFrame() { + +} +``` + +## Outline of a frame + +At a high level, rendering a frame in Vulkan consists of a common set of steps: + +* Wait for the previous frame to finish +* Acquire an image from the swap chain +* Record a command buffer which draws the scene onto that image +* Submit the recorded command buffer +* Present the swap chain image + +While we will expand the drawing function in later chapters, for now this is the +core of our render loop. + + + +## Synchronization + + + +A core design philosophy in Vulkan is that synchronization of execution on +the GPU is explicit. The order of operations is up to us to define using various +synchronization primitives which tell the driver the order we want things to run +in. This means that many Vulkan API calls which start executing work on the GPU +are asynchronous, the functions will return before the operation has finished. + +In this chapter there are a number of events that we need to order explicitly +because they happen on the GPU, such as: + +* Acquire an image from the swap chain +* Execute commands that draw onto the acquired image +* Present that image to the screen for presentation, returning it to the swapchain + +Each of these events is set in motion using a single function call, but are all +executed asynchronously. The function calls will return before the operations +are actually finished and the order of execution is also undefined. That is +unfortunate, because each of the operations depends on the previous one +finishing. Thus we need to explore which primitives we can use to achieve +the desired ordering. + +### Semaphores + +A semaphore is used to add order between queue operations. Queue operations +refer to the work we submit to a queue, either in a command buffer or from +within a function as we will see later. Examples of queues are the graphics +queue and the presentation queue. Semaphores are used both to order work inside +the same queue and between different queues. + +There happens to be two kinds of semaphores in Vulkan, binary and timeline. +Because only binary semaphores will be used in this tutorial, we will not +discuss timeline semaphores. Further mention of the term semaphore exclusively +refers to binary semaphores. + +A semaphore is either unsignaled or signaled. It begins life as unsignaled. The +way we use a semaphore to order queue operations is by providing the same +semaphore as a 'signal' semaphore in one queue operation and as a 'wait' +semaphore in another queue operation. For example, lets say we have semaphore S +and queue operations A and B that we want to execute in order. What we tell +Vulkan is that operation A will 'signal' semaphore S when it finishes executing, +and operation B will 'wait' on semaphore S before it begins executing. When +operation A finishes, semaphore S will be signaled, while operation B wont +start until S is signaled. After operation B begins executing, semaphore S +is automatically reset back to being unsignaled, allowing it to be used again. + +Pseudo-code of what was just described: +``` +VkCommandBuffer A, B = ... // record command buffers +VkSemaphore S = ... // create a semaphore + +// enqueue A, signal S when done - starts executing immediately +vkQueueSubmit(work: A, signal: S, wait: None) + +// enqueue B, wait on S to start +vkQueueSubmit(work: B, signal: None, wait: S) +``` + +Note that in this code snippet, both calls to `vkQueueSubmit()` return +immediately - the waiting only happens on the GPU. The CPU continues running +without blocking. To make the CPU wait, we need a different synchronization +primitive, which we will now describe. + +### Fences + +A fence has a similar purpose, in that it is used to synchronize execution, but +it is for ordering the execution on the CPU, otherwise known as the host. +Simply put, if the host needs to know when the GPU has finished something, we +use a fence. + +Similar to semaphores, fences are either in a signaled or unsignaled state. +Whenever we submit work to execute, we can attach a fence to that work. When +the work is finished, the fence will be signaled. Then we can make the host +wait for the fence to be signaled, guaranteeing that the work has finished +before the host continues. + +A concrete example is taking a screenshot. Say we have already done the +necessary work on the GPU. Now need to transfer the image from the GPU over +to the host and then save the memory to a file. We have command buffer A which +executes the transfer and fence F. We submit command buffer A with fence F, +then immediately tell the host to wait for F to signal. This causes the host to +block until command buffer A finishes execution. Thus we are safe to let the +host save the file to disk, as the memory transfer has completed. + +Pseudo-code for what was described: +``` +VkCommandBuffer A = ... // record command buffer with the transfer +VkFence F = ... // create the fence + +// enqueue A, start work immediately, signal F when done +vkQueueSubmit(work: A, fence: F) + +vkWaitForFence(F) // blocks execution until A has finished executing + +save_screenshot_to_disk() // can't run until the transfer has finished +``` + +Unlike the semaphore example, this example *does* block host execution. This +means the host won't do anything except wait until execution has finished. For +this case, we had to make sure the transfer was complete before we could save +the screenshot to disk. + +In general, it is preferable to not block the host unless necessary. We want to +feed the GPU and the host with useful work to do. Waiting on fences to signal +is not useful work. Thus we prefer semaphores, or other synchronization +primitives not yet covered, to synchronize our work. + +Fences must be reset manually to put them back into the unsignaled state. This +is because fences are used to control the execution of the host, and so the +host gets to decide when to reset the fence. Contrast this to semaphores which +are used to order work on the GPU without the host being involved. + +In summary, semaphores are used to specify the execution order of operations on +the GPU while fences are used to keep the CPU and GPU in sync with each-other. + +### What to choose? + +We have two synchronization primitives to use and conveniently two places to +apply synchronization: Swapchain operations and waiting for the previous frame +to finish. We want to use semaphores for swapchain operations because they +happen on the GPU, thus we don't want to make the host wait around if we can +help it. For waiting on the previous frame to finish, we want to use fences +for the opposite reason, because we need the host to wait. This is so we don't +draw more than one frame at a time. Because we re-record the command buffer +every frame, we cannot record the next frame's work to the command buffer +until the current frame has finished executing, as we don't want to overwrite +the current contents of the command buffer while the GPU is using it. + +## Creating the synchronization objects + +We'll need one semaphore to signal that an image has been acquired from the +swapchain and is ready for rendering, another one to signal that rendering has +finished and presentation can happen, and a fence to make sure only one frame +is rendering at a time. + +Create three class members to store these semaphore objects and fence object: + +```c++ +VkSemaphore imageAvailableSemaphore; +VkSemaphore renderFinishedSemaphore; +VkFence inFlightFence; +``` + +To create the semaphores, we'll add the last `create` function for this part of +the tutorial: `createSyncObjects`: + +```c++ +void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createGraphicsPipeline(); + createFramebuffers(); + createCommandPool(); + createCommandBuffer(); + createSyncObjects(); +} + +... + +void createSyncObjects() { + +} +``` + +Creating semaphores requires filling in the `VkSemaphoreCreateInfo`, but in the +current version of the API it doesn't actually have any required fields besides +`sType`: + +```c++ +void createSyncObjects() { + VkSemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; +} +``` + +Future versions of the Vulkan API or extensions may add functionality for the +`flags` and `pNext` parameters like it does for the other structures. + +Creating a fence requires filling in the `VkFenceCreateInfo`: + +```c++ +VkFenceCreateInfo fenceInfo{}; +fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; +``` + +Creating the semaphores and fence follows the familiar pattern with +`vkCreateSemaphore` & `vkCreateFence`: + +```c++ +if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphore) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphore) != VK_SUCCESS || + vkCreateFence(device, &fenceInfo, nullptr, &inFlightFence) != VK_SUCCESS) { + throw std::runtime_error("failed to create semaphores!"); +} +``` + +The semaphores and fence 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, imageAvailableSemaphore, nullptr); + vkDestroySemaphore(device, renderFinishedSemaphore, nullptr); + vkDestroyFence(device, inFlightFence, nullptr); +``` + +Onto the main drawing function! + +## Waiting for the previous frame + +At the start of the frame, we want to wait until the previous frame has +finished, so that the command buffer and semaphores are available to use. To do +that, we call `vkWaitForFences`: + +```c++ +void drawFrame() { + vkWaitForFences(device, 1, &inFlightFence, VK_TRUE, UINT64_MAX); +} +``` + +The `vkWaitForFences` function takes an array of fences and waits on the host +for either any or all of the fences 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 doesn't matter. This function also has a timeout +parameter that we set to the maximum value of a 64 bit unsigned integer, +`UINT64_MAX`, which effectively disables the timeout. + +After waiting, we need to manually reset the fence to the unsignaled state with +the `vkResetFences` call: +```c++ + vkResetFences(device, 1, &inFlightFence); +``` + +Before we can proceed, there is a slight hiccup in our design. On the first +frame we call `drawFrame()`, which immediately waits on `inFlightFence` to +be signaled. `inFlightFence` is only signaled after a frame has finished +rendering, yet since this is the first frame, there are no previous frames in +which to signal the fence! Thus `vkWaitForFences()` blocks indefinitely, +waiting on something which will never happen. + +Of the many solutions to this dilemma, there is a clever workaround built into +the API. Create the fence in the signaled state, so that the first call to +`vkWaitForFences()` returns immediately since the fence is already signaled. + +To do this, we add the `VK_FENCE_CREATE_SIGNALED_BIT` flag to the `VkFenceCreateInfo`: + +```c++ +void createSyncObjects() { + ... + + VkFenceCreateInfo fenceInfo{}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; + + ... +} +``` + +## Acquiring an image from the swap chain + +The next thing we need to do in the `drawFrame` function 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: + +```c++ +void drawFrame() { + uint32_t imageIndex; + vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex); +} +``` + +The first two parameters of `vkAcquireNextImageKHR` are the logical device and +the swap chain from which we wish to acquire an image. The third parameter +specifies a timeout in nanoseconds for an image to become available. Using the +maximum value of a 64 bit unsigned integer means we effectively disable the +timeout. + +The next two parameters specify synchronization objects that are to be signaled +when the presentation engine is finished using the image. That's the point in +time where we can start drawing to it. It is possible to specify a semaphore, +fence or both. We're going to use our `imageAvailableSemaphore` for that purpose +here. + +The last parameter specifies a variable to output the index of the swap chain +image that has become available. The index refers to the `VkImage` in our +`swapChainImages` array. We're going to use that index to pick the `VkFrameBuffer`. + +## Recording the command buffer + +With the imageIndex specifying the swap chain image to use in hand, we can now +record the command buffer. First, we call `vkResetCommandBuffer` on the command +buffer to make sure it is able to be recorded. + +```c++ +vkResetCommandBuffer(commandBuffer, 0); +``` + +The second parameter of `vkResetCommandBuffer` is a `VkCommandBufferResetFlagBits` +flag. Since we don't want to do anything special, we leave it as 0. + +Now call the function `recordCommandBuffer` to record the commands we want. + +```c++ +recordCommandBuffer(commandBuffer, imageIndex); +``` + +With a fully recorded command buffer, we can now submit it. + +## Submitting the command buffer + +Queue submission and synchronization is configured through parameters in the +`VkSubmitInfo` structure. + +```c++ +VkSubmitInfo submitInfo{}; +submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + +VkSemaphore waitSemaphores[] = {imageAvailableSemaphore}; +VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; +submitInfo.waitSemaphoreCount = 1; +submitInfo.pWaitSemaphores = waitSemaphores; +submitInfo.pWaitDstStageMask = waitStages; +``` + +The first three parameters specify which semaphores to wait on before execution +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 yet available. Each entry in the `waitStages` +array corresponds to the semaphore with the same index in `pWaitSemaphores`. + +```c++ +submitInfo.commandBufferCount = 1; +submitInfo.pCommandBuffers = &commandBuffer; +``` + +The next two parameters specify which command buffers to actually submit for +execution. We simply submit the single command buffer we have. + +```c++ +VkSemaphore signalSemaphores[] = {renderFinishedSemaphore}; +submitInfo.signalSemaphoreCount = 1; +submitInfo.pSignalSemaphores = signalSemaphores; +``` + +The `signalSemaphoreCount` and `pSignalSemaphores` parameters specify which +semaphores to signal once the command buffer(s) have finished execution. In our +case we're using the `renderFinishedSemaphore` for that purpose. + +```c++ +if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFence) != VK_SUCCESS) { + throw std::runtime_error("failed to submit draw command buffer!"); +} +``` + +We can now submit the command buffer to the graphics queue using +`vkQueueSubmit`. The function takes an array of `VkSubmitInfo` structures as +argument for efficiency when the workload is much larger. The last parameter +references an optional fence that will be signaled when the command buffers +finish execution. This allows us to know when it is safe for the command +buffer to be reused, thus we want to give it `inFlightFence`. Now on the next +frame, the CPU will wait for this command buffer to finish executing before it +records new commands into it. + +## Subpass dependencies + +Remember that the subpasses in a render pass automatically take care of image +layout transitions. These transitions are controlled by *subpass dependencies*, +which specify memory and execution dependencies between subpasses. We have only +a single subpass right now, but the operations right before and right after this +subpass also count as implicit "subpasses". + +There are two built-in dependencies that take care of the transition at the +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_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: + +```c++ +VkSubpassDependency dependency{}; +dependency.srcSubpass = VK_SUBPASS_EXTERNAL; +dependency.dstSubpass = 0; +``` + +The first two fields specify the indices of the dependency and the dependent +subpass. The special value `VK_SUBPASS_EXTERNAL` refers to the implicit subpass +before or after the render pass depending on whether it is specified in +`srcSubpass` or `dstSubpass`. The index `0` refers to our subpass, which is the +first and only one. The `dstSubpass` must always be higher than `srcSubpass` to +prevent cycles in the dependency graph (unless one of the subpasses is +`VK_SUBPASS_EXTERNAL`). + +```c++ +dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; +dependency.srcAccessMask = 0; +``` + +The next two fields specify the operations to wait on and the stages in which +these operations occur. We need to wait for the swap chain to finish reading +from the image before we can access it. This can be accomplished by waiting on +the color attachment output stage itself. + +```c++ +dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; +dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; +``` + +The operations that should wait on this are in the color attachment stage and +involve the writing of the color attachment. These settings will +prevent the transition from happening until it's actually necessary (and +allowed): when we want to start writing colors to it. + +```c++ +renderPassInfo.dependencyCount = 1; +renderPassInfo.pDependencies = &dependency; +``` + +The `VkRenderPassCreateInfo` struct has two fields to specify an array of +dependencies. + +## Presentation + +The last step of drawing a frame is submitting the result back to the swap chain +to have it eventually show up on the screen. Presentation is configured through +a `VkPresentInfoKHR` structure at the end of the `drawFrame` function. + +```c++ +VkPresentInfoKHR presentInfo{}; +presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + +presentInfo.waitSemaphoreCount = 1; +presentInfo.pWaitSemaphores = signalSemaphores; +``` + +The first two parameters specify which semaphores to wait on before presentation +can happen, just like `VkSubmitInfo`. Since we want to wait on the command buffer +to finish execution, thus our triangle being drawn, we take the semaphores +which will be signalled and wait on them, thus we use `signalSemaphores`. + + +```c++ +VkSwapchainKHR swapChains[] = {swapChain}; +presentInfo.swapchainCount = 1; +presentInfo.pSwapchains = swapChains; +presentInfo.pImageIndices = &imageIndex; +``` + +The next two parameters specify the swap chains to present images to and the +index of the image for each swap chain. This will almost always be a single one. + +```c++ +presentInfo.pResults = nullptr; // Optional +``` + +There is one last optional parameter called `pResults`. It allows you to specify +an array of `VkResult` values to check for every individual swap chain if +presentation was successful. It's not necessary if you're only using a single +swap chain, because you can simply use the return value of the present function. + +```c++ +vkQueuePresentKHR(presentQueue, &presentInfo); +``` + +The `vkQueuePresentKHR` function submits the request to present an image to the +swap chain. We'll add error handling for both `vkAcquireNextImageKHR` and +`vkQueuePresentKHR` in the next chapter, because their failure does not +necessarily mean that the program should terminate, unlike the functions we've +seen so far. + +If you did everything correctly up to this point, then you should now see +something resembling the following when you run your program: + +![](/images/triangle.png) + +>This colored triangle may look a bit different from the one you're used to seeing in graphics tutorials. That's because this tutorial lets the shader interpolate in linear color space and converts to sRGB color space afterwards. See [this blog post](https://medium.com/@heypete/hello-triangle-meet-swift-and-wide-color-6f9e246616d9) for a discussion of the difference. + +Yay! Unfortunately, you'll see that when validation layers are enabled, the +program crashes as soon as you close it. The messages printed to the terminal +from `debugCallback` tell us why: + +![](/images/semaphore_in_use.png) + +Remember that all of the operations in `drawFrame` are asynchronous. That means +that when we exit the loop in `mainLoop`, drawing and presentation operations +may still be going on. Cleaning up resources while that is happening is a bad +idea. + +To fix that problem, we should wait for the logical device to finish operations +before exiting `mainLoop` and destroying the window: + +```c++ +void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + drawFrame(); + } + + vkDeviceWaitIdle(device); +} +``` + +You can also wait for operations in a specific command queue to be finished with +`vkQueueWaitIdle`. These functions can be used as a very rudimentary way to +perform synchronization. You'll see that the program now exits without problems +when closing the window. + +## Conclusion + +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 +now to reread the code and build a mental model of the purpose of all of the +Vulkan objects in the program and how they relate to each other. We'll be +building on top of that knowledge to extend the functionality of the program +from this point on. + +The next chapter will expand the render loop to handle multiple frames in flight. + +[C++ code](/code/15_hello_triangle.cpp) / +[Vertex shader](/code/09_shader_base.vert) / +[Fragment shader](/code/09_shader_base.frag) diff --git a/en/03_Drawing_a_triangle/03_Drawing/03_Frames_in_flight.md b/en/03_Drawing_a_triangle/03_Drawing/03_Frames_in_flight.md new file mode 100644 index 00000000..e2345e31 --- /dev/null +++ b/en/03_Drawing_a_triangle/03_Drawing/03_Frames_in_flight.md @@ -0,0 +1,176 @@ +## Frames in flight + +Right now our render loop has one glaring flaw. We are required to wait on the +previous frame to finish before we can start rendering the next which results +in unnecessary idling of the host. + + + +The way to fix this is to allow multiple frames to be *in-flight* at once, that +is to say, allow the rendering of one frame to not interfere with the recording +of the next. How do we do this? Any resource that is accessed and modified +during rendering must be duplicated. Thus, we need multiple command buffers, +semaphores, and fences. In later chapters we will also add multiple instances +of other resources, so we will see this concept reappear. + +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; +``` + +We choose the number 2 because we don't want the CPU to get *too* far ahead of +the GPU. With 2 frames in flight, the CPU and the GPU can be working on their +own tasks at the same time. If the CPU finishes early, it will wait till the +GPU finishes rendering before submitting more work. With 3 or more frames in +flight, the CPU could get ahead of the GPU, adding frames of latency. +Generally, extra latency isn't desired. But giving the application control over +the number of frames in flight is another example of Vulkan being explicit. + +Each frame should have its own command buffer, set of semaphores, and fence. +Rename and then change them to be `std::vector`s of the objects: + +```c++ +std::vector commandBuffers; + +... + +std::vector imageAvailableSemaphores; +std::vector renderFinishedSemaphores; +std::vector inFlightFences; +``` + +Then we need to create multiple command buffers. Rename `createCommandBuffer` +to `createCommandBuffers`. Next we need to resize the command buffers vector +to the size of `MAX_FRAMES_IN_FLIGHT`, alter the `VkCommandBufferAllocateInfo` +to contain that many command buffers, and then change the destination to our +vector of command buffers: + +```c++ +void createCommandBuffers() { + commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); + ... + allocInfo.commandBufferCount = (uint32_t) commandBuffers.size(); + + if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate command buffers!"); + } +} +``` + +The `createSyncObjects` function should be changed to create all of the objects: + +```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; + 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!"); + } + } +} +``` + +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); + vkDestroyFence(device, inFlightFences[i], nullptr); + } + + ... +} +``` + +Remember, because command buffers are freed for us when we free the command +pool, there is nothing extra to do for command buffer cleanup. + +To use the right objects every frame, we need to keep track of the current +frame. We will use a frame index for that purpose: + +```c++ +uint32_t currentFrame = 0; +``` + +The `drawFrame` function can now be modified to use the right objects: + +```c++ +void drawFrame() { + vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX); + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + + ... + + vkResetCommandBuffer(commandBuffers[currentFrame], 0); + recordCommandBuffer(commandBuffers[currentFrame], imageIndex); + + ... + + submitInfo.pCommandBuffers = &commandBuffers[currentFrame]; + + ... + + VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[currentFrame]}; + + ... + + VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[currentFrame]}; + + ... + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) { +} +``` + +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. + + + +We've now implemented all the needed synchronization to ensure that there are +no more than `MAX_FRAMES_IN_FLIGHT` frames of work enqueued and that these +frames are not stepping over eachother. 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. + + +In the next chapter we'll deal with one more small thing that is required for a +well-behaved Vulkan program. + + +[C++ code](/code/16_frames_in_flight.cpp) / +[Vertex shader](/code/09_shader_base.vert) / +[Fragment shader](/code/09_shader_base.frag) diff --git a/en/03_Drawing_a_triangle/04_Swap_chain_recreation.md b/en/03_Drawing_a_triangle/04_Swap_chain_recreation.md new file mode 100644 index 00000000..87f0c3fb --- /dev/null +++ b/en/03_Drawing_a_triangle/04_Swap_chain_recreation.md @@ -0,0 +1,280 @@ +## Introduction + +The application we have now successfully draws a triangle, but there are some +circumstances that it isn't handling properly yet. It is possible for the window +surface to change such that the swap chain is no longer compatible with it. One +of the reasons that could cause this to happen is the size of the window +changing. We have to catch these events and recreate the swap chain. + +## Recreating the swap chain + +Create a new `recreateSwapChain` function that calls `createSwapChain` and all +of the creation functions for the objects that depend on the swap chain or the +window size. + +```c++ +void recreateSwapChain() { + vkDeviceWaitIdle(device); + + createSwapChain(); + createImageViews(); + createFramebuffers(); +} +``` + +We first call `vkDeviceWaitIdle`, because just like in the last chapter, we +shouldn't touch resources that may still be in use. Obviously, we'll have to recreate +the swap chain itself. The image views need to be recreated because they are based +directly on the swap chain images. Finally, the framebuffers directly depend on the +swap chain images, and thus must be recreated as well. + +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++ +void cleanupSwapChain() { + +} + +void recreateSwapChain() { + vkDeviceWaitIdle(device); + + cleanupSwapChain(); + + createSwapChain(); + createImageViews(); + createFramebuffers(); +} +``` + +Note that we don't recreate the renderpass here for simplicity. In theory it can be possible for the swap chain image format to change during an applications' lifetime, e.g. when moving a window from an standard range to an high dynamic range monitor. This may require the application to recreate the renderpass to make sure the change between dynamic ranges is properly reflected. + +We'll move the cleanup code of all objects that are recreated as part of a swap +chain refresh from `cleanup` to `cleanupSwapChain`: + +```c++ +void cleanupSwapChain() { + for (size_t i = 0; i < swapChainFramebuffers.size(); i++) { + vkDestroyFramebuffer(device, swapChainFramebuffers[i], nullptr); + } + + for (size_t i = 0; i < swapChainImageViews.size(); i++) { + vkDestroyImageView(device, swapChainImageViews[i], nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); +} + +void cleanup() { + cleanupSwapChain(); + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + + vkDestroyRenderPass(device, renderPass, 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) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); +} +``` + +Note that in `chooseSwapExtent` we already query the new window resolution to +make sure that the swap chain images have the (new) right size, so there's no +need to modify `chooseSwapExtent` (remember that we already had to use +`glfwGetFramebufferSize` get the resolution of the surface in pixels when +creating the swap chain). + +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 + +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. 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. + +```c++ +VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_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!"); +} +``` + +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. + +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. + +```c++ +result = vkQueuePresentKHR(presentQueue, &presentInfo); + +if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { + recreateSwapChain(); +} 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. + +## Fixing a deadlock + +If we try to run the code now, it is possible to encounter a deadlock. +Debugging the code, we find that the application reaches `vkWaitForFences` but +never continues past it. This is because when `vkAcquireNextImageKHR` returns +`VK_ERROR_OUT_OF_DATE_KHR`, we recreate the swapchain and then return from +`drawFrame`. But before that happens, the current frame's fence was waited upon +and reset. Since we return immediately, no work is submitted for execution and +the fence will never be signaled, causing `vkWaitForFences` to halt forever. + +There is a simple fix thankfully. Delay resetting the fence until after we +know for sure we will be submitting work with it. Thus, if we return early, the +fence is still signaled and `vkWaitForFences` wont deadlock the next time we +use the same fence object. + +The beginning of `drawFrame` should now look like this: +```c++ +vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX); + +uint32_t imageIndex; +VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_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!"); +} + +// Only reset the fence if we are submitting work +vkResetFences(device, 1, &inFlightFences[currentFrame]); +``` + +## 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; + +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) { + ... +} +``` + +It is important to do this after `vkQueuePresentKHR` to ensure that the semaphores are in a consistent state, otherwise a signaled semaphore may never be properly waited upon. 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 date 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; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + vkDeviceWaitIdle(device); + + ... +} +``` + +The initial call to `glfwGetFramebufferSize` handles the case where the size is already correct and `glfwWaitEvents` would have nothing to wait on. + +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/17_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/en/04_Vertex_buffers/00_Vertex_input_description.md similarity index 89% rename from 04_Vertex_buffers/00_Vertex_input_description.md rename to en/04_Vertex_buffers/00_Vertex_input_description.md index a6c47c64..e7da3e4f 100644 --- a/04_Vertex_buffers/00_Vertex_input_description.md +++ b/en/04_Vertex_buffers/00_Vertex_input_description.md @@ -14,17 +14,12 @@ shader code itself. The vertex shader takes input from a vertex buffer using the ```glsl #version 450 -#extension GL_ARB_separate_shader_objects : enable layout(location = 0) in vec2 inPosition; layout(location = 1) in vec3 inColor; layout(location = 0) out vec3 fragColor; -out gl_PerVertex { - vec4 gl_Position; -}; - void main() { gl_Position = vec4(inPosition, 0.0, 1.0); fragColor = inColor; @@ -36,6 +31,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 @@ -87,7 +94,7 @@ struct Vertex { glm::vec3 color; static VkVertexInputBindingDescription getBindingDescription() { - VkVertexInputBindingDescription bindingDescription = {}; + VkVertexInputBindingDescription bindingDescription{}; return bindingDescription; } @@ -99,7 +106,7 @@ vertices. It specifies the number of bytes between data entries and whether to move to the next data entry after each vertex or after each instance. ```c++ -VkVertexInputBindingDescription bindingDescription = {}; +VkVertexInputBindingDescription bindingDescription{}; bindingDescription.binding = 0; bindingDescription.stride = sizeof(Vertex); bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; @@ -129,7 +136,7 @@ to `Vertex` to fill in these structs. ... static std::array getAttributeDescriptions() { - std::array attributeDescriptions = {}; + std::array attributeDescriptions{}; return attributeDescriptions; } @@ -202,7 +209,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 +220,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/18_vertex_input.cpp) / +[Vertex shader](/code/18_shader_vertexbuffer.vert) / +[Fragment shader](/code/18_shader_vertexbuffer.frag) diff --git a/04_Vertex_buffers/01_Vertex_buffer_creation.md b/en/04_Vertex_buffers/01_Vertex_buffer_creation.md similarity index 80% rename from 04_Vertex_buffers/01_Vertex_buffer_creation.md rename to en/04_Vertex_buffers/01_Vertex_buffer_creation.md index 2fcd41f2..77122c50 100644 --- a/04_Vertex_buffers/01_Vertex_buffer_creation.md +++ b/en/04_Vertex_buffers/01_Vertex_buffer_creation.md @@ -16,7 +16,7 @@ before `createCommandBuffers`. ```c++ void initVulkan() { createInstance(); - setupDebugCallback(); + setupDebugMessenger(); createSurface(); pickPhysicalDevice(); createLogicalDevice(); @@ -28,7 +28,7 @@ void initVulkan() { createCommandPool(); createVertexBuffer(); createCommandBuffers(); - createSemaphores(); + createSyncObjects(); } ... @@ -41,7 +41,7 @@ void createVertexBuffer() { Creating a buffer requires us to fill a `VkBufferCreateInfo` structure. ```c++ -VkBufferCreateInfo bufferInfo = {}; +VkBufferCreateInfo bufferInfo{}; bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; bufferInfo.size = sizeof(vertices[0]) * vertices.size(); ``` @@ -74,23 +74,37 @@ 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; ... void createVertexBuffer() { - VkBufferCreateInfo bufferInfo = {}; + VkBufferCreateInfo bufferInfo{}; bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; bufferInfo.size = sizeof(vertices[0]) * vertices.size(); 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 @@ -187,7 +201,7 @@ We now have a way to determine the right memory type, so we can actually allocate the memory by filling in the `VkMemoryAllocateInfo` structure. ```c++ -VkMemoryAllocateInfo allocInfo = {}; +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); @@ -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 @@ -259,7 +282,7 @@ There are two ways to deal with that problem: * Use a memory heap that is host coherent, indicated with `VK_MEMORY_PROPERTY_HOST_COHERENT_BIT` -* Call `vkFlushMappedMemoryRanges` to after writing to the mapped memory, and +* Call `vkFlushMappedMemoryRanges` after writing to the mapped memory, and call `vkInvalidateMappedMemoryRanges` before reading from the mapped memory We went for the first approach, which ensures that the mapped memory always @@ -267,19 +290,21 @@ matches the contents of the allocated memory. Do keep in mind that this may lead to slightly worse performance than explicit flushing, but we'll see why that doesn't matter in the next chapter. +Flushing memory ranges or using a coherent memory heap means that the driver will be aware of our writes to the buffer, but it doesn't mean that they are actually visible on the GPU yet. The transfer of data to the GPU is an operation that happens in the background and the specification simply [tells us](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap7.html#synchronization-submission-host-writes) that it is guaranteed to be complete as of the next call to `vkQueueSubmit`. + ## Binding the vertex buffer All that remains now is binding the vertex buffer during rendering operations. -We're going to extend the `createCommandBuffers` function to do that. +We're going to extend the `recordCommandBuffer` function to do that. ```c++ -vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); +vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); VkBuffer vertexBuffers[] = {vertexBuffer}; VkDeviceSize offsets[] = {0}; -vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets); +vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); -vkCmdDraw(commandBuffers[i], vertices.size(), 1, 0, 0); +vkCmdDraw(commandBuffer, static_cast(vertices.size()), 1, 0, 0); ``` The `vkCmdBindVertexBuffers` function is used to bind vertex buffers to @@ -312,6 +337,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/19_vertex_buffer.cpp) / +[Vertex shader](/code/18_shader_vertexbuffer.vert) / +[Fragment shader](/code/18_shader_vertexbuffer.frag) diff --git a/04_Vertex_buffers/02_Staging_buffer.md b/en/04_Vertex_buffers/02_Staging_buffer.md similarity index 85% rename from 04_Vertex_buffers/02_Staging_buffer.md rename to en/04_Vertex_buffers/02_Staging_buffer.md index ea166480..289e74d4 100644 --- a/04_Vertex_buffers/02_Staging_buffer.md +++ b/en/04_Vertex_buffers/02_Staging_buffer.md @@ -24,7 +24,7 @@ specifically for transfer operations. It will require you to make the following modifications to your program: * Modify `QueueFamilyIndices` and `findQueueFamilies` to explicitly look for a -queue family with the `VK_QUEUE_TRANSFER` bit, but not the +queue family with the `VK_QUEUE_TRANSFER_BIT` bit, but not the `VK_QUEUE_GRAPHICS_BIT`. * Modify `createLogicalDevice` to request a handle to the transfer queue * Create a second command pool for command buffers that are submitted on the @@ -44,26 +44,26 @@ 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) { - VkBufferCreateInfo bufferInfo = {}; +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!"); } VkMemoryRequirements memRequirements; vkGetBufferMemoryRequirements(device, buffer, &memRequirements); - VkMemoryAllocateInfo allocInfo = {}; + 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.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; @@ -148,7 +148,7 @@ pool generation in that case. ```c++ void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { - VkCommandBufferAllocateInfo allocInfo = {}; + VkCommandBufferAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; allocInfo.commandPool = commandPool; @@ -162,21 +162,19 @@ void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { And immediately start recording the command buffer: ```c++ -VkCommandBufferBeginInfo beginInfo = {}; +VkCommandBufferBeginInfo beginInfo{}; beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; vkBeginCommandBuffer(commandBuffer, &beginInfo); ``` -The `VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT` flag that we used for the -drawing command buffers is not necessary here, because we're only going to use -the command buffer once and wait with returning from the function until the copy +We're only going to use the command buffer once and wait with returning from the function until the copy operation has finished executing. It's good practice to tell the driver about our intent using `VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT`. ```c++ -VkBufferCopy copyRegion = {}; +VkBufferCopy copyRegion{}; copyRegion.srcOffset = 0; // Optional copyRegion.dstOffset = 0; // Optional copyRegion.size = size; @@ -197,7 +195,7 @@ This command buffer only contains the copy command, so we can stop recording right after that. Now execute the command buffer to complete the transfer: ```c++ -VkSubmitInfo submitInfo = {}; +VkSubmitInfo submitInfo{}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &commandBuffer; @@ -229,10 +227,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 +256,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/20_staging_buffer.cpp) / +[Vertex shader](/code/18_shader_vertexbuffer.vert) / +[Fragment shader](/code/18_shader_vertexbuffer.frag) diff --git a/04_Vertex_buffers/03_Index_buffer.md b/en/04_Vertex_buffers/03_Index_buffer.md similarity index 83% rename from 04_Vertex_buffers/03_Index_buffer.md rename to en/04_Vertex_buffers/03_Index_buffer.md index e0323de8..088263db 100644 --- a/04_Vertex_buffers/03_Index_buffer.md +++ b/en/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,19 +100,36 @@ 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 -`createCommandBuffers`. We first need to bind the index buffer, just like we did +`recordCommandBuffer`. We first need to bind the index buffer, just like we did for the vertex buffer. The difference is that you can only have a single index buffer. It's unfortunately not possible to use different indices for each vertex attribute, so we do still have to completely duplicate vertex data even if just one attribute varies. ```c++ -vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets); +vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); -vkCmdBindIndexBuffer(commandBuffers[i], indexBuffer, 0, VK_INDEX_TYPE_UINT16); +vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT16); ``` An index buffer is bound with `vkCmdBindIndexBuffer` which has the index buffer, @@ -122,13 +142,13 @@ 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(commandBuffer, static_cast(indices.size()), 1, 0, 0, 0); ``` A call to this function is very similar to `vkCmdDraw`. The first two parameters specify the number of indices and the number of instances. We're not using instancing, so just specify `1` instance. The number of indices represents the -number of vertices that will be passed to the vertex buffer. The next parameter +number of vertices that will be passed to the vertex shader. The next parameter specifies an offset into the index buffer, using a value of `1` would cause the graphics card to start reading at the second index. The second to last parameter specifies an offset to add to the indices in the index buffer. The final @@ -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/21_index_buffer.cpp) / +[Vertex shader](/code/18_shader_vertexbuffer.vert) / +[Fragment shader](/code/18_shader_vertexbuffer.frag) diff --git a/05_Uniform_buffers/00_Descriptor_layout_and_buffer.md b/en/05_Uniform_buffers/00_Descriptor_layout_and_buffer.md similarity index 69% rename from 05_Uniform_buffers/00_Descriptor_layout_and_buffer.md rename to en/05_Uniform_buffers/00_Descriptor_layout_and_buffer.md index eb275d5d..2b89e084 100644 --- a/05_Uniform_buffers/00_Descriptor_layout_and_buffer.md +++ b/en/05_Uniform_buffers/00_Descriptor_layout_and_buffer.md @@ -61,12 +61,11 @@ make the rectangle from the previous chapter spin around in 3D. Modify the vertex shader to include the uniform buffer object like it was specified above. I will assume that you are familiar with MVP transformations. -If you're not, see [the resource](http://opengl.datenwolf.net/gltut/html/index.html) +If you're not, see [the resource](https://www.opengl-tutorial.org/beginners-tutorials/tutorial-3-matrices/) mentioned in the first chapter. ```glsl #version 450 -#extension GL_ARB_separate_shader_objects : enable layout(binding = 0) uniform UniformBufferObject { mat4 model; @@ -79,10 +78,6 @@ layout(location = 1) in vec3 inColor; layout(location = 0) out vec3 fragColor; -out gl_PerVertex { - vec4 gl_Position; -}; - void main() { gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 0.0, 1.0); fragColor = inColor; @@ -93,7 +88,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 @@ -138,7 +137,7 @@ struct. ```c++ void createDescriptorSetLayout() { - VkDescriptorSetLayoutBinding uboLayoutBinding = {}; + VkDescriptorSetLayoutBinding uboLayoutBinding{}; uboLayoutBinding.binding = 0; uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; uboLayoutBinding.descriptorCount = 1; @@ -158,7 +157,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,20 +173,20 @@ 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 a simple `VkDescriptorSetLayoutCreateInfo` with the array of bindings: ```c++ -VkDescriptorSetLayoutCreateInfo layoutInfo = {}; +VkDescriptorSetLayoutCreateInfo layoutInfo{}; 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 +197,10 @@ specified in the pipeline layout object. Modify the `VkPipelineLayoutCreateInfo` to reference the layout object: ```c++ -VkDescriptorSetLayout setLayouts[] = {descriptorSetLayout}; -VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; +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 +208,44 @@ 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. + +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! Thus, we need to have as many uniform buffers +as we have frames in flight, and write to a uniform buffer that is not currently +being read by the GPU -Add new class members for `uniformStagingBuffer`, `uniformStagingBufferMemory`, -`uniformBuffer`, and `uniformBufferMemory`: +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++ @@ -238,49 +253,70 @@ void initVulkan() { ... createVertexBuffer(); createIndexBuffer(); - createUniformBuffer(); + createUniformBuffers(); ... } ... -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(MAX_FRAMES_IN_FLIGHT); + uniformBuffersMemory.resize(MAX_FRAMES_IN_FLIGHT); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; 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 when we stop rendering. ```c++ -void mainLoop() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); +void cleanup() { + ... - updateUniformBuffer(); - drawFrame(); + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroyBuffer(device, uniformBuffers[i], nullptr); + vkFreeMemory(device, uniformBuffersMemory[i], nullptr); } - vkDeviceWaitIdle(device); + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); + + ... - glfwDestroyWindow(window); +} +``` + +## Updating uniform data + +Create a new function `updateUniformBuffer` and add a call to it from the `drawFrame` function before submitting the next frame: + +```c++ +void drawFrame() { + ... + + updateUniformBuffer(currentFrame); + + ... + + 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,30 +339,28 @@ 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 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)); +UniformBufferObject ubo{}; +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 +392,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/22_descriptor_layout.cpp) / +[Vertex shader](/code/22_shader_ubo.vert) / +[Fragment shader](/code/22_shader_ubo.frag) diff --git a/en/05_Uniform_buffers/01_Descriptor_pool_and_sets.md b/en/05_Uniform_buffers/01_Descriptor_pool_and_sets.md new file mode 100644 index 00000000..2f1a7813 --- /dev/null +++ b/en/05_Uniform_buffers/01_Descriptor_pool_and_sets.md @@ -0,0 +1,391 @@ +## 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 for each `VkBuffer` resource to bind it to the +uniform buffer descriptor. + +## Descriptor pool + +Descriptor sets can't be created directly, they must be allocated from a pool +like command buffers. The equivalent for descriptor sets is unsurprisingly +called a *descriptor pool*. We'll write a new function `createDescriptorPool` +to set it up. + +```c++ +void initVulkan() { + ... + createUniformBuffers(); + createDescriptorPool(); + ... +} + +... + +void createDescriptorPool() { + +} +``` + +We first need to describe which descriptor types our descriptor sets are going +to contain and how many of them, using `VkDescriptorPoolSize` structures. + +```c++ +VkDescriptorPoolSize poolSize{}; +poolSize.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; +poolSize.descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT); +``` + +We will allocate one of these descriptors for every frame. This +pool size structure is referenced by the main `VkDescriptorPoolCreateInfo`: + +```c++ +VkDescriptorPoolCreateInfo poolInfo{}; +poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; +poolInfo.poolSizeCount = 1; +poolInfo.pPoolSizes = &poolSize; +``` + +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 = static_cast(MAX_FRAMES_IN_FLIGHT); +``` + +The structure has an optional flag similar to command pools that determines if +individual descriptor sets can be freed or not: +`VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT`. We're not going to touch +the descriptor set after creating it, so we don't need this flag. You can leave +`flags` to its default value of `0`. + +```c++ +VkDescriptorPool descriptorPool; + +... + +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. + +## Descriptor set + +We can now allocate the descriptor sets themselves. Add a `createDescriptorSets` +function for that purpose: + +```c++ +void initVulkan() { + ... + createDescriptorPool(); + createDescriptorSets(); + ... +} + +... + +void createDescriptorSets() { + +} +``` + +A descriptor set allocation is described with a `VkDescriptorSetAllocateInfo` +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++ +std::vector layouts(MAX_FRAMES_IN_FLIGHT, descriptorSetLayout); +VkDescriptorSetAllocateInfo allocInfo{}; +allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; +allocInfo.descriptorPool = descriptorPool; +allocInfo.descriptorSetCount = static_cast(MAX_FRAMES_IN_FLIGHT); +allocInfo.pSetLayouts = layouts.data(); +``` + +In our case we will create one descriptor set for each frame in flight, 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++ +VkDescriptorPool descriptorPool; +std::vector descriptorSets; + +... + +descriptorSets.resize(MAX_FRAMES_IN_FLIGHT); +if (vkAllocateDescriptorSets(device, &allocInfo, descriptorSets.data()) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate descriptor sets!"); +} +``` + +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 descriptor sets, each with one uniform +buffer descriptor. + +```c++ +void cleanup() { + ... + vkDestroyDescriptorPool(device, descriptorPool, nullptr); + + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); + ... +} +``` + +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 < MAX_FRAMES_IN_FLIGHT; 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. + +```c++ +for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + VkDescriptorBufferInfo bufferInfo{}; + bufferInfo.buffer = uniformBuffers[i]; + bufferInfo.offset = 0; + bufferInfo.range = sizeof(UniformBufferObject); +} +``` + +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 = descriptorSets[i]; +descriptorWrite.dstBinding = 0; +descriptorWrite.dstArrayElement = 0; +``` + +The first two fields specify the descriptor set to update and the binding. We +gave our uniform buffer binding index `0`. Remember that descriptors can be +arrays, so we also need to specify the first index in the array that we want to +update. We're not using an array, so the index is simply `0`. + +```c++ +descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; +descriptorWrite.descriptorCount = 1; +``` + +We need to specify the type of descriptor again. It's possible to update +multiple descriptors at once in an array, starting at index `dstArrayElement`. +The `descriptorCount` field specifies how many array elements you want to +update. + +```c++ +descriptorWrite.pBufferInfo = &bufferInfo; +descriptorWrite.pImageInfo = nullptr; // Optional +descriptorWrite.pTexelBufferView = nullptr; // Optional +``` + +The last field references an array with `descriptorCount` structs that actually +configure the descriptors. It depends on the type of descriptor which one of the +three you actually need to use. The `pBufferInfo` field is used for descriptors +that refer to buffer data, `pImageInfo` is used for descriptors that refer to +image data, and `pTexelBufferView` is used for descriptors that refer to buffer +views. Our descriptor is based on buffers, so we're using `pBufferInfo`. + +```c++ +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 descriptors to each other, +as its name implies. + +## Using descriptor sets + +We now need to update the `recordCommandBuffer` function to actually bind the +right descriptor set for each frame to the descriptors in the shader with `vkCmdBindDescriptorSets`. This needs to be done before the `vkCmdDrawIndexed` call: + +```c++ +vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets[currentFrame], 0, nullptr); +vkCmdDrawIndexed(commandBuffer, static_cast(indices.size()), 1, 0, 0, 0); +``` + +Unlike vertex and index buffers, descriptor sets are not unique to graphics +pipelines. Therefore we need to specify if we want to bind descriptor sets to +the graphics or compute pipeline. The next parameter is the layout that the +descriptors are based on. The next three parameters specify the index of the +first descriptor set, the number of sets to bind, and the array of sets to bind. +We'll get back to this in a moment. The last two parameters specify an array of +offsets that are used for dynamic descriptors. We'll look at these in a future +chapter. + +If you run your program now, then you'll notice that unfortunately nothing is +visible. The problem is that because of the Y-flip we did in the projection +matrix, the vertices are now being drawn in counter-clockwise order instead of +clockwise order. This causes backface culling to kick in and prevents +any geometry from being drawn. Go to the `createGraphicsPipeline` function and +modify the `frontFace` in `VkPipelineRasterizationStateCreateInfo` to correct +this: + +```c++ +rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; +rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; +``` + +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 `updateUniformBuffer` takes care of screen +resizing, so we don't need to recreate the descriptor set in +`recreateSwapChain`. + +## Alignment requirements + +One thing we've glossed over so far is how exactly the data in the C++ structure should match with the uniform definition in the shader. It seems obvious enough to simply use the same types in both: + +```c++ +struct UniformBufferObject { + glm::mat4 model; + glm::mat4 view; + glm::mat4 proj; +}; + +layout(binding = 0) uniform UniformBufferObject { + mat4 model; + mat4 view; + mat4 proj; +} ubo; +``` + +However, that's not all there is to it. For example, try modifying the struct and shader to look like this: + +```c++ +struct UniformBufferObject { + glm::vec2 foo; + glm::mat4 model; + glm::mat4 view; + glm::mat4 proj; +}; + +layout(binding = 0) uniform UniformBufferObject { + vec2 foo; + mat4 model; + mat4 view; + mat4 proj; +} ubo; +``` + +Recompile your shader and your program and run it and you'll find that the colorful square you worked so far has disappeared! That's because we haven't taken into account the *alignment requirements*. + +Vulkan expects the data in your structure to be aligned in memory in a specific way, for example: + +* Scalars have to be aligned by N (= 4 bytes given 32 bit floats). +* A `vec2` must be aligned by 2N (= 8 bytes) +* A `vec3` or `vec4` must be aligned by 4N (= 16 bytes) +* A nested structure must be aligned by the base alignment of its members rounded up to a multiple of 16. +* A `mat4` matrix must have the same alignment as a `vec4`. + +You can find the full list of alignment requirements in [the specification](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap15.html#interfaces-resources-layout). + +Our original shader with just three `mat4` fields already met the alignment requirements. As each `mat4` is 4 x 4 x 4 = 64 bytes in size, `model` has an offset of `0`, `view` has an offset of 64 and `proj` has an offset of 128. All of these are multiples of 16 and that's why it worked fine. + +The new structure starts with a `vec2` which is only 8 bytes in size and therefore throws off all of the offsets. Now `model` has an offset of `8`, `view` an offset of `72` and `proj` an offset of `136`, none of which are multiples of 16. To fix this problem we can use the [`alignas`](https://en.cppreference.com/w/cpp/language/alignas) specifier introduced in C++11: + +```c++ +struct UniformBufferObject { + glm::vec2 foo; + alignas(16) glm::mat4 model; + glm::mat4 view; + glm::mat4 proj; +}; +``` + +If you now compile and run your program again you should see that the shader correctly receives its matrix values once again. + +Luckily there is a way to not have to think about these alignment requirements *most* of the time. We can define `GLM_FORCE_DEFAULT_ALIGNED_GENTYPES` right before including GLM: + +```c++ +#define GLM_FORCE_RADIANS +#define GLM_FORCE_DEFAULT_ALIGNED_GENTYPES +#include +``` + +This will force GLM to use a version of `vec2` and `mat4` that has the alignment requirements already specified for us. If you add this definition then you can remove the `alignas` specifier and your program should still work. + +Unfortunately this method can break down if you start using nested structures. Consider the following definition in the C++ code: + +```c++ +struct Foo { + glm::vec2 v; +}; + +struct UniformBufferObject { + Foo f1; + Foo f2; +}; +``` + +And the following shader definition: + +```c++ +struct Foo { + vec2 v; +}; + +layout(binding = 0) uniform UniformBufferObject { + Foo f1; + Foo f2; +} ubo; +``` + +In this case `f2` will have an offset of `8` whereas it should have an offset of `16` since it is a nested structure. In this case you must specify the alignment yourself: + +```c++ +struct UniformBufferObject { + Foo f1; + alignas(16) Foo f2; +}; +``` + +These gotchas are a good reason to always be explicit about alignment. That way you won't be caught offguard by the strange symptoms of alignment errors. + +```c++ +struct UniformBufferObject { + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; +}; +``` + +Don't forget to recompile your shader after removing the `foo` field. + +## Multiple descriptor sets + +As some of the structures and function calls hinted at, it is actually possible +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: + +```c++ +layout(set = 0, binding = 0) uniform UniformBufferObject { ... } +``` + +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/23_descriptor_sets.cpp) / +[Vertex shader](/code/22_shader_ubo.vert) / +[Fragment shader](/code/22_shader_ubo.frag) diff --git a/06_Texture_mapping/00_Images.md b/en/06_Texture_mapping/00_Images.md similarity index 56% rename from 06_Texture_mapping/00_Images.md rename to en/06_Texture_mapping/00_Images.md index 84ab051d..8c9967f6 100644 --- a/06_Texture_mapping/00_Images.md +++ b/en/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 @@ -72,7 +76,7 @@ STB_INCLUDE_PATH = /home/user/libraries/stb ... -CFLAGS = -std=c++11 -I$(VULKAN_SDK_PATH)/include -I$(STB_INCLUDE_PATH) +CFLAGS = -std=c++17 -I$(VULKAN_SDK_PATH)/include -I$(STB_INCLUDE_PATH) ``` ## Loading an image @@ -137,35 +141,70 @@ alpha channel, even if it doesn't have one, which is nice for consistency with other textures in the future. The middle three parameters are outputs for the width, height and actual number of channels in the image. The pointer that is 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 +out row by row with 4 bytes per pixel in the case of `STBI_rgb_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: ```c++ -VkImageCreateInfo imageInfo = {}; +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 @@ -175,14 +214,15 @@ many texels there are on each axis. That's why `depth` must be `1` instead of `0`. Our texture will not be an array and we won't be using mipmapping for now. ```c++ -imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM; +imageInfo.format = VK_FORMAT_R8G8B8A8_SRGB; ``` -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,13 +289,13 @@ 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!"); } ``` The image is created using `vkCreateImage`, which doesn't have any particularly -noteworthy parameters. It is possible that the `VK_FORMAT_R8G8B8A8_UNORM` format +noteworthy parameters. It is possible that the `VK_FORMAT_R8G8B8A8_SRGB` format is not supported by the graphics hardware. You should have a list of acceptable alternatives and go with the best one that is supported. However, support for this particular format is so widespread that we'll skip this step. Using @@ -259,127 +304,33 @@ 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 = {}; +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) { - VkImageCreateInfo imageInfo = {}; +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; imageInfo.extent.width = width; @@ -389,24 +340,24 @@ 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!"); } VkMemoryRequirements memRequirements; vkGetImageMemoryRequirements(device, image, &memRequirements); - VkMemoryAllocateInfo allocInfo = {}; + 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.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_SRGB, 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 @@ -503,7 +404,7 @@ two: ```c++ VkCommandBuffer beginSingleTimeCommands() { - VkCommandBufferAllocateInfo allocInfo = {}; + VkCommandBufferAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; allocInfo.commandPool = commandPool; @@ -512,7 +413,7 @@ VkCommandBuffer beginSingleTimeCommands() { VkCommandBuffer commandBuffer; vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer); - VkCommandBufferBeginInfo beginInfo = {}; + VkCommandBufferBeginInfo beginInfo{}; beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; @@ -524,7 +425,7 @@ VkCommandBuffer beginSingleTimeCommands() { void endSingleTimeCommands(VkCommandBuffer commandBuffer) { vkEndCommandBuffer(commandBuffer); - VkSubmitInfo submitInfo = {}; + VkSubmitInfo submitInfo{}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &commandBuffer; @@ -543,7 +444,7 @@ can now simplify that function to: void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { VkCommandBuffer commandBuffer = beginSingleTimeCommands(); - VkBufferCopy copyRegion = {}; + VkBufferCopy copyRegion{}; copyRegion.size = size; vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); @@ -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) { @@ -572,7 +473,7 @@ transfer queue family ownership when `VK_SHARING_MODE_EXCLUSIVE` is used. There is an equivalent *buffer memory barrier* to do this for buffers. ```c++ -VkImageMemoryBarrier barrier = {}; +VkImageMemoryBarrier barrier{}; barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; barrier.oldLayout = oldLayout; barrier.newLayout = 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.3-extensions/html/chap7.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,126 +554,170 @@ 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_SRGB, 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); +transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); ``` ## 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.3-extensions/html/chap7.html#VkPipelineStageFlagBits) +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_SRGB, 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/24_texture_image.cpp) / +[Vertex shader](/code/22_shader_ubo.vert) / +[Fragment shader](/code/22_shader_ubo.frag) diff --git a/06_Texture_mapping/01_Image_view_and_sampler.md b/en/06_Texture_mapping/01_Image_view_and_sampler.md similarity index 69% rename from 06_Texture_mapping/01_Image_view_and_sampler.md rename to en/06_Texture_mapping/01_Image_view_and_sampler.md index cf658e46..a7269404 100644 --- a/06_Texture_mapping/01_Image_view_and_sampler.md +++ b/en/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; ... @@ -36,11 +36,11 @@ The code for this function can be based directly on `createImageViews`. The only two changes you have to make are the `format` and the `image`: ```c++ -VkImageViewCreateInfo viewInfo = {}; +VkImageViewCreateInfo viewInfo{}; viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; viewInfo.image = textureImage; viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; -viewInfo.format = VK_FORMAT_R8G8B8A8_UNORM; +viewInfo.format = VK_FORMAT_R8G8B8A8_SRGB; viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; viewInfo.subresourceRange.baseMipLevel = 0; viewInfo.subresourceRange.levelCount = 1; @@ -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,8 +62,8 @@ 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) { - VkImageViewCreateInfo viewInfo = {}; +VkImageView createImageView(VkImage image, VkFormat format) { + VkImageViewCreateInfo viewInfo{}; viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; viewInfo.image = image; viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; @@ -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_SRGB); } ``` @@ -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 @@ -161,7 +177,7 @@ Samplers are configured through a `VkSamplerCreateInfo` structure, which specifies all filters and transformations that it should apply. ```c++ -VkSamplerCreateInfo samplerInfo = {}; +VkSamplerCreateInfo samplerInfo{}; samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; samplerInfo.magFilter = VK_FILTER_LINEAR; samplerInfo.minFilter = VK_FILTER_LINEAR; @@ -202,18 +218,30 @@ floors and walls. ```c++ samplerInfo.anisotropyEnable = VK_TRUE; -samplerInfo.maxAnisotropy = 16; +samplerInfo.maxAnisotropy = ???; ``` These two fields specify if anisotropic filtering should be used. There is no reason not to use this unless performance is a concern. The `maxAnisotropy` field limits the amount of texel samples that can be used to calculate the final color. A lower value results in better performance, but lower quality results. -There is no graphics hardware available today that will use more than 16 -samples, because the difference is negligible beyond that point. +To figure out which value we can use, we need to retrieve the properties of the physical device like so: + +```c++ +VkPhysicalDeviceProperties properties{}; +vkGetPhysicalDeviceProperties(physicalDevice, &properties); +``` + +If you look at the documentation for the `VkPhysicalDeviceProperties` structure, you'll see that it contains a `VkPhysicalDeviceLimits` member named `limits`. This struct in turn has a member called `maxSamplerAnisotropy` and this is the maximum value we can specify for `maxAnisotropy`. If we want to go for maximum quality, we can simply use that value directly: ```c++ -samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK ; +samplerInfo.maxAnisotropy = properties.limits.maxSamplerAnisotropy; +``` + +You can either query the properties at the beginning of your program and pass them around to the functions that need them, or query them in the `createTextureSampler` function itself. + +```c++ +samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; ``` The `borderColor` field specifies which color is returned when sampling beyond @@ -240,7 +268,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 +278,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 +306,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.0f; +``` + 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/25_sampler.cpp) / +[Vertex shader](/code/22_shader_ubo.vert) / +[Fragment shader](/code/22_shader_ubo.frag) diff --git a/06_Texture_mapping/02_Combined_image_sampler.md b/en/06_Texture_mapping/02_Combined_image_sampler.md similarity index 70% rename from 06_Texture_mapping/02_Combined_image_sampler.md rename to en/06_Texture_mapping/02_Combined_image_sampler.md index 10138400..df6089c1 100644 --- a/06_Texture_mapping/02_Combined_image_sampler.md +++ b/en/06_Texture_mapping/02_Combined_image_sampler.md @@ -18,7 +18,7 @@ Browse to the `createDescriptorSetLayout` function and add a simply put it in the binding after the uniform buffer: ```c++ -VkDescriptorSetLayoutBinding samplerLayoutBinding = {}; +VkDescriptorSetLayoutBinding samplerLayoutBinding{}; samplerLayoutBinding.binding = 1; samplerLayoutBinding.descriptorCount = 1; samplerLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; @@ -26,9 +26,9 @@ samplerLayoutBinding.pImmutableSamplers = nullptr; samplerLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; std::array bindings = {uboLayoutBinding, samplerLayoutBinding}; -VkDescriptorSetLayoutCreateInfo layoutInfo = {}; +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(); ``` @@ -38,34 +38,61 @@ fragment is going to be determined. It is possible to use texture sampling in 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 -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: +We must also create a larger descriptor pool to make room for the allocation +of the combined image sampler by adding another `VkPoolSize` of type +`VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER` to the +`VkDescriptorPoolCreateInfo`. Go to the `createDescriptorPool` function and +modify it to include a `VkDescriptorPoolSize` for this descriptor: ```c++ -std::array poolSizes = {}; +std::array poolSizes{}; poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; -poolSizes[0].descriptorCount = 1; +poolSizes[0].descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT); poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; -poolSizes[1].descriptorCount = 1; +poolSizes[1].descriptorCount = static_cast(MAX_FRAMES_IN_FLIGHT); -VkDescriptorPoolCreateInfo poolInfo = {}; +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(MAX_FRAMES_IN_FLIGHT); ``` +Inadequate descriptor pools are a good example of a problem that the validation +layers will not catch: As of Vulkan 1.1, `vkAllocateDescriptorSets` may fail +with the error code `VK_ERROR_POOL_OUT_OF_MEMORY` if the pool is not +sufficiently large, but the driver may also try to solve the problem internally. +This means that sometimes (depending on hardware, pool size and allocation size) +the driver will let us get away with an allocation that exceeds the limits of +our descriptor pool. Other times, `vkAllocateDescriptorSets` will fail and +return `VK_ERROR_POOL_OUT_OF_MEMORY`. This can be particularly frustrating if +the allocation succeeds on some machines, but fails on others. + +Since Vulkan shifts the responsiblity for the allocation to the driver, it is no +longer a strict requirement to only allocate as many descriptors of a certain +type (`VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER`, etc.) as specified by the +corresponding `descriptorCount` members for the creation of the descriptor pool. +However, it remains best practise to do so, and in the future, +`VK_LAYER_KHRONOS_validation` will warn about this type of problem if you enable +[Best Practice Validation](https://vulkan.lunarg.com/doc/view/1.1.126.0/windows/best_practices.html). + 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 < MAX_FRAMES_IN_FLIGHT; 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 @@ -74,10 +101,10 @@ buffer descriptor is specified in a `VkDescriptorBufferInfo` struct. This is where the objects from the previous chapter come together. ```c++ -std::array descriptorWrites = {}; +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 +112,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 @@ -112,7 +139,7 @@ struct Vertex { glm::vec2 texCoord; static VkVertexInputBindingDescription getBindingDescription() { - VkVertexInputBindingDescription bindingDescription = {}; + VkVertexInputBindingDescription bindingDescription{}; bindingDescription.binding = 0; bindingDescription.stride = sizeof(Vertex); bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; @@ -121,7 +148,7 @@ struct Vertex { } static std::array getAttributeDescriptions() { - std::array attributeDescriptions = {}; + std::array attributeDescriptions{}; attributeDescriptions[0].binding = 0; attributeDescriptions[0].location = 0; @@ -151,10 +178,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}} }; ``` @@ -190,7 +217,6 @@ this by having the fragment shader output the texture coordinates as colors: ```glsl #version 450 -#extension GL_ARB_separate_shader_objects : enable layout(location = 0) in vec3 fragColor; layout(location = 1) in vec2 fragTexCoord; @@ -265,6 +291,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/26_texture_mapping.cpp) / +[Vertex shader](/code/26_shader_textures.vert) / +[Fragment shader](/code/26_shader_textures.frag) diff --git a/07_Depth_buffering.md b/en/07_Depth_buffering.md similarity index 80% rename from 07_Depth_buffering.md rename to en/07_Depth_buffering.md index a4051350..731dfc19 100644 --- a/07_Depth_buffering.md +++ b/en/07_Depth_buffering.md @@ -20,7 +20,7 @@ struct Vertex { ... static std::array getAttributeDescriptions() { - std::array attributeDescriptions = {}; + std::array attributeDescriptions{}; attributeDescriptions[0].binding = 0; attributeDescriptions[0].location = 0; @@ -35,7 +35,7 @@ struct Vertex { Next, update the vertex shader to accept and transform 3D coordinates as input. Don't forget to recompile it afterwards! -```c++ +```glsl layout(location = 0) in vec3 inPosition; ... @@ -124,15 +124,14 @@ range of `0.0` to `1.0` using the `GLM_FORCE_DEPTH_ZERO_TO_ONE` definition. ## Depth image and view A depth attachment is based on an image, just like the color attachment. The -difference is that the swap chain will not automatically create depth images for -us. We only need a single depth image, because only one draw operation is +difference is that the swap chain will not automatically create depth images for us. We only need a single depth image, because only one draw operation is 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 +271,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 +279,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,19 +289,26 @@ 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_SRGB, VK_IMAGE_ASPECT_COLOR_BIT); ``` That's it for creating the depth image. We don't need to map it or copy another image to it, because we're going to clear it at the start of the render pass -like the color attachment. However, it still needs to be transitioned to a -layout that is suitable for depth attachment usage. We could do this in the -render pass like the color attachment, but here I've chosen to use a pipeline -barrier because the transition only needs to happen once: +like the color attachment. + +### Explicitly transitioning the depth image + +We don't need to explicitly transition the layout of the image to a depth +attachment because we'll take care of this in the render pass. However, for +completeness I'll still describe the process in this section. You may skip it if +you like. + +Make a call to `transitionImageLayout` at the end of the `createDepthResources` +function like so: ```c++ transitionImageLayout(depthImage, depthFormat, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); @@ -327,35 +333,46 @@ 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 We're now going to modify `createRenderPass` to include a depth attachment. -First specify the `VkAttachementDescription`: +First specify the `VkAttachmentDescription`: ```c++ -VkAttachmentDescription depthAttachment = {}; +VkAttachmentDescription depthAttachment{}; depthAttachment.format = findDepthFormat(); depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT; depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; @@ -373,7 +390,7 @@ optimizations. Just like the color buffer, we don't care about the previous depth contents, so we can use `VK_IMAGE_LAYOUT_UNDEFINED` as `initialLayout`. ```c++ -VkAttachmentReference depthAttachmentRef = {}; +VkAttachmentReference depthAttachmentRef{}; depthAttachmentRef.attachment = 1; depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; ``` @@ -381,7 +398,7 @@ depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; Add a reference to the attachment for the first (and only) subpass: ```c++ -VkSubpassDescription subpass = {}; +VkSubpassDescription subpass{}; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass.colorAttachmentCount = 1; subpass.pColorAttachments = &colorAttachmentRef; @@ -394,9 +411,9 @@ buffers. ```c++ std::array attachments = {colorAttachment, depthAttachment}; -VkRenderPassCreateInfo renderPassInfo = {}; +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; @@ -404,9 +421,17 @@ renderPassInfo.dependencyCount = 1; renderPassInfo.pDependencies = &dependency; ``` -Finally, update the `VkRenderPassCreateInfo` struct to refer to both +Next, update the `VkRenderPassCreateInfo` struct to refer to both attachments. +```c++ +dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; +dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; +dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; +``` + +Finally, we need to extend our subpass dependencies to make sure that there is no conflict between the transitioning of the depth image and it being cleared as part of its load operation. The depth image is first accessed in the early fragment test pipeline stage and because we have a load operation that *clears*, we should specify the access mask for writes. + ## Framebuffer The next step is to modify the framebuffer creation to bind the depth image to @@ -419,10 +444,10 @@ std::array attachments = { depthImageView }; -VkFramebufferCreateInfo framebufferInfo = {}; +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; @@ -448,15 +473,15 @@ void initVulkan() { ## Clear values Because we now have multiple attachments with `VK_ATTACHMENT_LOAD_OP_CLEAR`, we -also need to specify multiple clear values. Go to `createCommandBuffers` and +also need to specify multiple clear values. Go to `recordCommandBuffer` and create an array of `VkClearValue` structs: ```c++ -std::array clearValues = {}; -clearValues[0].color = {0.0f, 0.0f, 0.0f, 1.0f}; +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(); ``` @@ -465,6 +490,8 @@ lies at the far view plane and `0.0` at the near view plane. The initial value at each point in the depth buffer should be the furthest possible depth, which is `1.0`. +Note that the order of `clearValues` should be identical to the order of your attachments. + ## Depth and stencil state The depth attachment is ready to be used now, but depth testing still needs to @@ -472,7 +499,7 @@ be enabled in the graphics pipeline. It is configured through the `VkPipelineDepthStencilStateCreateInfo` struct: ```c++ -VkPipelineDepthStencilStateCreateInfo depthStencil = {}; +VkPipelineDepthStencilStateCreateInfo depthStencil{}; depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; depthStencil.depthTestEnable = VK_TRUE; depthStencil.depthWriteEnable = VK_TRUE; @@ -481,9 +508,7 @@ depthStencil.depthWriteEnable = VK_TRUE; The `depthTestEnable` field specifies if the depth of new fragments should be compared to the depth buffer to see if they should be discarded. The `depthWriteEnable` field specifies if the new depth of fragments that pass the -depth test should actually be written to the depth buffer. This is useful for -drawing transparent objects. They should be compared to the previously rendered -opaque objects, but not cause further away transparent objects to not be drawn. +depth test should actually be written to the depth buffer. ```c++ depthStencil.depthCompareOp = VK_COMPARE_OP_LESS; @@ -536,15 +561,34 @@ function to recreate the depth resources in that case: ```c++ 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(); +} +``` + +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); + + ... } ``` @@ -552,6 +596,6 @@ 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/27_depth_buffering.cpp) / +[Vertex shader](/code/27_shader_depth.vert) / +[Fragment shader](/code/27_shader_depth.frag) diff --git a/08_Loading_models.md b/en/08_Loading_models.md similarity index 80% rename from 08_Loading_models.md rename to en/08_Loading_models.md index 956bc068..e3430f5a 100644 --- a/08_Loading_models.md +++ b/en/08_Loading_models.md @@ -18,7 +18,7 @@ We will use the [tinyobjloader](https://github.com/syoyo/tinyobjloader) library to load vertices and faces from an OBJ file. It's fast and it's easy to integrate because it's a single file library like stb_image. Go to the repository linked above and download the `tiny_obj_loader.h` file to a folder in -your library directory. +your library directory. Make sure to use the version of the file from the `master` branch because the latest official release is outdated. **Visual Studio** @@ -38,7 +38,7 @@ TINYOBJ_INCLUDE_PATH = /home/user/libraries/tinyobjloader ... -CFLAGS = -std=c++11 -I$(VULKAN_SDK_PATH)/include -I$(STB_INCLUDE_PATH) -I$(TINYOBJ_INCLUDE_PATH) +CFLAGS = -std=c++17 -I$(VULKAN_SDK_PATH)/include -I$(STB_INCLUDE_PATH) -I$(TINYOBJ_INCLUDE_PATH) ``` ## Sample mesh @@ -48,14 +48,13 @@ model that has lighting baked into the texture. An easy way to find such models is to look for 3D scans on [Sketchfab](https://sketchfab.com/). Many of the models on that site are available in OBJ format with a permissive license. -For this tutorial I've decided to go with the [Chalet Hippolyte Chassande Baroz](https://skfb.ly/HDVU) -model by Escadrone. I tweaked the size and orientation of the model to use it +For this tutorial I've decided to go with the [Viking room](https://sketchfab.com/3d-models/viking-room-a49f1b8e4f5c4ecf9e1fe7d81915ad38) +model by [nigelgoh](https://sketchfab.com/nigelgoh) ([CC BY 4.0](https://web.archive.org/web/20200428202538/https://sketchfab.com/3d-models/viking-room-a49f1b8e4f5c4ecf9e1fe7d81915ad38)). I tweaked the size and orientation of the model to use it as a drop in replacement for the current geometry: -* [chalet.obj](/resources/chalet.obj.zip) -* [chalet.jpg](/resources/chalet.jpg) +* [viking_room.obj](/resources/viking_room.obj) +* [viking_room.png](/resources/viking_room.png) -It has half a million triangles, so it's a nice benchmark for our application. Feel free to use your own model, but make sure that it only consists of one material and that is has dimensions of about 1.5 x 1.5 x 1.5 units. If it is larger than that, then you'll have to change the view matrix. Put the model file @@ -66,11 +65,11 @@ Put two new configuration variables in your program to define the model and texture paths: ```c++ -const int WIDTH = 800; -const int HEIGHT = 600; +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; -const std::string MODEL_PATH = "models/chalet.obj"; -const std::string TEXTURE_PATH = "textures/chalet.jpg"; +const std::string MODEL_PATH = "models/viking_room.obj"; +const std::string TEXTURE_PATH = "textures/viking_room.png"; ``` And update `createTextureImage` to use this path variable: @@ -88,8 +87,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 @@ -97,7 +96,7 @@ there are going to be a lot more vertices than 65535. Remember to also change the `vkCmdBindIndexBuffer` parameter: ```c++ -vkCmdBindIndexBuffer(commandBuffers[i], indexBuffer, 0, VK_INDEX_TYPE_UINT32); +vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT32); ``` The tinyobjloader library is included in the same way as STB libraries. Include @@ -139,10 +138,10 @@ void loadModel() { tinyobj::attrib_t attrib; std::vector shapes; std::vector materials; - std::string err; + std::string warn, err; - if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &err, MODEL_PATH.c_str())) { - throw std::runtime_error(err); + if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, MODEL_PATH.c_str())) { + throw std::runtime_error(warn + err); } } ``` @@ -159,7 +158,7 @@ faces. Each face consists of an array of vertices, and each vertex contains the indices of the position, normal and texture coordinate attributes. OBJ models can also define a material and texture per face, but we will be ignoring those. -The `err` string contains errors and warnings that occurred while loading the +The `err` string contains errors and the `warn` string contains warnings that occurred while loading the file, like a missing material definition. Loading only really failed if the `LoadObj` function returns `false`. As mentioned above, faces in OBJ files can actually contain an arbitrary number of vertices, whereas our application can @@ -182,7 +181,7 @@ straight into our `vertices` vector: ```c++ for (const auto& shape : shapes) { for (const auto& index : shape.mesh.indices) { - Vertex vertex = {}; + Vertex vertex{}; vertices.push_back(vertex); indices.push_back(indices.size()); @@ -224,9 +223,7 @@ following: ![](/images/inverted_texture_coordinates.png) -Great, the geometry looks correct, but what's going on with the texture? The -problem is that the origin of texture coordinates in Vulkan is the top-left -corner, whereas the OBJ format assumes the bottom-left corner. Solve this by +Great, the geometry looks correct, but what's going on with the texture? The OBJ format assumes a coordinate system where a vertical coordinate of `0` means the bottom of the image, however we've uploaded our image into Vulkan in a top to bottom orientation where `0` means the top of the image. Solve this by flipping the vertical component of the texture coordinates: ```c++ @@ -242,6 +239,8 @@ When you run your program again, you should now see the correct result: All that hard work is finally beginning to pay off with a demo like this! +>As the model rotates you may notice that the rear (backside of the walls) looks a bit funny. This is normal and is simply because the model is not really designed to be viewed from that side. + ## Vertex deduplication Unfortunately we're not really taking advantage of the index buffer yet. The @@ -249,23 +248,23 @@ 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) { - Vertex vertex = {}; + Vertex vertex{}; ... if (uniqueVertices.count(vertex) == 0) { - uniqueVertices[vertex] = vertices.size(); + uniqueVertices[vertex] = static_cast(vertices.size()); vertices.push_back(vertex); } @@ -303,7 +302,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 +313,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/28_model_loading.cpp) / +[Vertex shader](/code/27_shader_depth.vert) / +[Fragment shader](/code/27_shader_depth.frag) diff --git a/en/09_Generating_Mipmaps.md b/en/09_Generating_Mipmaps.md new file mode 100644 index 00000000..377ffa53 --- /dev/null +++ b/en/09_Generating_Mipmaps.md @@ -0,0 +1,354 @@ +## 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_SRGB, 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_SRGB, 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_SRGB, 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. + +`vkCmdBlitImage` 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_SRGB, 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_SRGB, 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`. + +Beware if you are using a dedicated transfer queue (as suggested in [Vertex buffers](!en/Vertex_buffers/Staging_buffer)): `vkCmdBlitImage` must be submitted to a queue with graphics capability. + +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_SRGB, 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_SRGB, 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.0f; // Optional + samplerInfo.maxLod = static_cast(mipLevels); + samplerInfo.mipLodBias = 0.0f; // Optional + ... +} +``` + +To allow the full range of mip levels to be used, we set `minLod` to 0.0f, and `maxLod` to the number of mip levels. We have no reason to change the `lod` value , so we set `mipLodBias` to 0.0f. + +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 papers. 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/29_mipmapping.cpp) / +[Vertex shader](/code/27_shader_depth.vert) / +[Fragment shader](/code/27_shader_depth.frag) diff --git a/en/10_Multisampling.md b/en/10_Multisampling.md new file mode 100644 index 00000000..72d21e79 --- /dev/null +++ b/en/10_Multisampling.md @@ -0,0 +1,290 @@ +## 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 the 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 highest sample count that is supported by both (&) 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 = 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() { + ... + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + msaaSamples = getMaxUsableSampleCount(); + break; + } + } + ... +} +``` + +## Setting up a render target + +In MSAA, each pixel is sampled in an offscreen buffer which is then rendered to the screen. This new buffer is 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 resolved to the default framebuffer (which stores only a single sample per pixel). This is why we have to create an additional render target and modify our current drawing process. We only need one render target since only one drawing operation is active at a time, just like with the depth buffer. Add the following class members: + +```c++ +... +VkImage colorImage; +VkDeviceMemory colorImageMemory; +VkImageView colorImageView; +... +``` + +This new image will have to store the desired number of samples per pixel, so we need to pass this number to `VkImageCreateInfo` during the 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, depthImage, depthImageMemory); +... +createImage(texWidth, texHeight, mipLevels, VK_SAMPLE_COUNT_1_BIT, VK_FORMAT_R8G8B8A8_SRGB, 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. 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; + + 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, colorImage, colorImageMemory); + colorImageView = createImageView(colorImage, colorFormat, VK_IMAGE_ASPECT_COLOR_BIT, 1); +} +``` + +For consistency, call the function right before `createDepthResources`: + +```c++ +void initVulkan() { + ... + createColorResources(); + createDepthResources(); + ... +} +``` + +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, depthImage, depthImageMemory); + ... +} +``` + +We have now created a couple of new Vulkan resources, so let's not forget to release them when necessary: + +```c++ +void cleanupSwapChain() { + vkDestroyImageView(device, colorImageView, nullptr); + vkDestroyImage(device, colorImage, nullptr); + vkFreeMemory(device, colorImageMemory, nullptr); + ... +} +``` + +And update the `recreateSwapChain` so that the new color image can be recreated in the correct resolution when the window is resized: + +```c++ +void recreateSwapChain() { + ... + createGraphicsPipeline(); + createColorResources(); + createDepthResources(); + ... +} +``` + +We made it past the initial MSAA setup, now we need to start using this new resource 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 the new color attachment: + +```c++ + ... + std::array attachments = {colorAttachment, depthAttachment, colorAttachmentResolve}; + ... +``` + +With the render pass in place, modify `createFramebuffers` and add the new image view to the list: + +```c++ +void createFramebuffers() { + ... + std::array attachments = { + colorImageView, + depthImageView, + 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 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.3-extensions/html/chap27.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/30_multisampling.cpp) / +[Vertex shader](/code/27_shader_depth.vert) / +[Fragment shader](/code/27_shader_depth.frag) diff --git a/en/90_FAQ.md b/en/90_FAQ.md new file mode 100644 index 00000000..3e6899cf --- /dev/null +++ b/en/90_FAQ.md @@ -0,0 +1,25 @@ +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 / Validation layers are not available**: 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 the "Verify the Installation" instructions [on this page](https://vulkan.lunarg.com/doc/view/1.2.135.0/windows/getting_started.html). Also ensure that your SDK version is at least 1.1.106.0 to support the `VK_LAYER_KHRONOS_validation` layer. + +* **vkCreateSwapchainKHR triggers an error in SteamOverlayVulkanLayer64.dll**: +This appears to be a compatibility problem in the Steam client beta. There are a +few possible workarounds: + * Opt out of the Steam beta program. + * Set the `DISABLE_VK_LAYER_VALVE_steam_overlay_1` environment variable to `1` + * Delete the Steam overlay Vulkan layer entry in the registry under `HKEY_LOCAL_MACHINE\SOFTWARE\Khronos\Vulkan\ImplicitLayers` + +Example: + +![](/images/steam_layers_env.png) diff --git a/en/95_Privacy_policy.md b/en/95_Privacy_policy.md new file mode 100644 index 00000000..fc5cad78 --- /dev/null +++ b/en/95_Privacy_policy.md @@ -0,0 +1,21 @@ +## General + +This privacy policy applies to the information that is collected when you use vulkan-tutorial.com or any of its subdomains. It describes how the owner of this website, Alexander Overvoorde, collects, uses and shares information about you. + +## Analytics + +This website collects analytics about visitors using a self-hosted instance of Matomo ([https://matomo.org/](https://matomo.org/)), formerly known as Piwik. It records which pages you visit, what type of device and browser you use, how long you view a given page and where you came from. This information is anonymized by only recording the first two bytes of your IP address (e.g. `123.123.xxx.xxx`). These anonymized logs are stored for an indefinite amount of time. + +These analytics are used for the purpose of tracking how content on the website is consumed, how many people visit the website in general, and which other websites link here. This makes it easier to engage with the community and determine which areas of the website should be improved, for example if extra time should be spent on facilitating mobile reading. + +This data is not shared with third parties. + +## Advertisement + +This website uses a third-party advertisement server that may use cookies to track activities on the website to measure engagement with advertisements. + +## Comments + +Each chapter includes a comment section at the end that is provided by the third-party Disqus service. This service collects identity data to facilitate the reading and submission of comments, and aggregate usage information to improve their service. + +The full privacy policy of this third-party service can be found at [https://help.disqus.com/terms-and-policies/disqus-privacy-policy](https://help.disqus.com/terms-and-policies/disqus-privacy-policy). \ No newline at end of file diff --git a/fr/00_Introduction.md b/fr/00_Introduction.md new file mode 100644 index 00000000..82297941 --- /dev/null +++ b/fr/00_Introduction.md @@ -0,0 +1,119 @@ +>NOTICE: The English version of the tutorial has recently changed significantly (for the better) and these changes have not yet been applied to the French translation. + +## À propos + +Ce tutoriel vous enseignera les bases de l'utilisation de l'API [Vulkan](https://www.khronos.org/vulkan/) qui expose +les graphismes et le calcul sur cartes graphiques. Vulkan est une nouvelle API créée par le +[groupe Khronos](https://www.khronos.org/) (connu pour OpenGL). Elle fournit une bien meilleure abstraction des cartes +graphiques modernes. Cette nouvelle interface vous permet de mieux décrire ce que votre application souhaite faire, +ce qui peut mener à de meilleures performances et à des comportements moins variables comparés à des APIs +existantes comme [OpenGL](https://en.wikipedia.org/wiki/OpenGL) et +[Direct3D](https://en.wikipedia.org/wiki/Direct3D). Les concepts introduits par Vulkan sont similaires à ceux de +[Direct3D 12](https://en.wikipedia.org/wiki/Direct3D#Direct3D_12) et [Metal](https://en.wikipedia.org/wiki/Metal_(API)). +Cependant Vulkan a l'avantage d'être complètement cross-platform, et vous permet ainsi de développer pour Windows, +Linux, Mac et Android en même temps. + +Il y a cependant un contre-coup à ces avantages. L'API vous impose d'être explicite sur chaque détail. Vous ne pourrez +rien laisser au hasard, et il n'y a aucune structure, aucun environnement créé pour vous par défaut. Il faudra le +recréer à partir de rien. Le travail du driver graphique sera ainsi considérablement réduit, ce qui implique un plus +grand travail de votre part pour assurer un comportement correct. + +Le message véhiculé ici est que Vulkan n'est pas fait pour tout le monde. Cette API est conçue pour les programmeurs +concernés par la programmation avec GPU de haute performance, et qui sont prêts à y travailler sérieusement. Si vous +êtes intéressées dans le développement de jeux vidéo, et moins dans les graphismes eux-mêmes, vous devriez plutôt +continuer d'utiliser OpenGL et DirectX, qui ne seront pas dépréciés en faveur de Vulkan avant un certain temps. Une +autre alternative serait d'utiliser un moteur de jeu comme +[Unreal Engine](https://en.wikipedia.org/wiki/Unreal_Engine#Unreal_Engine_4) ou +[Unity](https://en.wikipedia.org/wiki/Unity_(game_engine)), qui pourront être capables d'utiliser Vulkan tout en +exposant une API de bien plus haut niveau. + +Cela étant dit, présentons quelques prérequis pour ce tutoriel: + +* Une carte graphique et un driver compatibles avec Vulkan ([NVIDIA](https://developer.nvidia.com/vulkan-driver), +[AMD](https://www.amd.com/en/technologies/vulkan), +[Intel](https://software.intel.com/en-us/blogs/2017/02/10/intel-announces-that-we-are-moving-from-beta-support-to-full-official-support-for)) +* De l'expérience avec le C++ (familiarité avec RAII, listes d'initialisation, et autres fonctionnalités modernes) +* Un compilateur avec un support décent des fonctionnalités du C++17 (Visual Studio 2017+, GCC 7+ ou Clang 5+) +* Un minimum d'expérience dans le domaine de la programmation graphique + +Ce tutoriel ne considérera pas comme acquis les concepts d'OpenGL et de Direct3D, mais il requiert que vous connaissiez +les bases du rendu 3D. Il n'expliquera pas non plus les mathématiques derrière la projection de perspective, par +exemple. Lisez [ce livre](http://opengl.datenwolf.net/gltut/html/index.html) pour une bonne introduction des concepts +de rendu 3D. D'autres ressources pour le développement d'application graphiques sont : +* [Ray tracing en un week-end](https://github.com/petershirley/raytracinginoneweekend) +* [Livre sur le Physical Based Rendering](http://www.pbr-book.org/) +* Une application de Vulkan dans les moteurs graphiques open source [Quake](https://github.com/Novum/vkQuake) et de +[DOOM 3](https://github.com/DustinHLand/vkDOOM3) + +Vous pouvez utiliser le C plutôt que le C++ si vous le souhaitez, mais vous devrez utiliser une autre bibliothèque +d'algèbre linéaire et vous structurerez vous-même votre code. Nous utiliserons des possibilités du C++ (RAII, +classes) pour organiser la logique et la durée de vie des ressources. Il existe aussi une +[version alternative](https://github.com/bwasty/vulkan-tutorial-rs) de ce tutoriel pour les développeurs rust. + +Pour faciliter la tâche des développeurs utilisant d'autres langages de programmation, et pour acquérir de l'expérience +avec l'API de base, nous allons utiliser l'API C originelle pour travailler avec Vulkan. Cependant, si vous utilisez le +C++, vous pourrez préférer utiliser le binding [Vulkan-Hpp](https://github.com/KhronosGroup/Vulkan-Hpp) plus récent, +qui permet de s'éloigner de certains détails ennuyeux et d'éviter certains types d'erreurs. + +## E-book + +Si vous préférez lire ce tutoriel en E-book, vous pouvez en télécharger une version EPUB ou PDF ici: + +* [EPUB](https://raw.githubusercontent.com/Overv/VulkanTutorial/master/ebook/Vulkan%20Tutorial%20fr.epub) +* [PDF](https://raw.githubusercontent.com/Overv/VulkanTutorial/master/ebook/Vulkan%20Tutorial%20fr.pdf) + +## Structure du tutoriel + +Nous allons commencer par une approche générale du fonctionnement de Vulkan, et verrons d'abord rapidement le travail à +effectuer pour afficher un premier triangle à l'écran. Le but de chaque petite étape aura ainsi plus de sens quand +vous aurez compris leur rôle dans le fonctionnement global. Ensuite, nous préparerons l'environnement de développement, +avec le [SDK Vulkan](https://lunarg.com/vulkan-sdk/), la [bibliothèque GLM](http://glm.g-truc.net/) pour les opérations +d'algèbre linéaire, et [GLFW](http://www.glfw.org/) pour la création d'une fenêtre. Ce tutoriel couvrira leur mise en +place sur Windows avec Visual Studio, sur Linux Ubuntu avec GCC et sur MacOS. + +Après cela, nous implémenterons tous les éléments nécessaires à un programme Vulkan pour afficher votre premier +triangle. Chaque chapitre suivra approximativement la structure suivante : + +* Introduction d'un nouveau concept et de son utilité +* Utilisation de tous les appels correspondants à l'API pour leur mise en place dans votre programme +* Placement d'une partie de ces appels dans des fonctions pour une réutilisation future + +Bien que chaque chapitre soit écrit comme suite du précédent, il est également possible de lire chacun d'entre eux +comme un article introduisant une certaine fonctionnalité de Vulkan. Ainsi le site peut vous être utile comme référence. +Toutes les fonctions et les types Vulkan sont liés à leur spécification, vous pouvez donc cliquer dessus pour en +apprendre plus. La spécification est par contre en Anglais. Vulkan est une API récente, il peut donc y avoir des +lacunes dans la spécification elle-même. Vous êtes encouragés à transmettre vos retours dans +[ce repo Khronos](https://github.com/KhronosGroup/Vulkan-Docs). + +Comme indiqué plus haut, Vulkan est une API assez prolixe, avec de nombreux paramètres, pensés pour vous fournir un +maximum de contrôle sur le hardware graphique. Ainsi des opérations comme créer une texture prennent de nombreuses +étapes qui doivent être répétées chaque fois. Nous créerons notre propre collection de fonctions d'aide tout le long +du tutoriel. + +Chaque chapitre se conclura avec un lien menant à la totalité du code écrit jusqu'à ce point. Vous pourrez vous y +référer si vous avez un quelconque doute quant à la structure du code, ou si vous rencontrez un bug et que voulez +comparer. Tous les fichiers de code ont été testés sur des cartes graphiques de différents vendeurs pour pouvoir +affirmer qu'ils fonctionnent. Chaque chapitre possède également une section pour écrire vos commentaires en relation +avec le sujet discuté. Veuillez y indiquer votre plateforme, la version de votre driver, votre code source, le +comportement attendu et celui obtenu pour nous simplifier la tâche de vous aider. + +Ce tutoriel est destiné à être un effort de communauté. Vulkan est encore une API très récente et les meilleures +manières d'arriver à un résultat n'ont pas encore été déterminées. Si vous avez un quelconque retour sur le tutoriel +et le site lui-même, n'hésitez alors pas à créer une issue ou une pull request sur le +[repo GitHub](https://github.com/Overv/VulkanTutorial). Vous pouvez *watch* le dépôt afin d'être notifié des +dernières mises à jour du site. + +Après avoir accompli le rituel de l'affichage de votre premier triangle avec Vulkan, nous étendrons le programme pour y +inclure les transformations linéaires, les textures et les modèles 3D. + +Si vous avez déjà utilisé une API graphique auparavant, vous devez savoir qu'il y a nombre d'étapes avant d'afficher la +première géométrie sur l'écran. Il y aura beaucoup plus de ces étapes préliminaires avec Vulkan, mais vous verrez que +chacune d'entre elle est simple à comprendre et n'est pas redondante. Gardez aussi à l'esprit qu'une fois que vous savez +afficher un triangle - certes peu intéressant -, afficher un modèle 3D parfaitement texturé ne nécessite pas tant de +travail supplémentaire, et que chaque étape à partir de ce point est bien mieux récompensée visuellement. + +Si vous rencontrez un problème en suivant ce tutoriel, vérifiez d'abord dans la FAQ que votre problème et sa solution +n'y sont pas déjà listés. Si vous êtes toujours coincé après cela, demandez de l'aide dans la section des commentaires +du chapitre le plus en lien avec votre problème. + +Prêt à vous lancer dans le futur des API graphiques de haute performance? [Allons-y!](!fr/Introduction) diff --git a/fr/01_Vue_d'ensemble.md b/fr/01_Vue_d'ensemble.md new file mode 100644 index 00000000..b5a9ea26 --- /dev/null +++ b/fr/01_Vue_d'ensemble.md @@ -0,0 +1,231 @@ +Ce chapitre commencera par introduire Vulkan et les problèmes auxquels l'API s’adresse. Nous nous intéresserons ensuite aux +éléments requis pour l'affichage d'un premier triangle. Cela vous donnera une vue d'ensemble pour mieux replacer les +futurs chapitres dans leur contexte. Nous conclurons sur la structure de Vulkan et la manière dont l'API est communément +utilisée. + +## Origine de Vulkan + +Comme les APIs précédentes, Vulkan est conçue comme une abstraction des +[GPUs](https://en.wikipedia.org/wiki/Graphics_processing_unit). Le problème avec la plupart de ces APIs est qu'elles +furent créées à une époque où le hardware graphique était limité à des fonctionnalités prédéfinies tout juste +configurables. Les développeurs devaient fournir les sommets dans un format standardisé, et étaient ainsi à la merci +des constructeurs pour les options d'éclairage et les jeux d'ombre. + +Au fur et à mesure que les cartes graphiques progressèrent, elles offrirent de plus en plus de fonctionnalités +programmables. Il fallait alors intégrer toutes ces nouvelles fonctionnalités aux APIs existantes. Ceci résulta +en une abstraction peu pratique et le driver devait deviner l'intention du développeur pour relier le programme aux +architectures modernes. C'est pour cela que les drivers étaient mis à jour si souvent, et que certaines augmentaient +soudainement les performances. À cause de la complexité de ces drivers, les développeurs devaient gérer les +différences de comportement entre les fabricants, dont par exemple des tolérances plus ou moins importantes pour les +[shaders](https://en.wikipedia.org/wiki/Shader). Un exemple de fonctionnalité est le +[tiled rendering](https://en.wikipedia.org/wiki/Tiled_rendering), pour laquelle une plus grande flexibilité mènerait à +de meilleures performance. Ces APIs anciennes souffrent également d’une autre limitation : le support limité du +multithreading, menant à des goulot d'étranglement du coté du CPU. Au-delà des nouveautés techniques, la dernière +décennie a aussi été témoin de l’arrivée de matériel mobile. Ces GPUs portables ont des architectures différentes qui +prennent en compte des contraintes spatiales ou énergétiques. + +Vulkan résout ces problèmes en ayant été repensée à partir de rien pour des architectures modernes. Elle réduit le +travail du driver en permettant (en fait en demandant) au développeur d’expliciter ses objectifs en passant par une +API plus prolixe. Elle permet à plusieurs threads d’invoquer des commandes de manière asynchrone. Elle supprime les +différences lors de la compilation des shaders en imposant un format en bytecode compilé par un compilateur officiel. +Enfin, elle reconnaît les capacités des cartes graphiques modernes en unifiant le computing et les graphismes dans +une seule et unique API. + +## Le nécessaire pour afficher un triangle + +Nous allons maintenant nous intéresser aux étapes nécessaires à l’affichage d’un triangle dans un programme Vulkan +correctement conçu. Tous les concepts ici évoqués seront développés dans les prochains chapitres. Le but ici est +simplement de vous donner une vue d’ensemble afin d’y replacer tous les éléments. + +### Étape 1 - Instance et sélection d’un physical device + +Une application commence par paramétrer l’API à l’aide d’une «`VkInstance`». Une instance est créée en décrivant votre +application et les extensions que vous comptez utiliser. Après avoir créé votre `VkInstance`, vous pouvez demander l’accès +à du hardware compatible avec Vulkan, et ainsi sélectionner un ou plusieurs «`VkPhysicalDevice`» pour y réaliser vos +opérations. Vous pouvez traiter des informations telles que la taille de la VRAM ou des capacités de la carte graphique, +et ainsi préférer par exemple du matériel dédié. + +### Étape 2 – Logical device et familles de queues (queue families) + +Après avoir sélectionné le hardware qui vous convient, vous devez créer un `VkDevice` (logical device). Vous décrivez +pour cela quelles `VkPhysicalDeviceFeatures` vous utiliserez, comme l’affichage multi-fenêtre ou des floats de 64 bits. +Vous devrez également spécifier quelles `vkQueueFamilies` vous utiliserez. La plupart des opérations, comme les +commandes d’affichage et les allocations de mémoire, sont exécutés de manière asynchrone en les envoyant à une +`VkQueue`. Ces queues sont crées à partir d’une famille de queues, chacune de ces dernières supportant uniquement une +certaine collection d’opérations. Il pourrait par exemple y avoir des familles différentes pour les graphismes, le +calcul et les opérations mémoire. L’existence d’une famille peut aussi être un critère pour la sélection d’un physical +device. En effet une queue capable de traiter les commandes graphiques et opérations mémoire permet d'augmenter +encore un peu les performances. Il sera possible qu’un périphérique supportant Vulkan ne fournisse aucun graphisme, +mais à ce jour toutes les opérations que nous allons utiliser devraient être disponibles. + +### Étape 3 – Surface d’affichage (window surface) et swap chain + +À moins que vous ne soyez intéressé que par le rendu off-screen, vous devrez créer une fenêtre dans laquelle afficher +les éléments. Les fenêtres peuvent être crées avec les APIs spécifiques aux différentes plateformes ou avec des +librairies telles que [GLFW](http://www.glfw.org/) et [SDL](https://www.libsdl.org/). Nous utiliserons GLFW dans ce +tutoriel, mais nous verrons tout cela dans le prochain chapitre. + +Nous avons cependant encore deux composants à évoquer pour afficher quelque chose : une Surface (`VkSurfaceKHR`) et une +Swap Chain (`VkSwapchainKHR`). Remarquez le suffixe «KHR», qui indique que ces fonctionnalités font partie d’une +extension. L'API est elle-même totalement agnostique de la plateforme sur laquelle elle travaille, nous devons donc +utiliser l’extension standard WSI (Window System Interface) pour interagir avec le gestionnaire de fenêtre. La +Surface est une abstraction cross-platform de la fenêtre, et est généralement créée en fournissant une référence à +une fenêtre spécifique à la plateforme, par exemple «HWND» sur Windows. Heureusement pour nous, la librairie GLFW +possède une fonction permettant de gérer tous les détails spécifiques à la plateforme pour nous. + +La swap chain est une collection de cibles sur lesquelles nous pouvons effectuer un rendu. Son but principal est +d’assurer que l’image sur laquelle nous travaillons n’est pas celle utilisée par l’écran. Nous sommes ainsi sûrs que +l’image affichée est complète. Chaque fois que nous voudrons afficher une image nous devrons demander à la swap chain de +nous fournir une cible disponible. Une fois le traitement de la cible terminé, nous la rendrons à la swap chain qui +l’utilisera en temps voulu pour l’affichage à l’écran. Le nombre de cibles et les conditions de leur affichage dépend +du mode utilisé lors du paramétrage de la Swap Chain. Ceux-ci peuvent être le double buffering (synchronisation +verticale) ou le triple buffering. Nous détaillerons tout cela dans le chapitre dédié à la Swap Chain. + +Certaines plateformes permettent d'effectuer un rendu directement à l'écran sans passer par un gestionnaire de fenêtre, +et ce en vous donnant la possibilité de créer une surface qui fait la taille de l'écran. Vous pouvez alors par exemple +créer votre propre gestionnaire de fenêtre. + +### Étape 4 - Image views et framebuffers + +Pour dessiner sur une image originaire de la swap chain, nous devons l'encapsuler dans une `VkImageView` et un +`VkFramebuffer`. Une vue sur une image correspond à une certaine partie de l’image utilisée, et un framebuffer +référence plusieurs vues pour les traiter comme des cible de couleur, de profondeur ou de stencil. Dans la mesure où +il peut y avoir de nombreuses images dans la swap chain, nous créerons en amont les vues et les framebuffers pour +chacune d’entre elles, puis sélectionnerons celle qui nous convient au moment de l’affichage. + +### Étape 5 - Render passes + +Avec Vulkan, une render pass décrit les types d’images utilisées lors du rendu, comment elles sont utilisées et +comment leur contenu doit être traité. Pour notre affichage d’un triangle, nous dirons à Vulkan que nous utilisons une +seule image pour la couleur et que nous voulons qu’elle soit préparée avant l’affichage en la remplissant d’une couleur +opaque. Là où la passe décrit le type d’images utilisées, un framebuffer sert à lier les emplacements utilisés par la +passe à une image complète. + +### Étape 6 - Le pipeline graphique + +Le pipeline graphique est configuré lors de la création d’un `VkPipeline`. Il décrit les éléments paramétrables de la +carte graphique, comme les opérations réalisées par le depth buffer (gestion de la profondeur), et les étapes +programmables à l’aide de `VkShaderModules`. Ces derniers sont créés à partir de byte code. Le driver doit également +être informé des cibles du rendu utilisées dans le pipeline, ce que nous lui disons en référençant la render pass. + +L’une des particularités les plus importantes de Vulkan est que la quasi totalité de la configuration des étapes doit +être réalisée à l’avance. Cela implique que si vous voulez changer un shader ou la conformation des sommets, la +totalité du pipeline doit être recréée. Vous aurez donc probablement de nombreux `VkPipeline` correspondant à toutes +les combinaisons dont votre programme aura besoin. Seules quelques configurations basiques peuvent être changées de +manière dynamique, comme la couleur de fond. Les états doivent aussi être anticipés : il n’y a par exemple pas de +fonction de blending par défaut. + +La bonne nouvelle est que grâce à cette anticipation, ce qui équivaut à peu près à une compilation versus une +interprétation, il y a beaucoup plus d’optimisations possibles pour le driver et le temps d’exécution est plus +prévisible, car les grandes étapes telles le changement de pipeline sont faites très explicites. + +### Étape 7 - Command pools et command buffers + +Comme dit plus haut, nombre d’opérations comme le rendu doivent être transmise à une queue. Ces opérations doivent +d’abord être enregistrées dans un `VkCommandBuffer` avant d’être envoyées. Ces command buffers sont alloués à partir +d’une «`VkCommandPool`» spécifique à une queue family. Pour afficher notre simple triangle nous devrons enregistrer les +opérations suivantes : + +* Lancer la render pass +* Lier le pipeline graphique +* Afficher 3 sommets +* Terminer la passe + +Du fait que l’image que nous avons extraite du framebuffer pour nous en servir comme cible dépend de l’image que la swap +chain nous fournira, nous devons préparer un command buffer pour chaque image possible et choisir le bon au moment de +l’affichage. Nous pourrions en créer un à chaque frame mais ce ne serait pas aussi efficace. + +### Étape 8 - Boucle principale + +Maintenant que nous avons inscrit les commandes graphiques dans des command buffers, la boucle principale n’est plus +qu'une question d’appels. Nous acquérons d’abord une image de la swap chain en utilisant `vkAcquireNextImageKHR`. Nous +sélectionnons ensuite le command buffer approprié pour cette image et le postons à la queue avec `vkQueueSubmit`. Enfin, +nous retournons l’image à la swap chain pour sa présentation à l’écran à l’aide de `vkQueuePresentKHR`. + +Les opérations envoyées à la queue sont exécutées de manière asynchrone. Nous devons donc utiliser des objets de +synchronisation tels que des sémaphores pour nous assurer que les opérations sont exécutées dans l’ordre voulu. +L’exécution du command buffer d’affichage doit de plus attendre que l’acquisition de l’image soit terminée, sinon nous +pourrions dessiner sur une image utilisée pour l’affichage. L’appel à `vkQueuePresentKHR` doit aussi attendre que +l’affichage soit terminé. + +### Résumé + +Ce tour devrait vous donner une compréhension basique du travail que nous aurons à fournir pour afficher notre premier +triangle. Un véritable programme contient plus d’étapes comme allouer des vertex Buffers, créer des Uniform Buffers et +envoyer des textures, mais nous verrons cela dans des chapitres suivants. Nous allons commencer par les bases car Vulkan +a suffisamment d’étapes ainsi. Notez que nous allons "tricher" en écrivant les coordonnées du triangle directement dans +un shader, afin d’éviter l’utilisation d’un vertex buffer qui nécessite une certaine familiarité avec les Command +Buffers. + +En résumé nous devrons, pour afficher un triangle : + +* Créer une `VkInstance` +* Sélectionner une carte graphique compatible (`VkPhysicalDevice`) +* Créer un `VkDevice` et une `VkQueue` pour l’affichage et la présentation +* Créer une fenêtre, une surface dans cette fenêtre et une swap chain +* Considérer les images de la swap chain comme des `VkImageViews` puis des `VkFramebuffers` +* Créer la render pass spécifiant les cibles d’affichage et leurs usages +* Créer des framebuffers pour ces passes +* Générer le pipeline graphique +* Allouer et enregistrer des Command Buffers contenant toutes les commandes pour toutes les images de la swap chain +* Dessiner sur les frames en acquérant une image, en soumettant la commande d’affichage correspondante et en retournant +l’image à la swap chain + +Cela fait beaucoup d’étapes, cependant le but de chacune d’entre elles sera explicitée clairement et simplement dans les +chapitres suivants. Si vous êtes confus quant à l’intérêt d’une étape dans le programme entier, référez-vous à ce +premier chapitre. + +## Concepts de l’API + +Ce chapitre va conclure en survolant la structure de l’API à un plus bas niveau. + +### Conventions + +Toute les fonctions, les énumérations et les structures de Vulkan sont définies dans le header `vulkan.h`, inclus dans +le [SDK Vulkan](https://lunarg.com/vulkan-sdk/) développé par LunarG. Nous verrons comment l’installer dans le prochain +chapitre. + +Les fonctions sont préfixées par ‘vk’, les types comme les énumération et les structures par ‘Vk’ et les macros par +‘VK_’. L’API utilise massivement les structures pour la création d’objet plutôt que de passer des arguments à des +fonctions. Par exemple la création d’objet suit généralement le schéma suivant : + +```c++ +VkXXXCreateInfo createInfo{}; +createInfo.sType = VK_STRUCTURE_TYPE_XXX_CREATE_INFO; +createInfo.pNext = nullptr; +createInfo.foo = ...; +createInfo.bar = ...; + +VkXXX object; +if (vkCreateXXX(&createInfo, nullptr, &object) != VK_SUCCESS) { + std::cerr << "failed to create object" << std::endl; + return false; +} +``` + +De nombreuses structure imposent que l’on spécifie explicitement leur type dans le membre donnée «sType». Le membre +donnée «pNext» peut pointer vers une extension et sera toujours `nullptr` dans ce tutoriel. Les fonctions qui créent ou +détruisent les objets ont un paramètre appelé `VkAllocationCallbacks`, qui vous permettent de spécifier un allocateur. +Nous le mettrons également à `nullptr`. + +La plupart des fonctions retournent un `VkResult`, qui peut être soit `VK_SUCCESS` soit un code d’erreur. La +spécification décrit lesquels chaque fonction renvoie et ce qu’ils signifient. + +### Validation layers + +Vulkan est pensé pour la performance et pour un travail minimal pour le driver. Il inclue donc très peu de gestion +d’erreur et de système de débogage. Le driver crashera beaucoup plus souvent qu’il ne retournera de code d’erreur si +vous faites quelque chose d’inattendu. Pire, il peut fonctionner sur votre carte graphique mais pas sur une autre. + +Cependant, Vulkan vous permet d’effectuer des vérifications précises de chaque élément à l’aide d’une fonctionnalité +nommée «validation layers». Ces layers consistent en du code s’insérant entre l’API et le driver, et permettent de +lancer des analyses de mémoire et de relever les défauts. Vous pouvez les activer pendant le développement et les +désactiver sans conséquence sur la performance. N’importe qui peut écrire ses validation layers, mais celui du SDK de +LunarG est largement suffisant pour ce tutoriel. Vous aurez cependant à écrire vos propres fonctions de callback pour +le traitement des erreurs émises par les layers. + +Du fait que Vulkan soit si explicite pour chaque opération et grâce à l’extensivité des validations layers, trouver les +causes de l’écran noir peut en fait être plus simple qu’avec OpenGL ou Direct3D! + +Il reste une dernière étape avant de commencer à coder : mettre en place +[l’environnement de développement](!fr/Environnement_de_développement). diff --git "a/fr/02_Environnement_de_d\303\251veloppement.md" "b/fr/02_Environnement_de_d\303\251veloppement.md" new file mode 100644 index 00000000..80796d81 --- /dev/null +++ "b/fr/02_Environnement_de_d\303\251veloppement.md" @@ -0,0 +1,520 @@ +Dans ce chapitre nous allons paramétrer votre environnement de développement pour Vulkan et installer des librairies +utiles. Tous les outils que nous allons utiliser, excepté le compilateur, seront compatibles Windows, Linux et MacOS. +Cependant les étapes pour les installer diffèrent un peu, d'où les sections suivantes. + +## Windows + +Si vous développez pour Windows, je partirai du principe que vous utilisez Visual Studio pour ce projet. +Pour un support complet de C++17, il vous faut Visual Studio 2017 or 2019. Les étapes décrites ci-dessous +ont été écrites pour VS 2017. + +### SDK Vulkan + +Le composant central du développement d'applications Vulkan est le SDK. Il inclut les headers, les validation layers +standards, des outils de débogage et un loader pour les fonctions Vulkan. Ce loader récupère les fonctions dans le +driver à l'exécution, comme GLEW pour OpenGL - si cela vous parle. + +Le SDK peut être téléchargé sur [le site de LunarG](https://vulkan.lunarg.com/) en utilisant les boutons en bas de page. +Vous n'avez pas besoin de compte, mais celui-ci vous donne accès à une documentation supplémentaire qui pourra vous être +utile. + +![](/images/vulkan_sdk_download_buttons.png) + +Réalisez l'installation et notez l'emplacement du SDK. La première chose que nous allons faire est vérifier que votre +carte graphique supporte Vulkan. Allez dans le dossier d'installation du SDK, ouvrez le dossier "Bin" et lancez +"vkcube.exe". Vous devriez voire la fenêtre suivante : + +![](/images/cube_demo.png) + +Si vous recevez un message d'erreur assurez-vous que votre driver est à jour, inclut Vulkan et que votre carte graphique +est supportée. Référez-vous au [chapitre introductif](!fr/Introduction) pour les liens vers les principaux constructeurs. + +Il y a d'autres programmes dans ce dossier qui vous seront utiles : "glslangValidator.exe" et "glslc.exe". Nous en aurons besoin pour la +compilation des shaders. Ils transforment un code compréhensible facilement et semblable au C (le +[GLSL](https://en.wikipedia.org/wiki/OpenGL_Shading_Language)) en bytecode. +Nous couvrirons cela dans le chapitre des [modules shader](!fr/Dessiner_un_triangle/Pipeline_graphique_basique/Modules_shaders). +Le dossier "Bin" contient aussi les fichiers binaires du loader Vulkan et des validation layers. Le dossier "Lib" en +contient les librairies. + +Enfin, le dossier "Include" contient les headers Vulkan. Vous pouvez parourir les autres +fichiers, mais nous ne les utiliserons pas dans ce tutoriel. + +### GLFW + +Comme dit précédemment, Vulkan ignore la plateforme sur laquelle il opère, et n'inclut pas d'outil de création +de fenêtre où afficher les résultats de notre travail. Pour bien exploiter les possibilités cross-platform de Vulkan +et éviter les horreurs de Win32, nous utiliserons la [librairie GLFW](http://www.glfw.org/) pour créer une fenêtre et ce +sur Windows, Linux ou MacOS. Il existe d'autres librairies telles que [SDL](https://www.libsdl.org/), mais GLFW a +l'avantage d'abstraire d'autres aspects spécifiques à la plateforme requis par Vulkan. + +Vous pouvez trouver la dernière version de GLFW sur leur site officiel. Nous utiliserons la version 64 bits, mais vous +pouvez également utiliser la version 32 bits. Dans ce cas assurez-vous de bien lier le dossier "Lib32" dans le SDK et +non "Lib". Après avoir téléchargé GLFW, extrayez l'archive à l'emplacement qui vous convient. J'ai choisi de créer un +dossier "Librairies" dans le dossier de Visual Studio. + +![](/images/glfw_directory.png) + +### GLM + +Contrairement à DirectX 12, Vulkan n'intègre pas de librairie pour l'algèbre linéaire. Nous devons donc en télécharger +une. [GLM](http://glm.g-truc.net/) est une bonne librairie conçue pour être utilisée avec les APIs graphiques, et est +souvent utilisée avec OpenGL. + +GLM est une librairie écrite exclusivement dans les headers, il suffit donc d'en télécharger la +[dernière version](https://github.com/g-truc/glm/releases), la stocker où vous le souhaitez et l'inclure là où vous en +aurez besoin. Vous devrez vous trouver avec quelque chose de semblable : + +![](/images/library_directory.png) + +### Préparer Visual Studio + +Maintenant que vous avez installé toutes les dépendances, nous pouvons préparer un projet Visual Studio pour Vulkan, +et écrire un peu de code pour vérifier que tout fonctionne. + +Lancez Visual Studio et créez un nouveau projet "Windows Desktop Wizard", entrez un nom et appuyez sur OK. + +![](/images/vs_new_cpp_project.png) + +Assurez-vous que "Console Application (.exe)" est séléctionné pour le type d'application afin que nous ayons un endroit +où afficher nos messages d'erreur, et cochez "Empty Project" afin que Visual Studio ne génère pas un code de base. + +![](/images/vs_application_settings.png) + +Appuyez sur OK pour créer le projet et ajoutez un fichier source C++. Vous devriez déjà savoir faire ça, mais les étapes +sont tout de même incluses ici. + +![](/images/vs_new_item.png) + +![](/images/vs_new_source_file.png) + +Ajoutez maintenant le code suivant à votre fichier. Ne cherchez pas à en comprendre les tenants et aboutissants, il sert +juste à s'assurer que tout compile correctement et qu'une application Vulkan fonctionne. Nous recommencerons tout depuis +le début dès le chapitre suivant. + +```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\n"; + + glm::mat4 matrix; + glm::vec4 vec; + auto test = matrix * vec; + + while(!glfwWindowShouldClose(window)) { + glfwPollEvents(); + } + + glfwDestroyWindow(window); + + glfwTerminate(); + + return 0; +} +``` + +Configurons maintenant le projet afin de se débarrasser des erreurs. Ouvrez le dialogue des propriétés du projet et +assurez-vous que "All Configurations" est sélectionné, car la plupart des paramètres s'appliquent autant à "Debug" +qu'à "Release". + +![](/images/vs_open_project_properties.png) + +![](/images/vs_all_configs.png) + +Allez à "C++" -> "General" -> "Additional Include Directories" et appuyez sur "" dans le menu déroulant. + +![](/images/vs_cpp_general.png) + +Ajoutez les dossiers pour les headers Vulkan, GLFW et GLM : + +![](/images/vs_include_dirs.png) + +Ensuite, ouvrez l'éditeur pour les dossiers des librairies sous "Linker" -> "General" : + +![](/images/vs_link_settings.png) + +Et ajoutez les emplacements des fichiers objets pour Vulkan et GLFW : + +![](/images/vs_link_dirs.png) + +Allez à "Linker" -> "Input" et appuyez sur "" dans le menu déroulant "Additional Dependencies" : + +![](/images/vs_link_input.png) + +Entrez les noms des fichiers objets GLFW et Vulkan : + +![](/images/vs_dependencies.png) + +Vous pouvez enfin fermer le dialogue des propriétés. Si vous avez tout fait correctement vous ne devriez plus voir +d'erreur dans votre code. + +Assurez-vous finalement que vous compilez effectivement en 64 bits : + +![](/images/vs_build_mode.png) + +Appuyez sur F5 pour compiler et lancer le projet. Vous devriez voir une fenêtre s'afficher comme cela : + +![](/images/vs_test_window.png) + +Si le nombre d'extensions est nul, il y a un problème avec la configuration de Vulkan sur votre système. Sinon, vous +êtes fin prêts à vous [lancer avec Vulkan!](!fr/Dessiner_un_triangle/Mise_en_place/Code_de_base) + +## Linux + +Ces instructions sont conçues pour les utilisateurs d'Ubuntu et Fedora, mais vous devriez pouvoir suivre ces instructions depuis +une autre distribution si vous adaptez les commandes "apt" ou "dnf" à votre propre gestionnaire de +packages. Il vous faut un compilateur qui supporte C++17 (GCC 7+ ou Clang 5+). Vous aurez également besoin de make. + +### Paquets Vulkan + +Les composants les plus importants pour le développement d'applications Vulkan sous Linux sont le loader Vulkan, les validation layers et quelques utilitaires pour tester que votre machine est bien en état de faire fonctionner une application Vulkan: +* `sudo apt install vulkan-tools` ou `sudo dnf install vulkan-tools`: Les utilitaires en ligne de commande, plus précisément `vulkaninfo` et `vkcube`. Lancez ceux-ci pour vérifier le bon fonctionnement de votre machine pour Vulkan. +* `sudo apt install libvulkan-dev` ou `sudo dnf install vulkan-headers vulkan-loader-devel`: Installe le loader Vulkan. Il sert à aller chercher les fonctions auprès du driver de votre GPU au runtime, de la même façon que GLEW le fait pour OpenGL - si vous êtes familier avec ceci. +* `sudo apt install vulkan-validationlayers-dev` ou `sudo dnf install mesa-vulkan-devel vulkan-validation-layers-devel`: Installe les layers de validation standards. Ceux-ci sont cruciaux pour débugger vos applications Vulkan, et nous en reparlerons dans un prochain chapitre. + +Si l'installation est un succès, vous devriez être prêt pour la partie Vulkan. N'oubliez pas de lancer `vkcube` et assurez-vous de voir la fenêtre suivante: + +![](/images/cube_demo_nowindow.png) + +### GLFW + +Comme dit précédemment, Vulkan ignore la plateforme sur laquelle il opère, et n'inclut pas d'outil de création +de fenêtre où afficher les résultats de notre travail. Pour bien exploiter les possibilités cross-platform de +Vulkan, nous utiliserons la [librairie GLFW](http://www.glfw.org/) pour créer une fenêtre sur Windows, Linux +ou MacOS indifféremment. Il existe d'autres librairies telles que [SDL](https://www.libsdl.org/), mais GLFW à +l'avantage d'abstraire d'autres aspects spécifiques à la plateforme requis par Vulkan. + +Nous allons installer GLFW à l'aide de la commande suivante: +```bash +sudo apt install libglfw3-dev +``` +ou +```bash +sudo dnf install glfw-devel +``` + +### GLM + +Contrairement à DirectX 12, Vulkan n'intègre pas de librairie pour l'algèbre linéaire. Nous devons donc en télécharger +une. [GLM](http://glm.g-truc.net/) est une bonne librairie conçue pour être utilisée avec les APIs graphiques, et est +souvent utilisée avec OpenGL. + +Cette librairie contenue intégralement dans les headers peut être installée depuis le package "libglm-dev" ou +"glm-devel" : + +```bash +sudo apt install libglm-dev +``` +ou +```bash +sudo dnf install glm-devel +``` + +### Compilateur de shader + +Nous avons tout ce qu'il nous faut, excepté un programme qui compile le code [GLSL](https://en.wikipedia.org/wiki/OpenGL_Shading_Language) lisible par un humain en bytecode. + +Deux compilateurs de shader populaires sont `glslangValidator` de Khronos et `glslc` de Google. Ce dernier a l'avantage d'être proche de GCC et Clang à l'usage,. +Pour cette raison, nous l'utiliserons: Ubuntu, téléchargez les exécutables [non officiels](https://github.com/google/shaderc/blob/main/downloads.md) et copiez `glslc` dans votre répertoire `/usr/local/bin`. Notez que vous aurez certainement besoin d'utiliser `sudo` en fonctions de vos permissions. Fedora, utilise `sudo dnf install glslc`. +Pour tester, lancez `glslc` depuis le répertoire de votre choix et il devrait se plaindre qu'il n'a reçu aucun shader à compiler de votre part: + +`glslc: error: no input files` + +Nous couvrirons l'usage de `glslc` plus en détails dans le chapitre des [modules shaders](!fr/03_Dessiner_un_triangle/02_Pipeline_graphique_basique/01_Modules_shaders.md) + +### Préparation d'un fichier makefile + +Maintenant que vous avez installé toutes les dépendances, nous pouvons préparer un makefile basique pour Vulkan et +écrire un code très simple pour s'assurer que tout fonctionne correctement. + +Ajoutez maintenant le code suivant à votre fichier. Ne cherchez pas à en comprendre les tenants et aboutissants, il sert +juste à s'assurer que tout compile correctement et qu'une application Vulkan fonctionne. Nous recommencerons tout depuis +le début dès le chapitre suivant. + +```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\n"; + + glm::mat4 matrix; + glm::vec4 vec; + auto test = matrix * vec; + + while(!glfwWindowShouldClose(window)) { + glfwPollEvents(); + } + + glfwDestroyWindow(window); + + glfwTerminate(); + + return 0; +} +``` + +Nous allons maintenant créer un makefile pour compiler et lancer ce code. Créez un fichier "Makefile". Je pars du +principe que vous connaissez déjà les bases de makefile, dont les variables et les règles. Sinon vous pouvez trouver des +introductions claires sur internet, par exemple [ici](https://makefiletutorial.com/). + +Nous allons d'abord définir quelques variables pour simplifier le reste du fichier. +Définissez `CFLAGS`, qui spécifiera les arguments pour la compilation : + +```make +CFLAGS = -std=c++17 -O2 +``` + +Nous utiliserons du C++ moderne (`-std=c++17`) et compilerons avec le paramètre d'optimisation `-O2`. Vous pouvez le retirer pour compiler nos programmes plus rapidement, mais n'oubliez pas de le remettre pour compiler des exécutables prêts à être distribués. + +Définissez de manière analogue `LDFLAGS` : + +```make +LDFLAGS = -lglfw -lvulkan -ldl -lpthread -lX11 -lXxf86vm -lXrandr -lXi +``` + +Le premier flag correspond à GLFW, `-lvulkan` correspond au loader dynamique des fonctions Vulkan. Le reste des options correspondent à des bibliothèques systèmes liés à la gestion des fenêtres et aux threads nécessaire pour le bon fonctionnement de GLFW. + +Spécifier les commandes pour la compilation de "VulkanTest" est désormais un jeu d'enfant. Assurez-vous que vous +utilisez des tabulations et non des espaces pour l'indentation. + +```make +VulkanTest: main.cpp + g++ $(CFLAGS) -o VulkanTest main.cpp $(LDFLAGS) +``` + +Vérifiez que le fichier fonctionne en le sauvegardant et en exécutant make depuis un terminal ouvert dans le +dossier le contenant. Vous devriez avoir un exécutable appelé "VulkanTest". + +Nous allons ensuite définir deux règles, `test` et `clean`. La première exécutera le programme et le second supprimera +l'exécutable : + +```make +.PHONY: test clean + +test: VulkanTest + ./VulkanTest + +clean: + rm -f VulkanTest +``` + +Lancer `make test` doit vous afficher le programme sans erreur, listant le nombre d'extensions disponible pour Vulkan. +L'application devrait retourner le code de retour 0 (succès) quand vous fermez la fenêtre vide. +Vous devriez désormais avoir un makefile ressemblant à ceci : + +```make +CFLAGS = -std=c++17 -O2 +LDFLAGS = -lglfw -lvulkan -ldl -lpthread -lX11 -lXxf86vm -lXrandr -lXi + +VulkanTest: main.cpp + g++ $(CFLAGS) -o VulkanTest main.cpp $(LDFLAGS) + +.PHONY: test clean + +test: VulkanTest + ./VulkanTest + +clean: + rm -f VulkanTest +``` + +Vous pouvez désormais utiliser ce dossier comme exemple pour vos futurs projets Vulkan. +Faites-en une copie, changez le nom du projet pour quelque chose comme `HelloTriangle` et retirez tout le code contenu dans `main.cpp`. + +Bravo, vous êtes fin prêts à vous [lancer avec Vulkan!](!fr/Dessiner_un_triangle/Mise_en_place/Code_de_base) + +## MacOS + +Ces instructions partent du principe que vous utilisez Xcode et le +[gestionnaire de packages Homebrew](https://brew.sh/). Vous aurez besoin de MacOS 10.11 minimum, et votre ordinateur +doit supporter l'[API Metal](https://en.wikipedia.org/wiki/Metal_(API)#Supported_GPUs). + +### Le SDK Vulkan + +Le SDK est le composant le plus important pour programmer une application avec Vulkan. Il inclue headers, validations +layers, outils de débogage et un loader dynamique pour les fonctions Vulkan. Le loader cherche les fonctions dans le +driver pendant l'exécution, comme GLEW pour OpenGL, si cela vous parle. + +Le SDK se télécharge sur le [site de LunarG](https://vulkan.lunarg.com/) en utilisant les boutons en bas de page. Vous +n'avez pas besoin de créer de compte, mais il permet d'accéder à une documentation supplémentaire qui pourra vous être +utile. + +![](/images/vulkan_sdk_download_buttons.png) + +La version MacOS du SDK utilise [MoltenVK](https://moltengl.com/). Il n'y a pas de support natif pour Vulkan sur MacOS, +donc nous avons besoin de MoltenVK pour transcrire les appels à l'API Vulkan en appels au framework Metal d'Apple. +Vous pouvez ainsi exploiter pleinement les possibilités de cet API automatiquement. + + +Une fois téléchargé, extrayez-en le contenu où vous le souhaitez. Dans le dossier extrait, il devrait y avoir un +sous-dossier "Applications" comportant des exécutables lançant des démos du SDK. Lancez "vkcube" pour vérifier que vous +obtenez ceci : + +![](/images/cube_demo_mac.png) + +### GLFW + +Comme dit précédemment, Vulkan ignore la plateforme sur laquelle il opère, et n'inclut pas d'outil de création +de fenêtre où afficher les résultats de notre travail. Pour bien exploiter les possibilités cross-platform de +Vulkan, nous utiliserons la [librairie GLFW](http://www.glfw.org/) pour créer une fenêtre qui supportera Windows, Linux +et MacOS. Il existe d'autres librairies telles que [SDL](https://www.libsdl.org/), mais GLFW à l'avantage d'abstraire +d'autres aspects spécifiques à la plateforme requis par Vulkan. + +Nous utiliserons le gestionnaire de package Homebrew pour installer GLFW. Le support Vulkan sur MacOS n'étant pas +parfaitement disponible (à l'écriture du moins) sur la version 3.2.1, nous installerons le package "glfw3" ainsi : + +```bash +brew install glfw3 --HEAD +``` + +### GLM + +Vulkan n'inclut aucune libraire pour l'algèbre linéaire, nous devons donc en télécharger une. +[GLM](http://glm.g-truc.net/) est une bonne librairie souvent utilisée avec les APIs graphiques dont OpenGL. + +Cette librairie est intégralement codée dans les headers et se télécharge avec le package "glm" : + +```bash +brew install glm +``` + +### Préparation de Xcode + +Maintenant que nous avons toutes les dépendances nous pouvons créer dans Xcode un projet Vulkan basique. La plupart +des opérations seront de la "tuyauterie" pour lier les dépendances au projet. Notez que vous devrez remplacer toutes les +mentions "vulkansdk" par le dossier où vous avez extrait le SDK Vulkan. + +Lancez Xcode et créez un nouveau projet. Sur la fenêtre qui s'ouvre sélectionnez Application > Command Line Tool. + +![](/images/xcode_new_project.png) + +Sélectionnez "Next", inscrivez un nom de projet et choisissez "C++" pour "Language". + +![](/images/xcode_new_project_2.png) + +Appuyez sur "Next" et le projet devrait être créé. Copiez le code suivant à la place du code généré dans le fichier +"main.cpp" : + +```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\n"; + + glm::mat4 matrix; + glm::vec4 vec; + auto test = matrix * vec; + + while(!glfwWindowShouldClose(window)) { + glfwPollEvents(); + } + + glfwDestroyWindow(window); + + glfwTerminate(); + + return 0; +} +``` + +Gardez à l'esprit que vous n'avez pas à comprendre tout ce que le code fait, dans la mesure où il se contente +d'appeler quelques fonctions de l'API pour s'assurer que tout fonctionne. Nous verrons toutes ces fonctions en détail +plus tard. + +Xcode devrait déjà vous afficher des erreurs comme le fait que des librairies soient introuvables. Nous allons +maintenant les faire disparaître. Sélectionnez votre projet sur le menu *Project Navigator*. Ouvrez +*Build Settings* puis : + +* Trouvez le champ **Header Search Paths** et ajoutez "/usr/local/include" (c'est ici que Homebrew installe les headers) +et "vulkansdk/macOS/include" pour le SDK. +* Trouvez le champ **Library Search Paths** et ajoutez "/usr/local/lib" (même raison pour les librairies) et +"vulkansdk/macOS/lib". + +Vous avez normalement (avec des différences évidentes selon l'endroit où vous avez placé votre SDK) : + +![](/images/xcode_paths.png) + +Maintenant, dans le menu *Build Phases*, ajoutez les frameworks "glfw3" et "vulkan" dans **Link Binary With +Librairies**. Pour nous simplifier les choses, nous allons ajouter les librairies dynamiques directement dans le projet +(référez-vous à la documentation de ces librairies si vous voulez les lier de manière statique). + +* Pour glfw ouvrez le dossier "/usr/local/lib" où vous trouverez un fichier avec un nom comme "libglfw.3.x.dylib" où x +est le numéro de la version. Glissez ce fichier jusqu'à la barre des "Linked Frameworks and Librairies" dans Xcode. +* Pour Vulkan, rendez-vous dans "vulkansdk/macOS/lib" et répétez l'opération pour "libvulkan.1.dylib" et "libvulkan.1.x.xx +.dylib" avec les x correspondant à la version du SDK que vous avez téléchargé. + +Maintenant que vous avez ajouté ces librairies, remplissez le champ `Destination` avec "Frameworks" dans **Copy Files**, +supprimez le sous-chemin et décochez "Copy only when installing". Cliquez sur le "+" et ajoutez-y les trois mêmes +frameworks. + +Votre configuration Xcode devrait ressembler à cela : + +![](/images/xcode_frameworks.png) + +Il ne reste plus qu'à définir quelques variables d'environnement. Sur la barre d'outils de Xcode allez à `Product` > +`Scheme` > `Edit Scheme...`, et dans la liste `Arguments` ajoutez les deux variables suivantes : + +* VK_ICD_FILENAMES = `vulkansdk/macOS/share/vulkan/icd.d/MoltenVK_icd.json` +* VK_LAYER_PATH = `vulkansdk/macOS/share/vulkan/explicit_layer.d` + +Vous avez normalement ceci : + +![](/images/xcode_variables.png) + +Vous êtes maintenant prêts! Si vous lancez le projet (en pensant à bien choisir Debug ou Release) vous devrez +avoir ceci : + +![](/images/xcode_output.png) + +Si vous obtenez `0 extensions supported`, il y a un problème avec la configuration de Vulkan sur votre système. Les +autres données proviennent de librairies, et dépendent de votre configuration. + +Vous êtes maintenant prêts à vous [lancer avec Vulkan!](!fr/Dessiner_un_triangle/Mise_en_place/Code_de_base). diff --git a/fr/03_Dessiner_un_triangle/00_Mise_en_place/00_Code_de_base.md b/fr/03_Dessiner_un_triangle/00_Mise_en_place/00_Code_de_base.md new file mode 100644 index 00000000..4411fa7b --- /dev/null +++ b/fr/03_Dessiner_un_triangle/00_Mise_en_place/00_Code_de_base.md @@ -0,0 +1,200 @@ +## Structure générale + +Dans le chapitre précédent nous avons créé un projet Vulkan avec une configuration solide et nous l'avons testé. Nous +recommençons ici à partir du code suivant : + +```c++ +#include + +#include +#include +#include +#include + +class HelloTriangleApplication { +public: + void run() { + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + void initVulkan() { + + } + + void mainLoop() { + + } + + void cleanup() { + + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl;` + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} +``` + +Nous incluons d'abord le header Vulkan du SDK, qui fournit les fonctions, les structures et les énumérations. +`` et `` nous permettront de reporter et de traiter les erreurs. Le header `` nous +servira pour l'écriture d'une lambda dans la section sur la gestion des ressources. `` nous fournit les macros +`EXIT_FAILURE` et `EXIT_SUCCESS` (optionnelles). + +Le programme est écrit à l'intérieur d'une classe, dans laquelle seront stockés les objets Vulkan. Nous avons également +une fonction pour la création de chacun de ces objets. Une fois toute l'initialisation réalisée, nous entrons dans la +boucle principale, qui attend que nous fermions la fenêtre pour quitter le programme, après avoir libéré grâce à la +fonction cleanup toutes les ressources que nous avons allouées . + +Si nous rencontrons une quelconque erreur lors de l'exécution nous lèverons une `std::runtime_error` comportant un +message descriptif, qui sera affiché sur le terminal depuis la fonction `main`. Afin de s'assurer que nous récupérons +bien toutes les erreurs, nous utilisons `std::exception` dans le `catch`. Nous verrons bientôt que la requête de +certaines extensions peut mener à lever des exceptions. + +À peu près tous les chapitres à partir de celui-ci introduiront une nouvelle fonction appelée dans `initVulkan` et un +nouvel objet Vulkan qui sera justement créé par cette fonction. Il sera soit détruit dans `cleanup`, soit libéré +automatiquement. + +## Gestion des ressources + +De la même façon qu'une quelconque ressource explicitement allouée par `new` doit être explicitement libérée par `delete`, nous +devrons explicitement détruire quasiment toutes les ressources Vulkan que nous allouerons. Il est possible d'exploiter +des fonctionnalités du C++ pour s’acquitter automatiquement de cela. Ces possibilités sont localisées dans `` si +vous désirez les utiliser. Cependant nous resterons explicites pour toutes les opérations dans ce tutoriel, car la +puissance de Vulkan réside en particulier dans la clareté de l'expression de la volonté du programmeur. De plus, cela +nous permettra de bien comprendre la durée de vie de chacun des objets. + +Après avoir suivi ce tutoriel vous pourrez parfaitement implémenter une gestion automatique des ressources en +spécialisant `std::shared_ptr` par exemple. L'utilisation du [RAII](https://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization) +à votre avantage est toujours recommandé en C++ pour de gros programmes Vulkan, mais il est quand même bon de +commencer par connaître les détails de l'implémentation. + +Les objets Vulkan peuvent être créés de deux manières. Soit ils sont directement créés avec une fonction du type +`vkCreateXXX`, soit ils sont alloués à l'aide d'un autre objet avec une fonction `vkAllocateXXX`. Après vous +être assuré qu'il n'est plus utilisé où que ce soit, il faut le détruire en utilisant les fonctions +`vkDestroyXXX` ou `vkFreeXXX`, respectivement. Les paramètres de ces fonctions varient sauf pour l'un d'entre eux : +`pAllocator`. Ce paramètre optionnel vous permet de spécifier un callback sur un allocateur de mémoire. Nous +n'utiliserons jamais ce paramètre et indiquerons donc toujours `nullptr`. + +## Intégrer GLFW + +Vulkan marche très bien sans fenêtre si vous voulez l'utiliser pour du rendu sans écran (offscreen rendering en +Anglais), mais c'est tout de même plus intéressant d'afficher quelque chose! Remplacez d'abord la ligne +`#include ` par : + +```c++ +#define GLFW_INCLUDE_VULKAN +#include +``` + +GLFW va alors automatiquement inclure ses propres définitions des fonctions Vulkan et vous fournir le header Vulkan. +Ajoutez une fonction `initWindow` et appelez-la depuis `run` avant les autres appels. Nous utiliserons cette fonction +pour initialiser GLFW et créer une fenêtre. + +```c++ +void run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); +} + +private: + void initWindow() { + + } +``` + +Le premier appel dans `initWindow` doit être `glfwInit()`, ce qui initialise la librairie. Dans la mesure où GLFW a été +créée pour fonctionner avec OpenGL, nous devons lui demander de ne pas créer de contexte OpenGL avec l'appel suivant : + +```c++ +glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); +``` + +Dans la mesure où redimensionner une fenêtre n'est pas chose aisée avec Vulkan, nous verrons cela plus tard et +l'interdisons pour l'instant. + +```c++ +glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); +``` + +Il ne nous reste plus qu'à créer la fenêtre. Ajoutez un membre privé `GLFWWindow* m_window` pour en stocker une +référence, et initialisez la ainsi : + +```c++ +window = glfwCreateWindow(800, 600, "Vulkan", nullptr, nullptr); +``` + +Les trois premiers paramètres indiquent respectivement la largeur, la hauteur et le titre de la fenêtre. Le quatrième +vous permet optionnellement de spécifier un moniteur sur lequel ouvrir la fenêtre, et le cinquième est spécifique à +OpenGL. + +Nous devrions plutôt utiliser des constantes pour la hauteur et la largeur dans la mesure où nous aurons besoin de ces +valeurs dans le futur. J'ai donc ajouté ceci au-dessus de la définition de la classe `HelloTriangleApplication` : + +```c++ +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; +``` + +et remplacé la création de la fenêtre par : + +```c++ +window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); +``` + +Vous avez maintenant une fonction `initWindow` ressemblant à ceci : + +```c++ +void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); +} +``` + +Pour s'assurer que l'application tourne jusqu'à ce qu'une erreur ou un clic sur la croix ne l'interrompe, nous +devons écrire une petite boucle de gestion d'évènements : + +```c++ +void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + } +} +``` + +Ce code est relativement simple. GLFW récupère tous les évènements disponibles, puis vérifie qu'aucun d'entre eux ne +correspond à une demande de fermeture de fenêtre. Ce sera aussi ici que nous appellerons la fonction qui affichera un +triangle. + +Une fois la requête pour la fermeture de la fenêtre récupérée, nous devons détruire toutes les ressources allouées et +quitter GLFW. Voici notre première version de la fonction `cleanup` : + +```c++ +void cleanup() { + glfwDestroyWindow(window); + + glfwTerminate(); +} +``` + +Si vous lancez l'application, vous devriez voir une fenêtre appelée "Vulkan" qui se ferme en cliquant sur la croix. +Maintenant que nous avons une base pour notre application Vulkan, [créons notre premier objet Vulkan!](!fr/Dessiner_un_triangle/Mise_en_place/Instance)! + +[Code C++](/code/00_base_code.cpp) diff --git a/fr/03_Dessiner_un_triangle/00_Mise_en_place/01_Instance.md b/fr/03_Dessiner_un_triangle/00_Mise_en_place/01_Instance.md new file mode 100644 index 00000000..496e840a --- /dev/null +++ b/fr/03_Dessiner_un_triangle/00_Mise_en_place/01_Instance.md @@ -0,0 +1,174 @@ +## Création d'une instance + +La première chose à faire avec Vulkan est son initialisation au travers d'une *instance*. Cette instance relie +l'application à l'API. Pour la créer vous devrez donner quelques informations au driver. + +Créez une fonction `createInstance` et appelez-la depuis la fonction `initVulkan` : + +```c++ +void initVulkan() { + createInstance(); +} +``` + +Ajoutez ensuite un membre donnée représentant cette instance : + +```c++ +private: +VkInstance instance; +``` + +Pour créer l'instance, nous allons d'abord remplir une première structure avec des informations sur notre application. +Ces données sont optionnelles, mais elles peuvent fournir des informations utiles au driver pour optimiser ou +dignostiquer les erreurs lors de l'exécution, par exemple en reconnaissant le nom d'un moteur graphique. Cette structure +s'appelle `VkApplicationInfo` : + +```c++ +void createInstance() { + 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; +} +``` + +Comme mentionné précédemment, la plupart des structures Vulkan vous demandent d'expliciter leur propre type dans le +membre `sType`. Cela permet d'indiquer la version exacte de la structure que nous voulons utiliser : il y aura dans +le futur des extensions à celles-ci. Pour simplifier leur implémentation, les utiliser ne nécessitera que de changer +le type `VK_STRUCTURE_TYPE_XXX` en `VK_STRUCTURE_TYPE_XXX_2` (ou plus de 2) et de fournir une structure complémentaire +à l'aide du pointeur `pNext`. Nous n'utiliserons aucune extension, et donnerons donc toujours `nullptr` à `pNext`. + +Avec Vulkan, nous rencontrerons souvent (TRÈS souvent) des structures à remplir pour passer les informations à Vulkan. +Nous allons maintenant remplir le reste de la structure permettant la création de l'instance. Celle-ci n'est pas +optionnelle. Elle permet d'informer le driver des extensions et des validation layers que nous utiliserons, et ceci +de manière globale. Globale siginifie ici que ces données ne serons pas spécifiques à un périphérique. Nous verrons +la signification de cela dans les chapitres suivants. + +```c++ +VkInstanceCreateInfo createInfo{}; +createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; +createInfo.pApplicationInfo = &appInfo; +``` + +Les deux premiers paramètres sont simples. Les deux suivants spécifient les extensions dont nous aurons besoin. Comme +nous l'avons vu dans l'introduction, Vulkan ne connaît pas la plateforme sur laquelle il travaille, et nous aurons donc +besoin d'extensions pour utiliser des interfaces avec le gestionnaire de fenêtre. GLFW possède une fonction très +pratique qui nous donne la liste des extensions dont nous aurons besoin pour afficher nos résultats. Remplissez donc la +structure de ces données : + +```c++ +uint32_t glfwExtensionCount = 0; +const char** glfwExtensions; + +glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + +createInfo.enabledExtensionCount = glfwExtensionCount; +createInfo.ppEnabledExtensionNames = glfwExtensions; +``` + +Les deux derniers membres de la structure indiquent les validations layers à activer. Nous verrons cela dans le prochain +chapitre, laissez ces champs vides pour le moment : + +```c++ +createInfo.enabledLayerCount = 0; +``` + +Nous avons maintenant indiqué tout ce dont Vulkan a besoin pour créer notre première instance. Nous pouvons enfin +appeler `vkCreateInstance` : + +```c++ +VkResult result = vkCreateInstance(&createInfo, nullptr, &instance); +``` + +Comme vous le reverrez, l'appel à une fonction pour la création d'un objet Vulkan a le prototype suivant : + +* Pointeur sur une structure contenant l'information pour la création +* Pointeur sur une fonction d'allocation que nous laisserons toujours `nullptr` +* Pointeur sur une variable stockant une référence au nouvel objet + +Si tout s'est bien passé, la référence à l'instance devrait être contenue dans le membre `VkInstance`. Quasiment toutes +les fonctions Vulkan retournent une valeur de type VkResult, pouvant être soit `VK_SUCCESS` soit un code d'erreur. Afin +de vérifier si la création de l'instance s'est bien déroulée nous pouvons placer l'appel dans un `if` : + +```c++ +if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("Echec de la création de l'instance!"); +} +``` + +Lancez votre programme pour voir si l'instance s'est créée correctement. + +## Vérification du support des extensions + +Si vous regardez la documentation pour `vkCreateInstance` vous pourrez voir que l'un des messages d'erreur possible est +`VK_ERROR_EXTENSION_NOT_PRESENT`. Nous pourrions juste interrompre le programme et afficher une erreur si une extension +manque. Ce serait logique pour des fonctionnalités cruciales comme l'affichage, mais pas dans le cas d'extensions +optionnelles. + +La fonction `vkEnumerateInstanceExtensionProperties` permet de récupérer la totalité des extensions supportées par le +système avant la création de l'instance. Elle demande un pointeur vers une variable stockant le nombre d'extensions +supportées et un tableau où stocker des informations sur chacune des extensions. Elle possède également un paramètre +optionnel permettant de filtrer les résultats pour une validation layer spécifique. Nous l'ignorerons pour le moment. + +Pour allouer un tableau contenant les détails des extensions nous devons déjà connaître le nombre de ces extensions. +Vous pouvez ne demander que cette information en laissant le premier paramètre `nullptr` : + +```c++ +uint32_t extensionCount = 0; +vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr); +``` + +Nous utiliserons souvent cette méthode. Allouez maintenant un tableau pour stocker les détails des extensions (incluez +) : + +```c++ +std::vector extensions(extensionCount); +``` + +Nous pouvons désormais accéder aux détails des extensions : + +```c++ +vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data()); +``` + +Chacune des structure `VkExtensionProperties` contient le nom et la version maximale supportée de l'extension. Nous +pouvons les afficher à l'aide d'une boucle `for` toute simple (`\t` représente une tabulation) : + +```c++ +std::cout << "Extensions disponibles :\n"; + +for (const auto& extension : extensions) { + std::cout << '\t' << extension.extensionName << '\n'; +} +``` + +Vous pouvez ajouter ce code dans la fonction `createInstance` si vous voulez indiquer des informations à propos du +support Vulkan sur la machine. Petit challenge : programmez une fonction vérifiant si les extensions dont vous avez +besoin (en particulier celles indiquées par GLFW) sont disponibles. + +## Libération des ressources + +L'instance contenue dans `VkInstance` ne doit être détruite qu'à la fin du programme. Nous la détruirons dans la +fonction `cleanup` grâce à la fonction `vkDestroyInstance` : + +```c++ +void cleanup() { + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); +} +``` + +Les paramètres de cette fonction sont évidents. Nous y retrouvons le paramètre pour un désallocateur que nous laissons +`nullptr`. Toutes les ressources que nous allouerons à partir du prochain chapitre devront être libérées avant la +libération de l'instance. + +Avant d'avancer dans les notions plus complexes, créons un moyen de déboger notre programme avec +[les validations layers.](!fr/Dessiner_un_triangle/Mise_en_place/Validation_layers). + +[Code C++](/code/01_instance_creation.cpp) diff --git a/fr/03_Dessiner_un_triangle/00_Mise_en_place/02_Validation_layers.md b/fr/03_Dessiner_un_triangle/00_Mise_en_place/02_Validation_layers.md new file mode 100644 index 00000000..c82b6158 --- /dev/null +++ b/fr/03_Dessiner_un_triangle/00_Mise_en_place/02_Validation_layers.md @@ -0,0 +1,450 @@ +## Que sont les validation layers? + +L'API Vulkan est conçue pour limiter au maximum le travail du driver. Par conséquent il n'y a aucun traitement d'erreur +par défaut. Une erreur aussi simple que se tromper dans la valeur d'une énumération ou passer un pointeur nul comme +argument non optionnel résultent en un crash. Dans la mesure où Vulkan nous demande d'être complètement explicite, il +est facile d'utiliser une fonctionnalité optionnelle et d'oublier de mettre en place l'utilisation de l'extension à +laquelle elle appartient, par exemple. + +Cependant de telles vérifications peuvent être ajoutées à l'API. Vulkan possède un système élégant appelé validation +layers. Ce sont des composants optionnels s'insérant dans les appels des fonctions Vulkan pour y ajouter des opérations. +Voici un exemple d'opérations qu'elles réalisent : + +* Comparer les valeurs des paramètres à celles de la spécification pour détecter une mauvaise utilisation +* Suivre la création et la destruction des objets pour repérer les fuites de mémoire +* Vérifier la sécurité des threads en suivant l'origine des appels +* Afficher toutes les informations sur les appels à l'aide de la sortie standard +* Suivre les appels Vulkan pour créer une analyse dynamique de l'exécution du programme + +Voici ce à quoi une fonction de diagnostic pourrait ressembler : + +```c++ +VkResult vkCreateInstance( + const VkInstanceCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkInstance* instance) { + + if (pCreateInfo == nullptr || instance == nullptr) { + log("Pointeur nul passé à un paramètre obligatoire!"); + return VK_ERROR_INITIALIZATION_FAILED; + } + + return real_vkCreateInstance(pCreateInfo, pAllocator, instance); +} +``` + +Les validation layers peuvent être combinées à loisir pour fournir toutes les fonctionnalités de débogage nécessaires. +Vous pouvez même activer les validations layers lors du développement et les désactiver lors du déploiement sans +aucun problème, sans aucune répercussion sur les performances et même sur l'exécutable! + +Vulkan ne fournit aucune validation layer, mais nous en avons dans le SDK de LunarG. Elles sont complètement [open +source](https://github.com/KhronosGroup/Vulkan-ValidationLayers), vous pouvez donc voir quelles erreurs elles suivent et +contribuer à leur développement. Les utiliser est la meilleure manière d'éviter que votre application fonctionne grâce +à un comportement spécifique à un driver. + +Les validations layers ne sont utilisables que si elles sont installées sur la machine. Il faut le SDK installé et +mis en place pour qu'elles fonctionnent. + +Il a existé deux formes de validation layers : les layers spécifiques à l'instance et celles spécifiques au physical +device (gpu). Elles ne vérifiaient ainsi respectivement que les appels aux fonctions d'ordre global et les appels aux +fonctions spécifiques au GPU. Les layers spécifiques du GPU sont désormais dépréciées. Les autres portent désormais sur +tous les appels. Cependant la spécification recommande encore que nous activions les validations layers au niveau du +logical device, car cela est requis par certaines implémentations. Nous nous contenterons de spécifier les mêmes +layers pour le logical device que pour le physical device, que nous verrons +[plus tard](!fr/Dessiner_un_triangle/Mise_en_place/Logical_device_et_queues). + +## Utiliser les validation layers + +Nous allons maintenant activer les validations layers fournies par le SDK de LunarG. Comme les extensions, nous devons +indiquer leurs nom. Au lieu de devoir spécifier les noms de chacune d'entre elles, nous pouvons les activer à l'aide +d'un nom générique : `VK_LAYER_KHRONOS_validation`. + +Mais ajoutons d'abord deux variables spécifiant les layers à activer et si le programme doit en effet les activer. J'ai +choisi d'effectuer ce choix selon si le programme est compilé en mode debug ou non. La macro `NDEBUG` fait partie +du standard c++ et correspond au second cas. + +```c++ +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +#ifdef NDEBUG + constexpr bool enableValidationLayers = false; +#else + constexpr bool enableValidationLayers = true; +#endif +``` + +Ajoutons une nouvelle fonction `checkValidationLayerSupport`, qui devra vérifier si les layers que nous voulons utiliser +sont disponibles. Listez d'abord les validation layers disponibles à l'aide de la fonction +`vkEnumerateInstanceLayerProperties`. Elle s'utilise de la même façon que `vkEnumerateInstanceExtensionProperties`. + +```c++ +bool checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + return false; +} +``` + +Vérifiez que toutes les layers de `validationLayers` sont présentes dans la liste des layers disponibles. Vous aurez +besoin de `` pour la fonction `strcmp`. + +```c++ +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; +``` + +Nous pouvons maintenant utiliser cette fonction dans `createInstance` : + +```c++ +void createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("les validations layers sont activées mais ne sont pas disponibles!"); + } + + ... +} +``` + +Lancez maintenant le programme en mode debug et assurez-vous qu'il fonctionne. Si vous obtenez une erreur, référez-vous +à la FAQ. + +Modifions enfin la structure `VkCreateInstanceInfo` pour inclure les noms des validation layers à utiliser lorsqu'elles +sont activées : + +```c++ +if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); +} else { + createInfo.enabledLayerCount = 0; +} +``` + +Si l'appel à la fonction `checkValidationLayerSupport` est un succès, `vkCreateInstance` ne devrait jamais retourner +`VK_ERROR_LAYER_NOT_PRESENT`, mais exécutez tout de même le programme pour être sûr que d'autres erreurs n'apparaissent +pas. + +## Fonction de rappel des erreurs + +Les validation layers affichent leur messages dans la console par défaut, mais on peut s'occuper de l'affichage nous-même en fournissant un callback explicite dans notre programme. Ceci nous permet également de choisir quels types de message afficher, car tous ne sont pas des erreurs (fatales). Si vous ne voulez pas vous occuper de ça maintenant, vous pouvez sauter à la dernière section de ce chapitre. + +Pour configurer un callback permettant de s'occuper des messages et des détails associés, nous devons mettre en place un debug messenger avec un callback en utilisant l'extension `VK_EXT_debug_utils`. + +Créons d'abord une fonction `getRequiredExtensions`. Elle nous fournira les extensions nécessaires selon que nous +activons les validation layers ou non : + +```c++ +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_UTILS_EXTENSION_NAME); + } + + return extensions; +} +``` + +Les extensions spécifiées par GLFW seront toujours nécessaires, mais celle pour le débogage n'est ajoutée que +conditionnellement. Remarquez l'utilisation de la macro `VK_EXT_DEBUG_UTILS_EXTENSION_NAME` au lieu du nom de +l'extension pour éviter les erreurs de frappe. + +Nous pouvons maintenant utiliser cette fonction dans `createInstance` : + +```c++ +auto extensions = getRequiredExtensions(); +createInfo.enabledExtensionCount = static_cast(extensions.size()); +createInfo.ppEnabledExtensionNames = extensions.data(); +``` + +Exécutez le programme et assurez-vous que vous ne recevez pas l'erreur `VK_ERROR_EXTENSION_NOT_PRESENT`. Nous ne devrions +pas avoir besoin de vérifier sa présence dans la mesure où les validation layers devraient impliquer son support, +mais sait-on jamais. + +Intéressons-nous maintenant à la fonction de rappel. Ajoutez la fonction statique `debugCallback` à votre classe avec le +prototype `PFN_vkDebugUtilsMessengerCallbackEXT`. `VKAPI_ATTR` et `VKAPI_CALL` assurent une compatibilité avec tous les +compilateurs. + +```c++ +static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback( + VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, + VkDebugUtilsMessageTypeFlagsEXT messageType, + const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, + void* pUserData) { + + std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; + + return VK_FALSE; +} +``` + +Le premier paramètre indique la sévérité du message, et peut prendre les valeurs suivantes : + +* `VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT`: Message de suivi des appels +* `VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT`: Message d'information (allocation d'une ressource...) +* `VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT`: Message relevant un comportement qui n'est pas un bug mais plutôt +une imperfection involontaire +* `VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT`: Message relevant un comportement invalide pouvant mener à un crash + +Les valeurs de cette énumération on été conçues de telle sorte qu'il est possible de les comparer pour vérifier la +sévérité d'un message, par exemple : + +```c++ +if (messageSeverity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) { + // Le message est suffisamment important pour être affiché +} +``` + +Le paramètre `messageType` peut prendre les valeurs suivantes : + +* `VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT` : Un événement quelconque est survenu, sans lien avec les +performances ou la spécification +* `VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT` : Une violation de la spécification ou une potentielle erreur est +survenue +* `VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT` : Utilisation potentiellement non optimale de Vulkan + +Le paramètre `pCallbackData` est une structure du type `VkDebugUtilsMessengerCallbackDataEXT` contenant les détails du +message. Ses membres les plus importants sont : + +* `pMessage`: Le message sous la forme d'une chaîne de type C terminée par le caractère nul `\0` +* `pObjects`: Un tableau d'objets Vulkan liés au message +* `objectCount`: Le nombre d'objets dans le tableau précédent + +Finalement, le paramètre `pUserData` est un pointeur sur une donnée quelconque que vous pouvez spécifier à la création +de la fonction de rappel. + +La fonction de rappel que nous programmons retourne un booléen déterminant si la fonction à l'origine de son appel doit +être interrompue. Si elle retourne `VK_TRUE`, l'exécution de la fonction est interrompue et cette dernière retourne +`VK_ERROR_VALIDATION_FAILED_EXT`. Cette fonctionnalité n'est globalement utilisée que pour tester les validation layers +elles-mêmes, nous retournerons donc invariablement `VK_FALSE`. + +Il ne nous reste plus qu'à fournir notre fonction à Vulkan. Surprenamment, même le messager de débogage se +gère à travers une référence de type `VkDebugUtilsMessengerEXT`, que nous devrons explicitement créer et détruire. Une +telle fonction de rappel est appelée *messager*, et vous pouvez en posséder autant que vous le désirez. Ajoutez un +membre donnée pour le messager sous l'instance : + +```c++ +VkDebugUtilsMessengerEXT callback; +``` + +Ajoutez ensuite une fonction `setupDebugMessenger` et appelez la dans `initVulkan` après `createInstance` : + +```c++ +void initVulkan() { + createInstance(); + setupDebugMessenger(); +} + +void setupDebugMessenger() { + if (!enableValidationLayers) return; + +} +``` + +Nous devons maintenant remplir une structure avec des informations sur le messager : + +```c++ +VkDebugUtilsMessengerCreateInfoEXT createInfo{}; +createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; +createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; +createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; +createInfo.pfnUserCallback = debugCallback; +createInfo.pUserData = nullptr; // Optionnel +``` + +Le champ `messageSeverity` vous permet de filtrer les niveaux de sévérité pour lesquels la fonction de rappel sera +appelée. J'ai laissé tous les types sauf `VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT`, ce qui permet de recevoir +toutes les informations à propos de possibles bugs tout en éliminant la verbose. + +De manière similaire, le champ `messageType` vous permet de filtrer les types de message pour lesquels la fonction de +rappel sera appelée. J'y ai mis tous les types possibles. Vous pouvez très bien en désactiver s'ils ne vous servent à +rien. + +Le champ `pfnUserCallback` indique le pointeur vers la fonction de rappel. + +Vous pouvez optionnellement ajouter un pointeur sur une donnée de votre choix grâce au champ `pUserData`. Le pointeur +fait partie des paramètres de la fonction de rappel. + +Notez qu'il existe de nombreuses autres manières de configurer des messagers auprès des validation layers, mais nous +avons ici une bonne base pour ce tutoriel. Référez-vous à la +[spécification de l'extension](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap50.html#VK_EXT_debug_utils) +pour plus d'informations sur ces possibilités. + +Cette structure doit maintenant être passée à la fonction `vkCreateDebugUtilsMessengerEXT` afin de créer l'objet +`VkDebugUtilsMessengerEXT`. Malheureusement cette fonction fait partie d'une extension non incluse par GLFW. Nous devons +donc gérer son activation nous-mêmes. Nous utiliserons la fonction `vkGetInstancePorcAddr` pous en +récupérer un pointeur. Nous allons créer notre propre fonction - servant de proxy - pour abstraire cela. Je l'ai ajoutée +au-dessus de la définition de la classe `HelloTriangleApplication`. + +```c++ +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pCallback) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pCallback); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} +``` + +La fonction `vkGetInstanceProcAddr` retourne `nullptr` si la fonction n'a pas pu être chargée. Nous pouvons maintenant +utiliser cette fonction pour créer le messager s'il est disponible : + +```c++ +if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &callback) != VK_SUCCESS) { + throw std::runtime_error("le messager n'a pas pu être créé!"); +} +``` + +Le troisième paramètre est l'invariable allocateur optionnel que nous laissons `nullptr`. Les autres paramètres sont +assez logiques. La fonction de rappel est spécifique de l'instance et des validation layers, nous devons donc passer +l'instance en premier argument. Lancez le programme et vérifiez qu'il fonctionne. Vous devriez avoir le résultat +suivant : + +![](/images/validation_layer_test.png) + +qui indique déjà un bug dans notre application! En effet l'objet `VkDebugUtilsMessengerEXT` doit être libéré +explicitement à l'aide de la fonction `vkDestroyDebugUtilsMessagerEXT`. De même qu'avec +`vkCreateDebugUtilsMessangerEXT` nous devons charger dynamiquement cette fonction. Notez qu'il est normal que le +message s'affiche plusieurs fois; il y a plusieurs validation layers, et dans certains cas leurs domaines d'expertise +se recoupent. + +Créez une autre fonction proxy en-dessous de `CreateDebugUtilsMessengerEXT` : + +```c++ +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT callback, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, callback, pAllocator); + } +} +``` + +Nous pouvons maintenant l'appeler dans notre fonction `cleanup` : + +```c++ +void cleanup() { + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, callback, nullptr); + } + + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); +} +``` + +Si vous exécutez le programme maintenant, vous devriez constater que le message n'apparait plus. Si vous voulez voir +quel fonction a lancé un appel au messager, vous pouvez insérer un point d'arrêt dans la fonction de rappel. + +## Déboguer la création et la destruction de l'instance + +Même si nous avons mis en place un système de débogage très efficace, deux fonctions passent sous le radar. Comme il +est nécessaire d'avoir une instance pour appeler `vkCreateDebugUtilsMessengerEXT`, la création de l'instance n'est pas +couverte par le messager. Le même problème apparait avec la destruction de l'instance. + +En lisant +[la documentation](https://github.com/KhronosGroup/Vulkan-Docs/blob/master/appendices/VK_EXT_debug_utils.txt#L120) on +voit qu'il existe un messager spécifiquement créé pour ces deux fonctions. Il suffit de passer un pointeur vers une +instance de `VkDebugUtilsMessengerCreateInfoEXT` au membre `pNext` de `VkInstanceCreateInfo`. Plaçons le remplissage de +la structure de création du messager dans une fonction : + +```c++ +void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; +} +... +void setupDebugMessenger() { + if (!enableValidationLayers) return; + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } +} +``` + +Nous pouvons réutiliser cette fonction dans `createInstance` : + +```c++ +void createInstance() { + ... + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + ... + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; + } else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create instance!"); + } +} +``` + +La variable `debugCreateInfo` est en-dehors du `if` pour qu'elle ne soit pas détruite avant l'appel à +`vkCreateInstance`. La structure fournie à la création de l'instance à travers la structure `VkInstanceCreateInfo` +mènera à la création d'un messager spécifique aux deux fonctions qui sera détruit automatiquement à la destruction de +l'instance. + +## Configuration + +Les validation layers peuvent être paramétrées de nombreuses autres manières que juste avec les informations que nous +avons fournies dans la structure `VkDebugUtilsMessangerCreateInfoEXT`. Ouvrez le SDK Vulkan et rendez-vous dans le +dossier `Config`. Vous y trouverez le fichier `vk_layer_settings.txt` qui vous expliquera comment configurer les +validation layers. + +Pour configurer les layers pour votre propre application, copiez le fichier dans les dossiers `Debug` et/ou `Release`, +puis suivez les instructions pour obtenir le comportement que vous souhaitez. Cependant, pour le reste du tutoriel, je +partirai du principe que vous les avez laissées avec leur comportement par défaut. + +Tout au long du tutoriel je laisserai de petites erreurs intentionnelles pour vous montrer à quel point les validation +layers sont pratiques, et à quel point vous devez comprendre tout ce que vous faites avec Vulkan. Il est maintenant +temps de s'intéresser aux [devices Vulkan dans le système](!fr/Dessiner_un_triangle/Mise_en_place/Physical_devices_et_queue_families). + +[Code C++](/code/02_validation_layers.cpp) diff --git a/fr/03_Dessiner_un_triangle/00_Mise_en_place/03_Physical_devices_et_queue_families.md b/fr/03_Dessiner_un_triangle/00_Mise_en_place/03_Physical_devices_et_queue_families.md new file mode 100644 index 00000000..2ca65359 --- /dev/null +++ b/fr/03_Dessiner_un_triangle/00_Mise_en_place/03_Physical_devices_et_queue_families.md @@ -0,0 +1,346 @@ +## Sélection d'un physical device + +La librairie étant initialisée à travers `VkInstance`, nous pouvons dès à présent chercher et sélectionner une carte +graphique (physical device) dans le système qui supporte les fonctionnalitées dont nous aurons besoin. Nous pouvons en +fait en sélectionner autant que nous voulons et travailler avec chacune d'entre elles, mais nous n'en utiliserons qu'une +dans ce tutoriel pour des raisons de simplicité. + +Ajoutez la fonction `pickPhysicalDevice` et appelez la depuis `initVulkan` : + +```c++ +void initVulkan() { + createInstance(); + setupDebugMessenger(); + pickPhysicalDevice(); +} + +void pickPhysicalDevice() { + +} +``` + +Nous stockerons le physical device que nous aurons sélectionnée dans un nouveau membre donnée de la classe, et celui-ci +sera du type `VkPhysicalDevice`. Cette référence sera implicitement détruit avec l'instance, nous n'avons donc rien à +ajouter à la fonction `cleanup`. + +```c++ +VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; +``` + +Lister les physical devices est un procédé très similaire à lister les extensions. Comme d'habitude, on commence par en +lister le nombre. + +```c++ +uint32_t deviceCount = 0; +vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); +``` + +Si aucun physical device ne supporte Vulkan, il est inutile de continuer l'exécution. + +```c++ +if (deviceCount == 0) { + throw std::runtime_error("aucune carte graphique ne supporte Vulkan!"); +} +``` + +Nous pouvons ensuite allouer un tableau contenant toutes les références aux `VkPhysicalDevice`. + +```c++ +std::vector devices(deviceCount); +vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); +``` + +Nous devons maintenant évaluer chacun des gpus et vérifier qu'ils conviennent pour ce que nous voudrons en faire, car +toutes les cartes graphiques n'ont pas été crées égales. Voici une nouvelle fonction qui fera le travail de +sélection : + +```c++ +bool isDeviceSuitable(VkPhysicalDevice device) { + return true; +} +``` + +Nous allons dans cette fonction vérifier que le physical device respecte nos conditions. + +```c++ +for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + break; + } +} + +if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("aucun GPU ne peut exécuter ce programme!"); +} +``` + +La section suivante introduira les premières contraintes que devront remplir les physical devices. Au fur et à mesure +que nous utiliserons de nouvelles fonctionnalités, nous les ajouterons dans cette fonction. + +## Vérification des fonctionnalités de base + +Pour évaluer la compatibilité d'un physical device nous devons d'abord nous informer sur ses capacités. Des propriétés +basiques comme le nom, le type et les versions de Vulkan supportées peuvent être obtenues en appelant +`vkGetPhysicalDeviceProperties`. + +```c++ +VkPhysicalDeviceProperties deviceProperties; +vkGetPhysicalDeviceProperties(device, &deviceProperties); +``` + +Le support des fonctionnalités optionnelles telles que les textures compressées, les floats de 64 bits et le multi +viewport rendering (pour la VR) s'obtiennent avec `vkGetPhysicalDeviceFeatures` : + +```c++ +VkPhysicalDeviceFeatures deviceFeatures; +vkGetPhysicalDeviceFeatures(device, &deviceFeatures); +``` + +De nombreux autres détails intéressants peuvent être requis, mais nous en remparlerons dans les prochains chapitres. + +Voyons un premier exemple. Considérons que notre application a besoin d'une carte graphique dédiée supportant les +geometry shaders. Notre fonction `isDeviceSuitable` ressemblerait alors à cela : + +```c++ +bool isDeviceSuitable(VkPhysicalDevice device) { + VkPhysicalDeviceProperties deviceProperties; + VkPhysicalDeviceFeatures deviceFeatures; + vkGetPhysicalDeviceProperties(device, &deviceProperties); + vkGetPhysicalDeviceFeatures(device, &deviceFeatures); + + return deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU && + deviceFeatures.geometryShader; +} +``` + +Au lieu de choisir le premier physical device nous convenant, nous pourrions attribuer un score à chacun d'entre eux et +utiliser celui dont le score est le plus élevé. Vous pourriez ainsi préférer une carte graphique dédiée, mais utiliser +un GPU intégré au CPU si le système n'en détecte aucune. Vous pourriez implémenter ce concept comme cela : + +```c++ +#include + +... + +void pickPhysicalDevice() { + ... + + // L'utilisation d'une map permet de les trier automatiquement de manière ascendante + std::multimap candidates; + + for (const auto& device : devices) { + int score = rateDeviceSuitability(device); + candidates.insert(std::make_pair(score, device)); + } + + // Voyons si la meilleure possède les fonctionnalités dont nous ne pouvons nous passer + if (candidates.rbegin()->first > 0) { + physicalDevice = candidates.rbegin()->second; + } else { + throw std::runtime_error("aucun GPU ne peut executer ce programme!"); + } +} + +int rateDeviceSuitability(VkPhysicalDevice device) { + ... + + int score = 0; + + // Les carte graphiques dédiées ont un énorme avantage en terme de performances + if (deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) { + score += 1000; + } + + // La taille maximale des textures affecte leur qualité + score += deviceProperties.limits.maxImageDimension2D; + + // L'application (fictive) ne peut fonctionner sans les geometry shaders + if (!deviceFeatures.geometryShader) { + return 0; + } + + return score; +} +``` + +Vous n'avez pas besoin d'implémenter tout ça pour ce tutoriel, mais faites-le si vous voulez, à titre d'entrainement. +Vous pourriez également vous contenter d'afficher les noms des cartes graphiques et laisser l'utilisateur choisir. + +Nous ne faisons que commencer donc nous prendrons la première carte supportant Vulkan : + +```c++ +bool isDeviceSuitable(VkPhysicalDevice device) { + return true; +} +``` + +Nous discuterons de la première fonctionnalité qui nous sera nécessaire dans la section suivante. + +## Familles de queues (queue families) + +Il a été évoqué que chaque opération avec Vulkan, de l'affichage jusqu'au chargement d'une texture, s'effectue en +ajoutant une commande à une queue. Il existe différentes queues appartenant à différents types de +*queue families*. De plus chaque queue family ne permet que certaines commandes. Il se peut par exemple qu'une queue ne +traite que les commandes de calcul et qu'une autre ne supporte que les commandes d'allocation de mémoire. + +Nous devons analyser quelles queue families existent sur le système et lesquelles correspondent aux commandes que nous +souhaitons utiliser. Nous allons donc créer la fonction `findQueueFamilies` dans laquelle nous chercherons les +commandes nous intéressant. + +Nous allons chercher une queue qui supporte les commandes graphiques, la fonction pourrait ressembler à ça: + +```c++ +uint32_t findQueueFamilies(VkPhysicalDevice device) { + // Code servant à trouver la famille de queue "graphique" +} +``` + +Mais dans un des prochains chapitres, nous allons avoir besoin d'une autre famille de queues, il est donc plus intéressant +de s'y préparer dès maintenant en empactant plusieurs indices dans une structure: + +```c++ +struct QueueFamilyIndices { + uint32_t graphicsFamily; +}; + +QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + // Code pour trouver les indices de familles à ajouter à la structure + return indices +} +``` + +Que se passe-t-il si une famille n'est pas disponible ? On pourrait lancer une exception dans `findQueueFamilies`, +mais cette fonction n'est pas vraiment le bon endroit pour prendre des decisions concernant le choix du bon Device. +Par exemple, on pourrait *préférer* des Devices avec une queue de transfert dédiée, sans toutefois le requérir. +Par conséquent nous avons besoin d'indiquer si une certaine famille de queues à été trouvé. + +Ce n'est pas très pratique d'utiliser une valeur magique pour indiquer la non-existence d'une famille, comme n'importe +quelle valeur de `uint32_t` peut théoriquement être une valeur valide d'index de famille, incluant `0`. +Heureusement, le C++17 introduit un type qui permet la distinction entre le cas où la valeur existe et celui +où elle n'existe pas: + +```c++ +#include + +... + +std::optional graphicsFamily; + +std::cout << std::boolalpha << graphicsFamily.has_value() << std::endl; // faux + +graphicsFamily = 0; + +std::cout << std::boolalpha << graphicsFamily.has_value() << std::endl; // vrai +``` + +`std::optional` est un wrapper qui ne contient aucune valeur tant que vous ne lui en assignez pas une. +Vous pouvez, quelque soit le moment, lui demander si il contient une valeur ou non en appelant sa fonction membre +`has_value()`. On peut donc changer le code comme suit: + +```c++ +#include + +... + +struct QueueFamilyIndices { + std::optional graphicsFamily; +}; + +QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + // Assigne l'index aux familles qui ont pu être trouvées + + return indices; +} +``` + +On peut maintenant commencer à implémenter `findQueueFamilies`: + +```c++ +QueueFamilyIndices findQueueFamily(VkPhysicalDevice) { + QueueFamilyIndices indices; + + ... + + return indices; +} +``` + +Récupérer la liste des queue families disponibles se fait de la même manière que d'habitude, avec la fonction +`vkGetPhysicalDeviceQueueFamilyProperties` : + +```c++ +uint32_t queueFamilyCount = 0; +vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + +std::vector queueFamilies(queueFamilyCount); +vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); +``` + +La structure `VkQueueFamilyProperties` contient des informations sur la queue family, et en particulier le type +d'opérations qu'elle supporte et le nombre de queues que l'on peut instancier à partir de cette famille. Nous devons +trouver au moins une queue supportant `VK_QUEUE_GRAPHICS_BIT` : + +```c++ +int i = 0; +for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + i++; +} +``` + +Nous pouvons maintenant utiliser cette fonction dans `isDeviceSuitable` pour s'assurer que le physical device peut +recevoir les commandes que nous voulons lui envoyer : + +```c++ +bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + return indices.graphicsFamily.has_value(); +} +``` + +Pour que ce soit plus pratique, nous allons aussi ajouter une fonction générique à la structure: + +```c++ +struct QueueFamilyIndices { + std::optional graphicsFamily; + + bool isComplete() { + return graphicsFamily.has_value(); + } +}; + +... + +bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + return indices.isComplete(); +} +``` + +On peut également utiliser ceci pour sortir plus tôt de `findQueueFamilies`: + +```c++ +for (const auto& queueFamily : queueFamilies) { + ... + + if (indices.isComplete()) { + break; + } + + i++; +} +``` + +Bien, c'est tout ce dont nous aurons besoin pour choisir le bon physical device! La prochaine étape est de [créer un +logical device](!fr/Dessiner_un_triangle/Mise_en_place/Logical_device_et_queues) pour créer une interface avec la carte. + +[Code C++](/code/03_physical_device_selection.cpp) diff --git a/fr/03_Dessiner_un_triangle/00_Mise_en_place/04_Logical_device_et_queues.md b/fr/03_Dessiner_un_triangle/00_Mise_en_place/04_Logical_device_et_queues.md new file mode 100644 index 00000000..c6cbdcc6 --- /dev/null +++ b/fr/03_Dessiner_un_triangle/00_Mise_en_place/04_Logical_device_et_queues.md @@ -0,0 +1,159 @@ +## Introduction + +La sélection d'un physical device faite, nous devons générer un *logical device* pour servir d'interface. Le +processus de sa création est similaire à celui de l'instance : nous devons décrire ce dont nous aurons besoin. Nous +devons également spécifier les queues dont nous aurons besoin. Vous pouvez également créer plusieurs logical devices à +partir d'un physical device si vous en avez besoin. + +Commencez par ajouter un nouveau membre donnée pour stocker la référence au logical device. + +```c++ +VkDevice device; +``` + +Ajoutez ensuite une fonction `createLogicalDevice` et appelez-la depuis `initVulkan`. + +```c++ +void initVulkan() { + createInstance(); + setupDebugMessenger(); + pickPhysicalDevice(); + createLogicalDevice(); +} + +void createLogicalDevice() { + +} +``` + +## Spécifier les queues à créer + +La création d'un logical device requiert encore que nous remplissions des informations dans des structures. La +première de ces structures s'appelle `VkDeviceQueueCreateInfo`. Elle indique le nombre de queues que nous désirons pour +chaque queue family. Pour le moment nous n'avons besoin que d'une queue originaire d'une unique queue family : la +première avec un support pour les graphismes. + +```c++ +QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + +VkDeviceQueueCreateInfo queueCreateInfo{}; +queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; +queueCreateInfo.queueFamilyIndex = indices.graphicsFamily.value(); +queueCreateInfo.queueCount = 1; +``` + +Actuellement les drivers ne vous permettent que de créer un petit nombre de queues pour chacune des familles, et vous +n'avez en effet pas besoin de plus. Vous pouvez très bien créer les commandes (command buffers) depuis plusieurs +threads et les soumettre à la queue d'un coup sur le thread principal, et ce sans perte de performance. + +Vulkan permet d'assigner des niveaux de priorité aux queues à l'aide de floats compris entre `0.0` et `1.0`. Vous +pouvez ainsi influencer l'exécution des command buffers. Il est nécessaire d'indiquer une priorité même lorsqu'une +seule queue est présente : + +```c++ +float queuePriority = 1.0f; +queueCreateInfo.pQueuePriorities = &queuePriority; +``` + +## Spécifier les fonctionnalités utilisées + +Les prochaines informations à fournir sont les fonctionnalités du physical device que nous souhaitons utiliser. Ce +sont celles dont nous avons vérifié la présence avec `vkGetPhysicalDeviceFeatures` dans le chapitre précédent. Nous +n'avons besoin de rien de spécial pour l'instant, nous pouvons donc nous contenter de créer la structure et de tout +laisser à `VK_FALSE`, valeur par défaut. Nous reviendrons sur cette structure quand nous ferons des choses plus +intéressantes avec Vulkan. + +```c++ +VkPhysicalDeviceFeatures deviceFeatures{}; +``` + +## Créer le logical device + +Avec ces deux structure prêtes, nous pouvons enfin remplir la structure principale appelée `VkDeviceCreateInfo`. + +```c++ +VkDeviceCreateInfo createInfo{}; +createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; +``` + +Référencez d'abord les structures sur la création des queues et sur les fonctionnalités utilisées : + +```c++ +createInfo.pQueueCreateInfos = &queueCreateInfo; +createInfo.queueCreateInfoCount = 1; + +createInfo.pEnabledFeatures = &deviceFeatures; +``` + +Le reste ressemble à la structure `VkInstanceCreateInfo`. Nous devons spécifier les extensions spécifiques de la +carte graphique et les validation layers. + +Un exemple d'extension spécifique au GPU est `VK_KHR_swapchain`. Celle-ci vous permet de présenter à l'écran les images +sur lesquels votre programme a effectué un rendu. Il est en effet possible que certains GPU ne possèdent pas cette +capacité, par exemple parce qu'ils ne supportent que les compute shaders. Nous reviendrons sur cette extension +dans le chapitre dédié à la swap chain. + +Comme dit dans le chapitre sur les validation layers, nous activerons les mêmes que celles que nous avons spécifiées +lors de la création de l'instance. Nous n'avons pour l'instant besoin d'aucune validation layer en particulier. Notez +que le standard ne fait plus la différence entre les extensions de l'instance et celles du device, au point que les +paramètres `enabledLayerCount` et `ppEnabledLayerNames` seront probablement ignorés. Nous les remplissons quand même +pour s'assurer de la bonne compatibilité avec les anciennes implémentations. + +```c++ +createInfo.enabledExtensionCount = 0; + +if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); +} else { + createInfo.enabledLayerCount = 0; +} +``` + +C'est bon, nous pouvons maintenant instancier le logical device en appelant la fonction `vkCreateDevice`. + +```c++ +if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("échec lors de la création d'un logical device!"); +} +``` + +Les paramètres sont d'abord le physical device dont on souhaite extraire une interface, ensuite la structure contenant +les informations, puis un pointeur optionnel pour l'allocation et enfin un pointeur sur la référence au logical +device créé. Vérifions également si la création a été un succès ou non, comme lors de la création de l'instance. + +Le logical device doit être explicitement détruit dans la fonction `cleanup` avant le physical device : + +```c++ +void cleanup() { + vkDestroyDevice(device, nullptr); + ... +} +``` + +Les logical devices n'interagissent pas directement avec l'instance mais seulement avec le physical device, c'est +pourquoi il n'y a pas de paramètre pour l'instance. + +## Récupérer des références aux queues + +Les queue families sont automatiquement crées avec le logical device. Cependant nous n'avons aucune interface avec +elles. Ajoutez un membre donnée pour stocker une référence à la queue family supportant les graphismes : + +```c++ +VkQueue graphicsQueue; +``` + +Les queues sont implicitement détruites avec le logical device, nous n'avons donc pas à nous en charger dans `cleanup`. + +Nous pourrons ensuite récupérer des références à des queues avec la fonction `vkGetDeviceQueue`. Les paramètres en +sont le logical device, la queue family, l'indice de la queue à récupérer et un pointeur où stocker la référence à la +queue. Nous ne créons qu'une seule queue, nous écrirons donc `0` pour l'indice de la queue. + +```c++ +vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); +``` + +Avec le logical device et les queues nous allons maintenant pouvoir faire travailler la carte graphique! Dans le +prochain chapitre nous mettrons en place les ressources nécessaires à la présentation des images à l'écran. + +[Code C++](/code/04_logical_device.cpp) diff --git "a/fr/03_Dessiner_un_triangle/01_Pr\303\251sentation/00_Window_surface.md" "b/fr/03_Dessiner_un_triangle/01_Pr\303\251sentation/00_Window_surface.md" new file mode 100644 index 00000000..93908382 --- /dev/null +++ "b/fr/03_Dessiner_un_triangle/01_Pr\303\251sentation/00_Window_surface.md" @@ -0,0 +1,206 @@ +## Introduction + +Vulkan ignore la plateforme sur laquelle il opère et ne peut donc pas directement établir d'interface avec le +gestionnaire de fenêtres. Pour créer une interface permettant de présenter les rendus à l'écran, nous devons utiliser +l'extension WSI (Window System Integration). Nous verrons dans ce chapitre l'extension `VK_KHR_surface`, l'une des +extensions du WSI. Nous pourrons ainsi obtenir l'objet `VkSurfaceKHR`, qui est un type abstrait de surface sur +lequel nous pourrons effectuer des rendus. Cette surface sera en lien avec la fenêtre que nous avons créée grâce à GLFW. + +L'extension `VK_KHR_surface`, qui se charge au niveau de l'instance, a déjà été ajoutée, car elle fait partie des +extensions retournées par la fonction `glfwGetRequiredInstanceExtensions`. Les autres fonctions WSI que nous verrons +dans les prochains chapitres feront aussi partie des extensions retournées par cette fonction. + +La surface de fenêtre doit être créée juste après l'instance car elle peut influencer le choix du physical device. +Nous ne nous intéressons à ce sujet que maintenant car il fait partie du grand ensemble que nous abordons et qu'en +parler plus tôt aurait été confus. Il est important de noter que cette surface est complètement optionnelle, et vous +pouvez l'ignorer si vous voulez effectuer du rendu off-screen ou du calculus. Vulkan vous offre ces possibilités sans +vous demander de recourir à des astuces comme créer une fenêtre invisible, là où d'autres APIs le demandaient (cf +OpenGL). + +## Création de la window surface + +Commencez par ajouter un membre donnée `surface` sous le messager. + +```c++ +VkSurfaceKHR surface; +``` + +Bien que l'utilisation d'un objet `VkSurfaceKHR` soit indépendant de la plateforme, sa création ne l'est pas. +Celle-ci requiert par exemple des références à `HWND` et à `HMODULE` sous Windows. C'est pourquoi il existe des +extensions spécifiques à la plateforme, dont par exemple `VK_KHR_win32_surface` sous Windows, mais celles-ci sont +aussi évaluées par GLFW et intégrées dans les extensions retournées par la fonction `glfwGetRequiredInstanceExtensions`. + +Nous allons voir l'exemple de la création de la surface sous Windows, même si nous n'utiliserons pas cette méthode. +Il est en effet contre-productif d'utiliser une librairie comme GLFW et un API comme Vulkan pour se retrouver à écrire +du code spécifique à la plateforme. La fonction de GLFW `glfwCreateWindowSurface` permet de gérer les différences de +plateforme. Cet exemple ne servira ainsi qu'à présenter le travail de bas niveau, dont la connaissance est toujours +utile à une bonne utilisation de Vulkan. + +Une window surface est un objet Vulkan comme un autre et nécessite donc de remplir une structure, ici +`VkWin32SurfaceCreateInfoKHR`. Elle possède deux paramètres importants : `hwnd` et `hinstance`. Ce sont les références +à la fenêtre et au processus courant. + +```c++ +VkWin32SurfaceCreateInfoKHR createInfo{}; +createInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR; +createInfo.hwnd = glfwGetWin32Window(window); +createInfo.hinstance = GetModuleHandle(nullptr); +``` + +Nous pouvons extraire `HWND` de la fenêtre à l'aide de la fonction `glfwGetWin32Window`. La fonction +`GetModuleHandle` fournit une référence au `HINSTANCE` du thread courant. + +La surface peut maintenant être crée avec `vkCreateWin32SurfaceKHR`. Cette fonction prend en paramètre une instance, des +détails sur la création de la surface, l'allocateur optionnel et la variable dans laquelle placer la référence. Bien que +cette fonction fasse partie d'une extension, elle est si communément utilisée qu'elle est chargée par défaut par Vulkan. +Nous n'avons ainsi pas à la charger à la main : + +```c++ +if (vkCreateWin32SurfaceKHR(instance, &createInfo, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("échec de la creation d'une window surface!"); +} +``` + +Ce processus est similaire pour Linux, où la fonction `vkCreateXcbSurfaceKHR` requiert la fenêtre et une connexion à +XCB comme paramètres pour X11. + +La fonction `glfwCreateWindowSurface` implémente donc tout cela pour nous et utilise le code correspondant à la bonne +plateforme. Nous devons maintenant l'intégrer à notre programme. Ajoutez la fonction `createSurface` et appelez-la +dans `initVulkan` après la création de l'instance et du messager : + +```c++ +void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); +} + +void createSurface() { + +} +``` + +L'appel à la fonction fournie par GLFW ne prend que quelques paramètres au lieu d'une structure, ce qui rend le tout +très simple : + +```c++ +void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("échec de la création de la window surface!"); + } +} +``` + +Les paramètres sont l'instance, le pointeur sur la fenêtre, l'allocateur optionnel et un pointeur sur une variable de +type `VkSurfaceKHR`. GLFW ne fournit aucune fonction pour détruire cette surface mais nous pouvons le faire +nous-mêmes avec une simple fonction Vulkan : + +```c++ +void cleanup() { + ... + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + ... + } +``` + +Détruisez bien la surface avant l'instance. + +## Demander le support pour la présentation + +Bien que l'implémentation de Vulkan supporte le WSI, il est possible que d'autres éléments du système ne le supportent +pas. Nous devons donc allonger `isDeviceSuitable` pour s'assurer que le logical device puisse présenter les +rendus à la surface que nous avons créée. La présentation est spécifique aux queues families, ce qui signifie que +nous devons en fait trouver une queue family supportant cette présentation. + +Il est possible que les queue families supportant les commandes d'affichage et celles supportant les commandes de +présentation ne soient pas les mêmes, nous devons donc considérer que ces deux queues sont différentes. En fait, les +spécificités des queues families diffèrent majoritairement entre les vendeurs, et assez peu entre les modèles d'une même +série. Nous devons donc étendre la structure `QueueFamilyIndices` : + +```c++ +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; +``` + +Nous devons ensuite modifier la fonction `findQueueFamilies` pour qu'elle cherche une queue family pouvant supporter +les commandes de présentation. La fonction qui nous sera utile pour cela est `vkGetPhysicalDeviceSurfaceSupportKHR`. +Elle possède quatre paramètres, le physical device, un indice de queue family, la surface et un booléen. Appelez-la +depuis la même boucle que pour `VK_QUEUE_GRAPHICS_BIT` : + +```c++ +VkBool32 presentSupport = false; +vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); +``` + +Vérifiez simplement la valeur du booléen et stockez la queue dans la structure si elle est intéressante : + +```c++ +if (presentSupport) { + indices.presentFamily = i; +} +``` + +Il est très probable que ces deux queue families soient en fait les mêmes, mais nous les traiterons comme si elles +étaient différentes pour une meilleure compatibilité. Vous pouvez cependant ajouter un alorithme préférant des +queues combinées pour améliorer légèrement les performances. + +## Création de la queue de présentation (presentation queue) + +Il nous reste à modifier la création du logical device pour extraire de celui-ci la référence à une presentation queue +`VkQueue`. Ajoutez un membre donnée pour cette référence : + +```c++ +VkQueue presentQueue; +``` + +Nous avons besoin de plusieurs structures `VkDeviceQueueCreateInfo`, une pour chaque queue family. Une manière de +gérer ces structures est d'utiliser un `set` contenant tous les indices des queues et un `vector` pour les structures : + +```c++ +#include + +... + +QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + +std::vector queueCreateInfos; +std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + +float queuePriority = 1.0f; +for (uint32_t 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); +} +``` + +Puis modifiez `VkDeviceCreateInfo` pour qu'il pointe sur le contenu du vector : + +```c++ +createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); +createInfo.pQueueCreateInfos = queueCreateInfos.data(); +``` + +Si les queues sont les mêmes, nous n'avons besoin de les indiquer qu'une seule fois, ce dont le set s'assure. Ajoutez +enfin un appel pour récupérer les queue families : + +```c++ +vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); +``` + +Si les queues sont les mêmes, les variables contenant les références contiennent la même valeur. Dans le prochain +chapitre nous nous intéresserons aux swap chain, et verrons comment elle permet de présenter les rendus à l'écran. + +[Code C++](/code/05_window_surface.cpp) diff --git "a/fr/03_Dessiner_un_triangle/01_Pr\303\251sentation/01_Swap_chain.md" "b/fr/03_Dessiner_un_triangle/01_Pr\303\251sentation/01_Swap_chain.md" new file mode 100644 index 00000000..d12f1c76 --- /dev/null +++ "b/fr/03_Dessiner_un_triangle/01_Pr\303\251sentation/01_Swap_chain.md" @@ -0,0 +1,543 @@ +Vulkan ne possède pas de concept comme le framebuffer par défaut, et nous devons donc créer une infrastructure qui +contiendra les buffers sur lesquels nous effectuerons les rendus avant de les présenter à l'écran. Cette +infrastructure s'appelle _swap chain_ sur Vulkan et doit être créée explicitement. La swap chain est essentiellement +une file d'attente d'images attendant d'être affichées. Notre application devra récupérer une des images de la file, +dessiner dessus puis la retourner à la file d'attente. Le fonctionnement de la file d'attente et les conditions de la +présentation dépendent du paramétrage de la swap chain. Cependant, l'intérêt principal de la swap chain est de +synchroniser la présentation avec le rafraîchissement de l'écran. + +## Vérification du support de la swap chain + +Toutes les cartes graphiques ne sont pas capables de présenter directement les images à l'écran, et ce pour +différentes raisons. Ce pourrait être car elles sont destinées à être utilisées dans un serveur et n'ont aucune +sortie vidéo. De plus, dans la mesure où la présentation est très dépendante du gestionnaire de fenêtres et de la +surface, la swap chain ne fait pas partie de Vulkan "core". Il faudra donc utiliser des extensions, dont +`VK_KHR_swapchain`. + +Pour cela nous allons modifier `isDeviceSuitable` pour qu'elle vérifie si cette extension est supportée. Nous avons +déjà vu comment lister les extensions supportées par un `VkPhysicalDevice` donc cette modification devrait être assez +simple. Notez que le header Vulkan intègre la macro `VK_KHR_SWAPCHAIN_EXTENSION_NAME` qui permet d'éviter une faute +de frappe. Toutes les extensions ont leur macro. + +Déclarez d'abord une liste d'extensions nécessaires au physical device, comme nous l'avons fait pour les validation +layers : + +```c++ +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME +}; +``` + +Créez ensuite une nouvelle fonction appelée `checkDeviceExtensionSupport` et appelez-la depuis `isDeviceSuitable` +comme vérification supplémentaire : + +```c++ +bool isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + return indices.isComplete() && extensionsSupported; +} + +bool checkDeviceExtensionSupport(VkPhysicalDevice device) { + return true; +} +``` + +Énumérez les extensions et vérifiez si toutes les extensions requises en font partie. + +```c++ +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(); +} +``` + +J'ai décidé d'utiliser une collection de strings pour représenter les extensions requises en attente de confirmation. +Nous pouvons ainsi facilement les éliminer en énumérant la séquence. Vous pouvez également utiliser des boucles +imbriquées comme dans `checkValidationLayerSupport`, car la perte en performance n'est pas capitale dans cette phase de +chargement. Lancez le code et vérifiez que votre carte graphique est capable de gérer une swap chain. Normalement +la disponibilité de la queue de présentation implique que l'extension de la swap chain est supportée. Mais soyons +tout de mêmes explicites pour cela aussi. + +## Activation des extensions du device + +L'utilisation de la swap chain nécessite l'extension `VK_KHR_swapchain`. Son activation ne requiert qu'un léger +changement à la structure de création du logical device : + +```c++ +createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); +createInfo.ppEnabledExtensionNames = deviceExtensions.data(); +``` + +Supprimez bien l'ancienne ligne `createInfo.enabledExtensionCount = 0;`. + +## Récupérer des détails à propos du support de la swap chain + +Vérifier que la swap chain est disponible n'est pas suffisant. Nous devons vérifier qu'elle est compatible avec notre +surface de fenêtre. La création de la swap chain nécessite un nombre important de paramètres, et nous devons récupérer +encore d'autres détails pour pouvoir continuer. + +Il y a trois types de propriétés que nous devrons vérifier : + +* Possibilités basiques de la surface (nombre min/max d'images dans la swap chain, hauteur/largeur min/max des images) +* Format de la surface (format des pixels, palette de couleur) +* Mode de présentation disponibles + +Nous utiliserons une structure comme celle dans `findQueueFamilies` pour contenir ces détails une fois qu'ils auront +été récupérés. Les trois catégories mentionnées plus haut se présentent sous la forme de la structure et des listes de +structures suivantes : + +```c++ +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; +``` + +Créons maintenant une nouvelle fonction `querySwapChainSupport` qui remplira cette structure : + +```c++ +SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + return details; +} +``` + +Cette section couvre la récupération des structures. Ce qu'elles signifient sera expliqué dans la section suivante. + +Commençons par les capacités basiques de la texture. Il suffit de demander ces informations et elles nous seront +fournies sous la forme d'une structure du type `VkSurfaceCapabilitiesKHR`. + +```c++ +vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); +``` + +Cette fonction requiert que le physical device et la surface de fenêtre soient passées en paramètres, car elle en +extrait ces capacités. Toutes les fonctions récupérant des capacités de la swap chain demanderont ces paramètres, +car ils en sont les composants centraux. + +La prochaine étape est de récupérer les formats de texture supportés. Comme c'est une liste de structure, cette +acquisition suit le rituel des deux étapes : + +```c++ +uint32_t formatCount; +vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + +if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); +} +``` + +Finalement, récupérer les modes de présentation supportés suit le même principe et utilise +`vkGetPhysicalDeviceSurfacePresentModesKHR` : + +```c++ +uint32_t presentModeCount; +vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + +if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); +} +``` + +Tous les détails sont dans des structures, donc étendons `isDeviceSuitable` une fois de plus et utilisons cette +fonction pour vérifier que le support de la swap chain nous correspond. Nous ne demanderons que des choses très +simples dans ce tutoriel. + +```c++ +bool swapChainAdequate = false; +if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); +} +``` + +Il est important de ne vérifier le support de la swap chain qu'après s'être assuré que l'extension est disponible. La +dernière ligne de la fonction devient donc : + +```c++ +return indices.isComplete() && extensionsSupported && swapChainAdequate; +``` + +## Choisir les bons paramètres pour la swap chain + +Si la fonction `swapChainAdequate` retourne `true` le support de la swap chain est assuré. Il existe cependant encore +plusieurs modes ayant chacun leur intérêt. Nous allons maintenant écrire quelques fonctions qui détermineront les bons +paramètres pour obtenir la swap chain la plus efficace possible. Il y a trois types de paramètres à déterminer : + +* Format de la surface (profondeur de la couleur) +* Modes de présentation (conditions de "l'échange" des images avec l'écran) +* Swap extent (résolution des images dans la swap chain) + +Pour chacun de ces paramètres nous aurons une valeur idéale que nous choisirons si elle est disponible, sinon nous +nous rabattrons sur ce qui nous restera de mieux. + +### Format de la surface + +La fonction utilisée pour déterminer ce paramètre commence ainsi. Nous lui passerons en argument le membre donnée +`formats` de la structure `SwapChainSupportDetails`. + +```c++ +VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + +} +``` + +Chaque `VkSurfaceFormatKHR` contient les données `format` et `colorSpace`. Le `format` indique les canaux de couleur +disponibles et les types qui contiennent les valeurs des gradients. Par exemple `VK_FORMAT_B8G8R8A8_SRGB` signifie que +nous stockons les canaux de couleur R, G, B et A dans cet ordre et en entiers non signés de 8 bits. `colorSpace` permet +de vérifier que le sRGB est supporté en utilisant le champ de bits `VK_COLOR_SPACE_SRGB_NONLINEAR_KHR`. + +Pour l'espace de couleur nous utiliserons sRGB si possible, car il en résulte +[un rendu plus réaliste](http://stackoverflow.com/questions/12524623/). Le format le plus commun est +`VK_FORMAT_B8G8R8A8_SRGB`. + +Itérons dans la liste et voyons si le meilleur est disponible : + +```c++ +for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } +} +``` + +Si cette approche échoue aussi nous pourrions trier les combinaisons disponibles, mais pour rester simple nous +prendrons le premier format disponible. + +```c++ +VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; +} +``` + +### Mode de présentation + +Le mode de présentation est clairement le paramètre le plus important pour la swap chain, car il touche aux +conditions d'affichage des images à l'écran. Il existe quatre modes avec Vulkan : + +* `VK_PRESENT_MODE_IMMEDIATE_KHR` : les images émises par votre application sont directement envoyées à l'écran, ce +qui peut produire des déchirures (tearing). +* `VK_PRESENT_MODE_FIFO_KHR` : la swap chain est une file d'attente, et l'écran récupère l'image en haut de la pile +quand il est rafraîchi, alors que le programme insère ses nouvelles images à l'arrière. Si la queue est pleine le +programme doit attendre. Ce mode est très similaire à la synchronisation verticale utilisée par la plupart des jeux +vidéo modernes. L'instant durant lequel l'écran est rafraichi s'appelle l'*intervalle de rafraîchissement vertical* (vertical blank). +* `VK_PRESENT_MODE_FIFO_RELAXED_KHR` : ce mode ne diffère du précédent que si l'application est en retard et que la +queue est vide pendant le vertical blank. Au lieu d'attendre le prochain vertical blank, une image arrivant dans la +file d'attente sera immédiatement transmise à l'écran. +* `VK_PRESENT_MODE_MAILBOX_KHR` : ce mode est une autre variation du second mode. Au lieu de bloquer l'application +quand le file d'attente est pleine, les images présentes dans la queue sont simplement remplacées par de nouvelles. +Ce mode peut être utilisé pour implémenter le triple buffering, qui vous permet d'éliminer le tearing tout en réduisant +le temps de latence entre le rendu et l'affichage qu'une file d'attente implique. + +Seul `VK_PRESENT_MODE_FIFO_KHR` est toujours disponible. Nous aurons donc encore à écrire une fonction pour réaliser +un choix, car le mode que nous choisirons préférentiellement est `VK_PRESENT_MODE_MAILBOX_KHR` : + +```c++ +VkPresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) { + return VK_PRESENT_MODE_FIFO_KHR; +} +``` + +Je pense que le triple buffering est un très bon compromis. Vérifions si ce mode est disponible : + +```c++ +VkPresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; +} +``` + +### Le swap extent + +Il ne nous reste plus qu'une propriété, pour laquelle nous allons créer encore une autre fonction : + +```c++ +VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + +} +``` + +Le swap extent donne la résolution des images dans la swap chain et correspond quasiment toujours à la résolution de +la fenêtre que nous utilisons. L'étendue des résolutions disponibles est définie dans la +structure `VkSurfaceCapabilitiesKHR`. Vulkan nous demande de faire correspondre notre résolution à celle de la fenêtre +fournie par le membre `currentExtent`. Cependant certains gestionnaires de fenêtres nous permettent de choisir une +résolution différente, ce que nous pouvons détecter grâce aux membres `width` et `height` qui sont alors égaux à la plus +grande valeur d'un `uint32_t`. Dans ce cas nous choisirons la résolution correspondant le mieux à la taille de la +fenêtre, dans les bornes de `minImageExtent` et `maxImageExtent`. + +```c++ +#include // uint32_t +#include // std::numeric_limits +#include // std::clamp + +... + +VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } else { + VkExtent2D actualExtent = {WIDTH, HEIGHT}; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } +} +``` + +La fonction `clamp` est utilisée ici pour limiter les valeurs `WIDTH` et `HEIGHT` entre le minimum et le +maximum supportés par l'implémentation. + +## Création de la swap chain + +Maintenant que nous avons toutes ces fonctions nous pouvons enfin acquérir toutes les informations nécessaires à la +création d'une swap chain. + +Créez une fonction `createSwapChain`. Elle commence par récupérer les résultats des fonctions précédentes. Appelez-la +depuis `initVulkan` après la création du logical device. + +```c++ +void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); +} + +void createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); +} +``` + +Il nous reste une dernière chose à faire : déterminer le nombre d'images dans la swap chain. L'implémentation décide +d'un minimum nécessaire pour fonctionner : + +```c++ +uint32_t imageCount = swapChainSupport.capabilities.minImageCount; +``` + +Se contenter du minimum pose cependant un problème. Il est possible que le driver fasse attendre notre programme car il +n'a pas fini certaines opérations, ce que nous ne voulons pas. Il est recommandé d'utiliser au moins une image de plus +que ce minimum : + +```c++ +uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; +``` + +Il nous faut également prendre en compte le maximum d'images supportées par l'implémentation. La valeur `0` signifie +qu'il n'y a pas de maximum autre que la mémoire. + +```c++ +if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; +} +``` + +Comme la tradition le veut avec Vulkan, la création d'une swap chain nécessite de remplir une grande structure. Elle +commence de manière familière : + +```c++ +VkSwapchainCreateInfoKHR createInfo{}; +createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; +createInfo.surface = surface; +``` + +Après avoir indiqué la surface à laquelle la swap chain doit être liée, les détails sur les images de la swap chain +doivent être fournis : + +```c++ +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; +``` + +Le membre `imageArrayLayers` indique le nombre de couches que chaque image possède. Ce sera toujours `1` sauf si vous +développez une application stéréoscopique 3D. Le champ de bits `imageUsage` spécifie le type d'opérations que nous +appliquerons aux images de la swap chain. Dans ce tutoriel nous effectuerons un rendu directement sur les images, +nous les utiliserons donc comme *color attachement*. Vous voudrez peut-être travailler sur une image séparée pour +pouvoir appliquer des effets en post-processing. Dans ce cas vous devrez utiliser une valeur comme +`VK_IMAGE_USAGE_TRANSFER_DST_BIT` à la place et utiliser une opération de transfert de mémoire pour placer le +résultat final dans une image de la swap chain. + +```c++ +QueueFamilyIndices indices = findQueueFamilies(physicalDevice); +uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + +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.queueFamilyIndexCount = 0; // Optionnel + createInfo.pQueueFamilyIndices = nullptr; // Optionnel +} +``` + +Nous devons ensuite indiquer comment les images de la swap chain seront utilisées dans le cas où plusieurs queues +seront à l'origine d'opérations. Cela sera le cas si la queue des graphismes n'est pas la même que la queue de +présentation. Nous devrons alors dessiner avec la graphics queue puis fournir l'image à la presentation queue. Il +existe deux manières de gérer les images accédées par plusieurs queues : + +* `VK_SHARING_MODE_EXCLUSIVE` : une image n'est accesible que par une queue à la fois et sa gestion doit être +explicitement transférée à une autre queue pour pouvoir être utilisée. Cette option offre le maximum de performances. +* `VK_SHARING_MODE_CONCURRENT` : les images peuvent être simplement utilisées par différentes queue families. + +Si nous avons deux queues différentes, nous utiliserons le mode concurrent pour éviter d'ajouter un chapitre sur la +possession des ressources, car cela nécessite des concepts que nous ne pourrons comprendre correctement que plus tard. +Le mode concurrent vous demande de spécifier à l'avance les queues qui partageront les images en utilisant les +paramètres `queueFamilyIndexCount` et `pQueueFamilyIndices`. Si les graphics queue et presentation queue sont les +mêmes, ce qui est le cas sur la plupart des cartes graphiques, nous devons rester sur le mode exclusif car le mode +concurrent requiert au moins deux queues différentes. + +```c++ +createInfo.preTransform = swapChainSupport.capabilities.currentTransform; +``` + +Nous pouvons spécifier une transformation à appliquer aux images quand elles entrent dans la swap chain si cela est +supporté (à vérifier avec `supportedTransforms` dans `capabilities`), comme par exemple une rotation de 90 degrés ou +une symétrie verticale. Si vous ne voulez pas de transformation, spécifiez la transformation actuelle. + +```c++ +createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; +``` + +Le champ `compositeAlpha` indique si le canal alpha doit être utilisé pour mélanger les couleurs avec celles des autres +fenêtres. Vous voudrez quasiment tout le temps ignorer cela, et indiquer `VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR` : + +```c++ +createInfo.presentMode = presentMode; +createInfo.clipped = VK_TRUE; +``` + +Le membre `presentMode` est assez simple. Si le membre `clipped` est activé avec `VK_TRUE` alors les couleurs des +pixels masqués par d'autres fenêtres seront ignorées. Si vous n'avez pas un besoin particulier de lire ces +informations, vous obtiendrez de meilleures performances en activant ce mode. + +```c++ +createInfo.oldSwapchain = VK_NULL_HANDLE; +``` + +Il nous reste un dernier champ, `oldSwapChain`. Il est possible avec Vulkan que la swap chain devienne +invalide ou mal adaptée pendant que votre application tourne, par exemple parce que la fenêtre a été redimensionnée. +Dans ce cas la swap chain doit être intégralement recréée et une référence à l'ancienne swap chain doit être fournie. +C'est un sujet compliqué que nous aborderons [dans un chapitre futur](!fr/Dessiner_un_triangle/Recréation_de_la_swap_chain). +Pour le moment, considérons que nous ne devrons jamais créer qu'une swap chain. + +Ajoutez un membre donnée pour stocker l'objet `VkSwapchainKHR` : + +```c++ +VkSwapchainKHR swapChain; +``` + +Créer la swap chain ne se résume plus qu'à appeler `vkCreateSwapchainKHR` : + +```c++ +if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw std::runtime_error("échec de la création de la swap chain!"); +} +``` + +Les paramètres sont le logical device, la structure contenant les informations, l'allocateur optionnel et la variable +contenant la référence à la swap chain. Cet objet devra être explicitement détruit à l'aide de la fonction +`vkDestroySwapchainKHR` avant de détruire le logical device : + +```c++ +void cleanup() { + vkDestroySwapchainKHR(device, swapChain, nullptr); + ... +} +``` + +Lancez maintenant l'application et contemplez la création de la swap chain! Si vous obtenez une erreur de violation +d'accès dans `vkCreateSwapchainKHR` ou voyez un message comme `Failed to find 'vkGetInstanceProcAddress' in layer +SteamOverlayVulkanLayer.ddl`, allez voir [la FAQ à propos de la layer Steam](!fr/FAQ). + +Essayez de retirer la ligne `createInfo.imageExtent = extent;` avec les validation layers actives. Vous verrez que +l'une d'entre elles verra l'erreur et un message vous sera envoyé : + +![](/images/swap_chain_validation_layer.png) + +## Récupérer les images de la swap chain + +La swap chain est enfin créée. Il nous faut maintenant récupérer les références aux `VkImage` dans la swap +chain. Nous les utiliserons pour l'affichage et dans les chapitres suivants. Ajoutez un membre donnée pour les +stocker : + +```c++ +std::vector swapChainImages; +``` + +Ces images ont été créées par l'implémentation avec la swap chain et elles seront automatiquement supprimées avec la +destruction de la swap chain, nous n'aurons donc rien à rajouter dans la fonction `cleanup`. + +Ajoutons le code nécessaire à la récupération des références à la fin de `createSwapChain`, juste après l'appel à +`vkCreateSwapchainKHR`. Comme notre logique n'a au final informé Vulkan que d'un minimum pour le nombre d'images dans la +swap chain, nous devons nous enquérir du nombre d'images avant de redimensionner le conteneur. + +```c++ +vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); +swapChainImages.resize(imageCount); +vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); +``` + +Une dernière chose : gardez dans des variables le format et le nombre d'images de la swap chain, nous en aurons +besoin dans de futurs chapitres. + +```c++ +VkSwapchainKHR swapChain; +std::vector swapChainImages; +VkFormat swapChainImageFormat; +VkExtent2D swapChainExtent; + +... + +swapChainImageFormat = surfaceFormat.format; +swapChainExtent = extent; +``` + +Nous avons maintenant un ensemble d'images sur lesquelles nous pouvons travailler et qui peuvent être présentées pour +être affichées. Dans le prochain chapitre nous verrons comment utiliser ces images comme des cibles de rendu, +puis nous verrons le pipeline graphique et les commandes d'affichage! + +[Code C++](/code/06_swap_chain_creation.cpp) diff --git "a/fr/03_Dessiner_un_triangle/01_Pr\303\251sentation/02_Image_views.md" "b/fr/03_Dessiner_un_triangle/01_Pr\303\251sentation/02_Image_views.md" new file mode 100644 index 00000000..f1f7a262 --- /dev/null +++ "b/fr/03_Dessiner_un_triangle/01_Pr\303\251sentation/02_Image_views.md" @@ -0,0 +1,118 @@ +Quelque soit la `VkImage` que nous voulons utiliser, dont celles de la swap chain, nous devons en créer une +`VkImageView` pour la manipuler. Cette image view correspond assez litéralement à une vue dans l'image. Elle décrit +l'accès à l'image et les parties de l'image à accéder. Par exemple elle indique si elle doit être traitée comme une +texture 2D pour la profondeur sans aucun niveau de mipmapping. + +Dans ce chapitre nous écrirons une fonction `createImageViews` pour créer une image view basique pour chacune des +images dans la swap chain, pour que nous puissions les utiliser comme cibles de couleur. + +Ajoutez d'abord un membre donnée pour y stocker une image view : + +```c++ +std::vector swapChainImageViews; +``` + +Créez la fonction `createImageViews` et appelez-la juste après la création de la swap chain. + +```c++ +void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); +} + +void createImageViews() { + +} +``` + +Nous devons d'abord redimensionner la liste pour pouvoir y mettre toutes les image views que nous créerons : + +```c++ +void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + +} +``` + +Créez ensuite la boucle qui parcourra toutes les images de la swap chain. + +```c++ +for (size_t i = 0; i < swapChainImages.size(); i++) { + +} +``` + +Les paramètres pour la création d'image views se spécifient dans la structure `VkImageViewCreateInfo`. Les deux +premiers paramètres sont assez simples : + +```c++ +VkImageViewCreateInfo createInfo{}; +createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; +createInfo.image = swapChainImages[i]; +``` + +Les champs `viewType` et `format` indiquent la manière dont les images doivent être interprétées. Le paramètre +`viewType` permet de traiter les images comme des textures 1D, 2D, 3D ou cube map. + +```c++ +createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; +createInfo.format = swapChainImageFormat; +``` + +Le champ `components` vous permet d'altérer les canaux de couleur. Par exemple, vous pouvez envoyer tous les +canaux au canal rouge pour obtenir une texture monochrome. Vous pouvez aussi donner les valeurs constantes `0` ou `1` +à un canal. Dans notre cas nous garderons les paramètres par défaut. + +```c++ +createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; +createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; +createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; +createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; +``` + +Le champ `subresourceRange` décrit l'utilisation de l'image et indique quelles parties de l'image devraient être +accédées. Notre image sera utilisée comme cible de couleur et n'aura ni mipmapping ni plusieurs couches. + +```c++ +createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; +createInfo.subresourceRange.baseMipLevel = 0; +createInfo.subresourceRange.levelCount = 1; +createInfo.subresourceRange.baseArrayLayer = 0; +createInfo.subresourceRange.layerCount = 1; +``` + +Si vous travailliez sur une application 3D stéréoscopique, vous devrez alors créer une swap chain avec plusieurs +couches. Vous pourriez alors créer plusieurs image views pour chaque image. Elles représenteront ce qui sera affiché +pour l'œil gauche et pour l'œil droit. + +Créer l'image view ne se résume plus qu'à appeler `vkCreateImageView` : + +```c++ +if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { + throw std::runtime_error("échec de la création d'une image view!"); +} +``` + +À la différence des images, nous avons créé les image views explicitement et devons donc les détruire de la même +manière, ce que nous faisons à l'aide d'une boucle : + +```c++ +void cleanup() { + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + + ... +} +``` + +Une image view est suffisante pour commencer à utiliser une image comme une texture, mais pas pour que l'image soit +utilisée comme cible d'affichage. Pour cela nous avons encore une étape, appelée framebuffer. Mais nous devons +d'abord mettre en place le pipeline graphique. + +[Code C++](/code/07_image_views.cpp) diff --git a/fr/03_Dessiner_un_triangle/02_Pipeline_graphique_basique/00_Introduction.md b/fr/03_Dessiner_un_triangle/02_Pipeline_graphique_basique/00_Introduction.md new file mode 100644 index 00000000..7ff76a6a --- /dev/null +++ b/fr/03_Dessiner_un_triangle/02_Pipeline_graphique_basique/00_Introduction.md @@ -0,0 +1,84 @@ +Dans les chapitres qui viennent nous allons configurer une pipeline graphique pour qu'elle affiche notre premier +triangle. La pipeline graphique est l'ensemble des opérations qui prennent les vertices et les textures de vos +éléments et les utilisent pour en faire des pixels sur les cibles d'affichage. Un résumé simplifié ressemble à ceci : + +![](/images/vulkan_simplified_pipeline.svg) + +L'_input assembler_ collecte les données des sommets à partir des buffers que vous avez mis en place, et peut aussi +utiliser un _index buffer_ pour répéter certains éléments sans avoir à stocker deux fois les mêmes données dans un +buffer. + +Le _vertex shader_ est exécuté pour chaque sommet et leur applique en général des transformations pour que leurs +coordonnées passent de l'espace du modèle (model space) à l'espace de l'écran (screen space). Il fournit ensuite des +données à la suite de la pipeline. + +Les _tesselation shaders_ permettent de subdiviser la géométrie selon des règles paramétrables afin d'améliorer la +qualité du rendu. Ce procédé est notamment utilisé pour que des surface comme les murs de briques ou les escaliers +aient l'air moins plats lorsque l'on s'en approche. + +Le _geometry shader_ est invoqué pour chaque primitive (triangle, ligne, points...) et peut les détruire ou en créer +de nouvelles, du même type ou non. Ce travail est similaire au tesselation shader tout en étant beaucoup plus +flexible. Il n'est cependant pas beaucoup utilisé à cause de performances assez moyennes sur les cartes graphiques +(avec comme exception les GPUs intégrés d'Intel). + +La _rasterization_ transforme les primitives en _fragments_. Ce sont les pixels auxquels les primitives correspondent +sur le framebuffer. Tout fragment en dehors de l'écran est abandonné. Les attributs sortant du vertex shader +sont interpolés lorsqu'ils sont donnés aux étapes suivantes. Les fragments cachés par d'autres fragments sont aussi +quasiment toujours éliminés grâce au test de profondeur (depth testing). + +Le _fragment shader_ est invoqué pour chaque fragment restant et détermine à quel(s) framebuffer(s) le fragment +est envoyé, et quelles données y sont inscrites. Il réalise ce travail à l'aide des données interpolées émises par le +vertex shader, ce qui inclut souvent des coordonnées de texture et des normales pour réaliser des calculs d'éclairage. + +Le _color blending_ applique des opérations pour mixer différents fragments correspondant à un même pixel sur le +framebuffer. Les fragments peuvent remplacer les valeurs des autres, s'additionner ou se mélanger selon les +paramètres de transparence (ou plus correctement de translucidité, en anglais translucency). + +Les étapes écrites en vert sur le diagramme s'appellent _fixed-function stages_ (étapes à fonction fixée). Il est +possible de modifier des paramètres influençant les calculs, mais pas de modifier les calculs eux-mêmes. + +Les étapes colorées en orange sont programmables, ce qui signifie que vous pouvez charger votre propre code dans la +carte graphique pour y appliquer exactement ce que vous voulez. Cela vous permet par exemple d'utiliser les fragment +shaders pour implémenter n'importe quoi, de l'utilisation de textures et d'éclairage jusqu'au _ray tracing_. Ces +programmes tournent sur de nombreux coeurs simultanément pour y traiter de nombreuses données en parallèle. + +Si vous avez utilisé d'anciens APIs comme OpenGL ou Direct3D, vous êtes habitués à pouvoir changer un quelconque +paramètre de la pipeline à tout moment, avec des fonctions comme `glBlendFunc` ou `OMSSetBlendState`. Cela n'est plus +possible avec Vulkan. La pipeline graphique y est quasiment fixée, et vous devrez en recréer une complètement si +vous voulez changer de shader, y attacher différents framebuffers ou changer le color blending. Devoir créer une +pipeline graphique pour chacune des combinaisons dont vous aurez besoin tout au long du programme représente un gros +travail, mais permet au driver d'optimiser beaucoup mieux l'exécution des tâches car il sait à l'avance ce que la carte +graphique aura à faire. + +Certaines étapes programmables sont optionnelles selon ce que vous voulez faire. Par exemple la tesselation et le +geometry shader peuvent être désactivés. Si vous n'êtes intéressé que par les valeurs de profondeur vous pouvez +désactiver le fragment shader, ce qui est utile pour les [shadow maps](https://en.wikipedia.org/wiki/Shadow_mapping). + +Dans le prochain chapitre nous allons d'abord créer deux étapes nécessaires à l'affichage d'un triangle à l'écran : +le vertex shader et le fragment shader. Les étapes à fonction fixée seront mises en place dans le chapitre suivant. +La dernière préparation nécessaire à la mise en place de la pipeline graphique Vulkan sera de fournir les framebuffers +d'entrée et de sortie. + +Créez la fonction `createGraphicsPipeline` et appelez-la depuis `initVulkan` après `createImageViews`. Nous +travaillerons sur cette fonction dans les chapitres suivants. + +```c++ +void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createGraphicsPipeline(); +} + +... + +void createGraphicsPipeline() { + +} +``` + +[Code C++](/code/08_graphics_pipeline.cpp) diff --git a/fr/03_Dessiner_un_triangle/02_Pipeline_graphique_basique/01_Modules_shaders.md b/fr/03_Dessiner_un_triangle/02_Pipeline_graphique_basique/01_Modules_shaders.md new file mode 100644 index 00000000..c6474c07 --- /dev/null +++ b/fr/03_Dessiner_un_triangle/02_Pipeline_graphique_basique/01_Modules_shaders.md @@ -0,0 +1,432 @@ +À la différence d'anciens APIs, le code des shaders doit être fourni à Vulkan sous la forme de bytecode et non sous une +forme facilement compréhensible par l'homme, comme [GLSL](https://en.wikipedia.org/wiki/OpenGL_Shading_Language) ou +[HLSL](https://en.wikipedia.org/wiki/High-Level_Shading_Language). Ce format est appelé +[SPIR-V](https://www.khronos.org/spir) et est conçu pour fonctionner avec Vulkan et OpenCL (deux APIs de Khronos). Ce +format peut servir à écrire du code éxécuté sur la carte graphique pour les graphismes et pour le calcul, mais nous +nous concentrerons sur la pipeline graphique dans ce tutoriel. + +L'avantage d'un tel format est que le compilateur spécifique de la carte graphique a beaucoup moins de travail +d'interprétation. L'expérience a en effet montré qu'avec les syntaxes compréhensibles par l'homme, certains +compilateurs étaient très laxistes par rapport à la spécification qui leur était fournie. Si vous écriviez du code +complexe, il pouvait être accepté par l'un et pas par l'autre, ou pire s'éxécuter différemment. Avec le format de +plus bas niveau qu'est SPIR-V, ces problèmes seront normalement éliminés. + +Cela ne veut cependant pas dire que nous devrons écrire ces bytecodes à la main. Khronos fournit même un +compilateur transformant GLSL en SPIR-V. Ce compilateur standard vérifiera que votre code correspond à la spécification. +Vous pouvez également l'inclure comme une bibliothèque pour produire du SPIR-V au runtime, mais nous ne ferons pas cela dans ce tutoriel. +Le compilateur est fourni avec le SDK et s'appelle `glslangValidator`, mais nous allons utiliser un autre compilateur +nommé `glslc`, écrit par Google. L'avantage de ce dernier est qu'il utilise le même format d'options que GCC ou Clang, +et inclu quelques fonctionnalités supplémentaires comme les *includes*. Les deux compilateurs sont fournis dans le SDK, +vous n'avez donc rien de plus à télécharger. + +GLSL est un langage possédant une syntaxe proche du C. Les programmes y ont une fonction `main` invoquée pour chaque +objet à traiter. Plutôt que d'utiliser des paramètres et des valeurs de retour, GLSL utilise des variables globales +pour les entrées et sorties des invocations. Le langage possède des fonctionnalités avancées pour aider le travail +avec les mathématiques nécessaires aux graphismes, avec par exemple des vecteurs, des matrices et des fonctions pour +les traiter. On y trouve des fonctions pour réaliser des produits vectoriels ou des réflexions d'un vecteurs par +rapport à un autre. Le type pour les vecteurs s'appelle `vec` et est suivi d'un nombre indiquant le nombre d'éléments, +par exemple `vec3`. On peut accéder à ses données comme des membres avec par exemple `.y`, mais aussi créer de nouveaux +vecteurs avec plusieurs indications, par exemple `vec3(1.0, 2.0, 3.0).xz` qui crée un `vec2` égal à `(1.0, 3.0)`. +Leurs constructeurs peuvent aussi être des combinaisons de vecteurs et de valeurs. Par exemple il est possible de +créer un `vec3` ainsi : `vec3(vec2(1.0, 2.0), 3.0)`. + +Comme nous l'avons dit au chapitre précédent, nous devrons écrire un vertex shader et un fragment shader pour pouvoir +afficher un triangle à l'écran. Les deux prochaines sections couvrirons ce travail, puis nous verrons comment créer +des bytecodes SPIR-V avec ce code. + +## Le vertex shader + +Le vertex shader traite chaque sommet envoyé depuis le programme C++. Il récupère des données telles la position, la +normale, la couleur ou les coordonnées de texture. Ses sorties sont la position du somment dans l'espace de l'écran et +les autres attributs qui doivent être fournies au reste de la pipeline, comme la couleur ou les coordonnées de texture. +Ces valeurs seront interpolées lors de la rasterization afin de produire un dégradé continu. Ainsi les invocation du +fragment shader recevrons des vecteurs dégradés entre deux sommets. + +Une _clip coordinate_ est un vecteur à quatre éléments émis par le vertex shader. Il est ensuite transformé en une +_normalized screen coordinate_ en divisant ses trois premiers composants par le quatrième. Ces coordonnées sont des +[coordonnées homogènes](https://fr.wikipedia.org/wiki/Coordonn%C3%A9es_homog%C3%A8nes) qui permettent d'accéder au frambuffer +grâce à un repère de [-1, 1] par [-1, 1]. Il ressemble à cela : + +![](/images/normalized_device_coordinates.svg) + +Vous devriez déjà être familier de ces notions si vous avez déjà utilisé des graphismes 3D. Si vous avez utilisé +OpenGL avant vous vous rendrez compte que l'axe Y est maintenenant inversé et que l'axe Z va de 0 à 1, comme Direct3D. + +Pour notre premier triangle nous n'appliquerons aucune transformation, nous nous contenterons de spécifier +directement les coordonnées des trois sommets pour créer la forme suivante : + +![](/images/triangle_coordinates.svg) + +Nous pouvons directement émettre ces coordonnées en mettant leur quatrième composant à 1 de telle sorte que la +division ne change pas les valeurs. + +Ces coordonnées devraient normalement être stockées dans un vertex buffer, mais sa création et son remplissage ne +sont pas des opérations triviales. J'ai donc décidé de retarder ce sujet afin d'obtenir plus rapidement un résultat +visible à l'écran. Nous ferons ainsi quelque chose de peu orthodoxe en attendant : inclure les coordonnées directement +dans le vertex shader. Son code ressemble donc à ceci : + +```glsl +#version 450 + +vec2 positions[3] = vec2[]( + vec2(0.0, -0.5), + vec2(0.5, 0.5), + vec2(-0.5, 0.5) +); + +void main() { + gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); +} +``` + +La fonction `main` est invoquée pour chaque sommet. La variable prédéfinie `gl_VertexIndex` contient l'index du +sommet à l'origine de l'invocation du `main`. Elle est en général utilisée comme index dans le vertex buffer, mais nous +l'emploierons pour déterminer la coordonnée à émettre. Cette coordonnée est extraite d'un tableau prédéfini à trois +entrées, et est combinée avec un `z` à 0.0 et un `w` à 1.0 pour faire de la division une identité. La variable +prédéfinie `gl_Position` fonctionne comme sortie pour les coordonnées. + +## Le fragment shader + +Le triangle formé par les positions émises par le vertex shader remplit un certain nombre de fragments. Le fragment +shader est invoqué pour chacun d'entre eux et produit une couleur et une profondeur, qu'il envoie à un ou plusieurs +framebuffer(s). Un fragment shader colorant tout en rouge est ainsi écrit : + +```glsl +#version 450 + +layout(location = 0) out vec4 outColor; + +void main() { + outColor = vec4(1.0, 0.0, 0.0, 1.0); +} +``` + +Le `main` est appelé pour chaque fragment de la même manière que le vertex shader est appelé pour chaque sommet. Les +couleurs sont des vecteurs de quatre composants : R, G, B et le canal alpha. Les valeurs doivent être incluses dans +[0, 1]. Au contraire de `gl_Position`, il n'y a pas (plus exactement il n'y a plus) de variable prédéfinie dans +laquelle entrer la valeur de la couleur. Vous devrez spécifier votre propre variable pour contenir la couleur du +fragment, où `layout(location = 0)` indique l'index du framebuffer où la couleur sera écrite. Ici, la couleur rouge est +écrite dans `outColor` liée au seul et unique premier framebuffer. + +## Une couleur pour chaque vertex + +Afficher ce que vous voyez sur cette image ne serait pas plus intéressant qu'un triangle entièrement rouge? + +![](/images/triangle_coordinates_colors.png) + +Nous devons pour cela faire quelques petits changements aux deux shaders. Spécifions d'abord une couleur distincte +pour chaque sommet. Ces couleurs seront inscrites dans le vertex shader de la même manière que les positions : + +```glsl +vec3 colors[3] = vec3[]( + vec3(1.0, 0.0, 0.0), + vec3(0.0, 1.0, 0.0), + vec3(0.0, 0.0, 1.0) +); +``` + +Nous devons maintenant passer ces couleurs au fragment shader afin qu'il puisse émettre des valeurs interpolées et +dégradées au framebuffer. Ajoutez une variable de sortie pour la couleur dans le vertex shader et donnez lui une +valeur dans le `main`: + +```glsl +layout(location = 0) out vec3 fragColor; + +void main() { + gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); + fragColor = colors[gl_VertexIndex]; +} +``` + +Nous devons ensuite ajouter l'entrée correspondante dans le fragment shader, dont la valeur sera l'interpolation +correspondant à la position du fragment pour lequel le shader sera invoqué : + +```glsl +layout(location = 0) in vec3 fragColor; + +void main() { + outColor = vec4(fragColor, 1.0); +} +``` + +Les deux variables n'ont pas nécessairement le même nom, elles seront reliées selon l'index fourni dans la directive +`location`. La fonction `main` doit être modifiée pour émettre une couleur possédant un canal alpha. Le résultat +montré dans l'image précédente est dû à l'interpolation réalisée lors de la rasterization. + +## Compilation des shaders + +Créez un dossier `shaders` à la racine de votre projet, puis enregistrez le vertex shader dans un fichier appelé +`shader.vert` et le fragment shader dans un fichier appelé `shader.frag`. Les shaders en GLSL n'ont pas d'extension +officielle mais celles-ci correspondent à l'usage communément accepté. + +Le contenu de `shader.vert` devrait être: + +```glsl +#version 450 + +out gl_PerVertex { + vec4 gl_Position; +}; + +layout(location = 0) out vec3 fragColor; + +vec2 positions[3] = vec2[]( + vec2(0.0, -0.5), + vec2(0.5, 0.5), + vec2(-0.5, 0.5) +); + +vec3 colors[3] = vec3[]( + vec3(1.0, 0.0, 0.0), + vec3(0.0, 1.0, 0.0), + vec3(0.0, 0.0, 1.0) +); + +void main() { + gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); + fragColor = colors[gl_VertexIndex]; +} +``` + +Et `shader.frag` devrait contenir : + +```glsl +#version 450 + +layout(location = 0) in vec3 fragColor; + +layout(location = 0) out vec4 outColor; + +void main() { + outColor = vec4(fragColor, 1.0); +} +``` + +Nous allons maintenant compiler ces shaders en bytecode SPIR-V à l'aide du programme `glslc`. + +**Windows** + +Créez un fichier `compile.bat` et copiez ceci dedans : + +```bash +C:/VulkanSDK/x.x.x.x/Bin32/glslc.exe shader.vert -o vert.spv +C:/VulkanSDK/x.x.x.x/Bin32/glslc.exe shader.frag -o frag.spv +pause +``` + +Corrigez le chemin vers `glslc.exe` pour que le .bat pointe effectivement là où le vôtre se trouve. +Double-cliquez pour lancer ce script. + +**Linux** + +Créez un fichier `compile.sh` et copiez ceci dedans : + +```bash +/home/user/VulkanSDK/x.x.x.x/x86_64/bin/glslc shader.vert -o vert.spv +/home/user/VulkanSDK/x.x.x.x/x86_64/bin/glslc shader.frag -o frag.spv +``` + +Corrigez le chemin menant au `glslc` pour qu'il pointe là où il est. Rendez le script exécutable avec la +commande `chmod +x compile.sh` et lancez-le. + +**Fin des instructions spécifiques** + +Ces deux commandes instruisent le compilateur de lire le code GLSL source contenu dans un fichier et d'écrire +le bytecode SPIR-V dans un fichier grâce à l'option `-o` (output). + +Si votre shader contient une erreur de syntaxe le compilateur vous indiquera le problème et la ligne à laquelle il +apparait. Essayez de retirer un point-virgule et voyez l'efficacité du debogueur. Essayez également de voir les +arguments supportés. Il est possible de le forcer à émettre le bytecode sous un format compréhensible permettant de +voir exactement ce que le shader fait et quelles optimisations le compilateur y a réalisées. + +La compilation des shaders en ligne de commande est l'une des options les plus simples et les plus évidentes. C'est ce +que nous utiliserons dans ce tutoriel. Sachez qu'il est également possible de compiler les shaders depuis votre code. Le +SDK inclue la librairie [libshaderc](https://github.com/google/shaderc) , qui permet de compiler le GLSL en SPIR-V +depuis le programme C++. + +## Charger un shader + +Maintenant que vous pouvez créer des shaders SPIR-V il est grand temps de les charger dans le programme et de les +intégrer à la pipeline graphique. Nous allons d'abord écrire une fonction qui réalisera le chargement des données +binaires à partir des fichiers. + +```c++ +#include + +... + +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(std::string {"échec de l'ouverture du fichier "} + filename + "!"); + } +} +``` + +La fonction `readFile` lira tous les octets du fichier qu'on lui indique et les retournera dans un `vector` de +caractères servant ici d'octets. L'ouverture du fichier se fait avec deux paramètres particuliers : +* `ate` : permet de commencer la lecture à la fin du fichier +* `binary` : indique que le fichier doit être lu comme des octets et que ceux-ci ne doivent pas être formatés + +Commencer la lecture à la fin permet d'utiliser la position du pointeur comme indicateur de la taille totale du +fichier et nous pouvons ainsi allouer un stockage suffisant : + +```c++ +size_t fileSize = (size_t) file.tellg(); +std::vector buffer(fileSize); +``` +Après cela nous revenons au début du fichier et lisons tous les octets d'un coup : + +```c++ +file.seekg(0); +file.read(buffer.data(), fileSize); +``` + +Nous pouvons enfin fermer le fichier et retourner les octets : + +```c++ +file.close(); + +return buffer; +``` + +Appelons maintenant cette fonction depuis `createGraphicsPipeline` pour charger les bytecodes des deux shaders : + +```c++ +void createGraphicsPipeline() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); +} +``` + +Assurez-vous que les shaders soient correctement chargés en affichant la taille des fichiers lus depuis votre +programme puis en comparez ces valeurs à la taille des fichiers indiquées par l'OS. Notez que le code n'a pas besoin +d'avoir un caractère nul en fin de chaîne car nous indiquerons à Vulkan sa taille exacte. + +## Créer des modules shader + +Avant de passer ce code à la pipeline nous devons en faire un `VkShaderModule`. Créez pour cela une fonction +`createShaderModule`. + +```c++ +VkShaderModule createShaderModule(const std::vector& code) { + +} +``` + +Cette fonction prendra comme paramètre le buffer contenant le bytecode et créera un `VkShaderModule` avec ce code. + +La création d'un module shader est très simple. Nous avons juste à indiquer un pointeur vers le buffer et la taille +de ce buffer. Ces informations seront inscrites dans la structure `VkShaderModuleCreatInfo`. Le seul problème est que +la taille doit être donnée en octets mais le pointeur sur le code est du type `uint32_t` et non du type `char`. Nous +devrons donc utiliser `reinterpet_cast` sur notre pointeur. Cet opérateur de conversion nécessite que les données +aient un alignement compatible avec `uint32_t`. Heuresement pour nous l'objet allocateur de la classe `std::vector` +s'assure que les données satisfont le pire cas d'alignement. + +```c++ +VkShaderModuleCreateInfo createInfo{}; +createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; +createInfo.codeSize = code.size(); +createInfo.pCode = reinterpret_cast(code.data()); +``` + +Le `VkShaderModule` peut alors être créé en appelant la fonction `vkCreateShaderModule` : + + +```c++ +VkShaderModule shaderModule; +if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { + throw std::runtime_error("échec de la création d'un module shader!"); +} +``` + +Les paramètres sont les mêmes que pour la création des objets précédents : le logical device, le pointeur sur la +structure avec les informations, le pointeur vers l'allocateur optionnnel et la référence à l'objet créé. Le buffer +contenant le code peut être libéré immédiatement après l'appel. Retournez enfin le shader module créé : + +```c++ +return shaderModule; +``` + +Les modules shaders ne sont au fond qu'une fine couche autour du byte code chargé depuis les fichiers. Au moment de la +création de la pipeline, les codes des shaders sont compilés et mis sur la carte. Nous pouvons donc détruire les modules +dès que la pipeline est crée. Nous en ferons donc des variables locales à la fonction `createGraphicsPipeline` : + +```c++ +void createGraphicsPipeline() { + auto vertShaderModule = createShaderModule(vertShaderCode); + fragShaderModule = createShaderModule(fragShaderCode); + + vertShaderModule = createShaderModule(vertShaderCode); + fragShaderModule = createShaderModule(fragShaderCode); +``` + +Ils doivent être libérés une fois que la pipeline est créée, juste avant que `createGraphicsPipeline` ne retourne. +Ajoutez ceci à la fin de la fonction : + + +```c++ + ... + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); +} +``` + +Le reste du code de ce chapitre sera ajouté entre les deux parties de la fonction présentés ci-dessus. + +## Création des étapes shader + +Nous devons assigner une étape shader aux modules que nous avons crées. Nous allons utiliser une structure du type +`VkPipelineShaderStageCreateInfo` pour cela. + +Nous allons d'abord remplir cette structure pour le vertex shader, une fois de plus dans la fonction +`createGraphicsPipeline`. + +```c++ +VkPipelineShaderStageCreateInfo vertShaderStageInfo{}; +vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; +vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; +``` + +La première étape, sans compter le membre `sType`, consiste à dire à Vulkan à quelle étape le shader sera utilisé. Il +existe une valeur d'énumération pour chacune des étapes possibles décrites dans le chapitre précédent. + +```c++ +vertShaderStageInfo.module = vertShaderModule; +vertShaderStageInfo.pName = "main"; +``` + +Les deux membres suivants indiquent le module contenant le code et la fonction à invoquer en *entrypoint*. Il est donc +possible de combiner plusieurs fragment shaders dans un seul module et de les différencier à l'aide de leurs points +d'entrée. Nous nous contenterons du `main` standard. + +Il existe un autre membre, celui-ci optionnel, appelé `pSpecializationInfo`, que nous n'utiliserons pas mais qu'il +est intéressant d'évoquer. Il vous permet de donner des valeurs à des constantes présentes dans le code du shader. +Vous pouvez ainsi configurer le comportement d'un shader lors de la création de la pipeline, ce qui est plus efficace +que de le faire pendant l'affichage, car alors le compilateur (qui n'a toujours pas été invoqué!) peut éliminer des +pants entiers de code sous un `if` vérifiant la valeur d'une constante ainsi configurée. Si vous n'avez aucune +constante mettez ce paramètre à `nullptr`. + +Modifier la structure pour qu'elle corresponde au fragment shader est très simple : + +```c++ +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"; +``` + +Intégrez ces deux valeurs dans un tableau que nous utiliserons plus tard et vous aurez fini ce chapitre! + +```c++ +VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; +``` + +C'est tout ce que nous dirons sur les étapes programmables de la pipeline. Dans le prochain chapitre nous verrons les +étapes à fonction fixée. + +[Code C++](/code/09_shader_modules.cpp) / +[Vertex shader](/code/09_shader_base.vert) / +[Fragment shader](/code/09_shader_base.frag) diff --git "a/fr/03_Dessiner_un_triangle/02_Pipeline_graphique_basique/02_Fonctions_fix\303\251es.md" "b/fr/03_Dessiner_un_triangle/02_Pipeline_graphique_basique/02_Fonctions_fix\303\251es.md" new file mode 100644 index 00000000..8cf9da12 --- /dev/null +++ "b/fr/03_Dessiner_un_triangle/02_Pipeline_graphique_basique/02_Fonctions_fix\303\251es.md" @@ -0,0 +1,380 @@ +Les anciens APIs définissaient des configurations par défaut pour toutes les étapes à fonction fixée de la pipeline +graphique. Avec Vulkan vous devez être explicite dans ce domaine également et devrez donc configurer la fonction de +mélange par exemple. Dans ce chapitre nous remplirons toutes les structures nécessaires à la configuration des étapes à +fonction fixée. + +## Entrée des sommets + +La structure `VkPipelineVertexInputStateCreateInfo` décrit le format des sommets envoyés au vertex shader. Elle +fait cela de deux manières : + +* Liens (bindings) : espace entre les données et information sur ces données; sont-elles par sommet ou par instance? +(voyez [l'instanciation](https://en.wikipedia.org/wiki/Geometry_instancing)) +* Descriptions d'attributs : types d'attributs passés au vertex shader, de quels bindings les charger et avec quel +décalage entre eux. + +Dans la mesure où nous avons écrit les coordonnées directement dans le vertex shader, nous remplirons cette structure +en indiquant qu'il n'y a aucune donnée à charger. Nous y reviendrons dans le chapitre sur les vertex buffers. + +```c++ +VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; +vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; +vertexInputInfo.vertexBindingDescriptionCount = 0; +vertexInputInfo.pVertexBindingDescriptions = nullptr; // Optionnel +vertexInputInfo.vertexAttributeDescriptionCount = 0; +vertexInputInfo.pVertexAttributeDescriptions = nullptr; // Optionnel +``` + +Les membres `pVertexBindingDescriptions` et `pVertexAttributeDescriptions` pointent vers un tableau de structures +décrivant les détails du chargement des données des sommets. Ajoutez cette structure à la fonction +`createGraphicsPipeline` juste après le tableau `shaderStages`. + +## Input assembly + +La structure `VkPipelineInputAssemblyStateCreateInfo` décrit la nature de la géométrie voulue quand les sommets sont +reliés, et permet d'activer ou non la réévaluation des vertices. La première information est décrite dans le membre +`topology` et peut prendre ces valeurs : + +* `VK_PRIMITIVE_TOPOLOGY_POINT_LIST` : chaque sommet est un point +* `VK_PRIMITIVE_TOPOLOGY_LINE_LIST` : dessine une ligne liant deux sommet en n'utilisant ces derniers qu'une seule fois +* `VK_PRIMITIVE_TOPOLOGY_LINE_STRIP` : le dernier sommet de chaque ligne est utilisée comme premier sommet +pour la ligne suivante +* `VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST` : dessine un triangle en utilisant trois sommets, sans en réutiliser pour le +triangle suivant +* `VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP ` : le deuxième et troisième sommets sont utilisées comme les deux premiers +pour le triangle suivant + +Les sommets sont normalement chargés séquentiellement depuis le vertex buffer. Avec un _element buffer_ vous pouvez +cependant choisir vous-même les indices à charger. Vous pouvez ainsi réaliser des optimisations, comme n'utiliser +une combinaison de sommet qu'une seule fois au lieu de d'avoir les mêmes données plusieurs fois dans le buffer. Si +vous mettez le membre `primitiveRestartEnable` à la valeur `VK_TRUE`, il devient alors possible d'interrompre les +liaisons des vertices pour les modes `_STRIP` en utilisant l'index spécial `0xFFFF` ou `0xFFFFFFFF`. + +Nous n'afficherons que des triangles dans ce tutoriel, nous nous contenterons donc de remplir la structure de +cette manière : + +```c++ +VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; +inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; +inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; +inputAssembly.primitiveRestartEnable = VK_FALSE; +``` + +## Viewports et ciseaux + +Un viewport décrit simplement la région d'un framebuffer sur laquelle le rendu sera effectué. Il couvrira dans la +pratique quasiment toujours la totalité du framebuffer, et ce sera le cas dans ce tutoriel. + +```c++ +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; +``` + +N'oubliez pas que la taille des images de la swap chain peut différer des macros `WIDTH` et `HEIGHT`. Les images de +la swap chain seront plus tard les framebuffers sur lesquels la pipeline opérera, ce que nous devons prendre en compte +en donnant les dimensions dynamiquement acquises. + +Les valeurs `minDepth` et `maxDepth` indiquent l'étendue des valeurs de profondeur à utiliser pour le frambuffer. Ces +valeurs doivent être dans `[0.0f, 1.0f]` mais `minDepth` peut être supérieure à `maxDepth`. Si vous ne faites rien de +particulier contentez-vous des valeurs `0.0f` et `1.0f`. + +Alors que les viewports définissent la transformation de l'image vers le framebuffer, les rectangles de ciseaux +définissent la région de pixels qui sera conservée. Tout pixel en dehors des rectangles de ciseaux seront +éliminés par le rasterizer. Ils fonctionnent plus comme un filtre que comme une transformation. Les différence sont +illustrée ci-dessous. Notez que le rectangle de ciseau dessiné sous l'image de gauche n'est qu'une des possibilités : +tout rectangle plus grand que le viewport aurait fonctionné. + +![](/images/viewports_scissors.png) + +Dans ce tutoriel nous voulons dessiner sur la totalité du framebuffer, et ce sans transformation. Nous +définirons donc un rectangle de ciseaux couvrant tout le frambuffer : + +```c++ +VkRect2D scissor{}; +scissor.offset = {0, 0}; +scissor.extent = swapChainExtent; +``` + +Le viewport et le rectangle de ciseau se combinent en un *viewport state* à l'aide de la structure +`VkPipelineViewportStateCreateInfo`. Il est possible sur certaines cartes graphiques d'utiliser plusieurs viewports +et rectangles de ciseaux, c'est pourquoi la structure permet d'envoyer des tableaux de ces deux données. +L'utilisation de cette possibilité nécessite de l'activer au préalable lors de la création du logical device. + +```c++ +VkPipelineViewportStateCreateInfo viewportState{}; +viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; +viewportState.viewportCount = 1; +viewportState.pViewports = &viewport; +viewportState.scissorCount = 1; +viewportState.pScissors = &scissor; +``` + +## Rasterizer + +Le rasterizer récupère la géométrie définie par des sommets et calcule les fragments qu'elle recouvre. Ils sont ensuite +traités par le fragment shaders. Il réalise également un +[test de profondeur](https://en.wikipedia.org/wiki/Z-buffering), le +[face culling](https://en.wikipedia.org/wiki/Back-face_culling) et le test de ciseau pour vérifier si le fragment doit +effectivement être traité ou non. Il peut être configuré pour émettre des fragments remplissant tous les polygones ou +bien ne remplissant que les cotés (wireframe rendering). Tout cela se configure dans la structure +`VkPipelineRasterizationStateCreateInfo`. + +```c++ +VkPipelineRasterizationStateCreateInfo rasterizer{}; +rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; +rasterizer.depthClampEnable = VK_FALSE; +``` + +Si le membre `depthClampEnable` est mis à `VK_TRUE`, les fragments au-delà des plans near et far ne pas supprimés +mais affichés à cette distance. Cela est utile dans quelques situations telles que les shadow maps. Cela aussi doit +être explicitement activé lors de la mise en place du logical device. + +```c++ +rasterizer.rasterizerDiscardEnable = VK_FALSE; +``` + +Si le membre `rasterizerDiscardEnable` est mis à `VK_TRUE`, aucune géométrie ne passe l'étape du rasterizer, ce qui +désactive purement et simplement toute émission de donnée vers le frambuffer. + +```c++ +rasterizer.polygonMode = VK_POLYGON_MODE_FILL; +``` + +Le membre `polygonMode` définit la génération des fragments pour la géométrie. Les modes suivants sont disponibles : + +* `VK_POLYGON_MODE_FILL` : remplit les polygones de fragments +* `VK_POLYGON_MODE_LINE` : les côtés des polygones sont dessinés comme des lignes +* `VK_POLYGON_MODE_POINT` : les sommets sont dessinées comme des points + +Tout autre mode que fill doit être activé lors de la mise en place du logical device. + +```c++ +rasterizer.lineWidth = 1.0f; +``` + +Le membre `lineWidth` définit la largeur des lignes en terme de fragments. La taille maximale supportée dépend du GPU +et pour toute valeur autre que `1.0f` l'extension `wideLines` doit être activée. + +```c++ +rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; +rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; +``` + +Le membre `cullMode` détermine quel type de face culling utiliser. Vous pouvez désactiver tout ce filtrage, +n'éliminer que les faces de devant, que celles de derrière ou éliminer toutes les faces. Le membre `frontFace` +indique l'ordre d'évaluation des vertices pour dire que la face est devant ou derrière, qui est le sens des +aiguilles d'une montre ou le contraire. + +```c++ +rasterizer.depthBiasEnable = VK_FALSE; +rasterizer.depthBiasConstantFactor = 0.0f; // Optionnel +rasterizer.depthBiasClamp = 0.0f; // Optionnel +rasterizer.depthBiasSlopeFactor = 0.0f; // Optionnel +``` + +Le rasterizer peut altérer la profondeur en y ajoutant une valeur constante ou en la modifiant selon l'inclinaison du +fragment. Ces possibilités sont parfois exploitées pour le shadow mapping mais nous ne les utiliserons pas. Laissez +`depthBiasEnabled` à la valeur `VK_FALSE`. + +## Multisampling + +La structure `VkPipelineMultisampleCreateInfo` configure le multisampling, l'un des outils permettant de réaliser +[l'anti-aliasing](https://en.wikipedia.org/wiki/Multisample_anti-aliasing). Le multisampling combine les résultats +d'invocations du fragment shader sur des fragments de différents polygones qui résultent au même pixel. Cette +superposition arrive plutôt sur les limites entre les géométries, et c'est aussi là que les problèmes visuels de +hachage arrivent le plus. Dans la mesure où le fragment shader n'a pas besoin d'être invoqué plusieurs fois si seul un +polygone correspond à un pixel, cette approche est beaucoup plus efficace que d'augmenter la résolution de la texture. +Son utilisation nécessite son activation au niveau du GPU. + +```c++ +VkPipelineMultisampleStateCreateInfo multisampling{}; +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; // Optionnel +multisampling.pSampleMask = nullptr; // Optionnel +multisampling.alphaToCoverageEnable = VK_FALSE; // Optionnel +multisampling.alphaToOneEnable = VK_FALSE; // Optionnel +``` + +Nous reverrons le multisampling plus tard, pour l'instant laissez-le désactivé. + +## Tests de profondeur et de pochoir + +Si vous utilisez un buffer de profondeur (depth buffer) et/ou de pochoir (stencil buffer) vous devez configurer les +tests de profondeur et de pochoir avec la structure `VkPipelineDepthStencilStateCreateInfo`. Nous n'avons aucun de +ces buffers donc nous indiquerons `nullptr` à la place d'une structure. Nous y reviendrons au chapitre sur le depth +buffering. + +## Color blending + +La couleur donnée par un fragment shader doit être combinée avec la couleur déjà présente dans le framebuffer. Cette +opération s'appelle color blending et il y a deux manières de la réaliser : + +* Mélanger linéairement l'ancienne et la nouvelle couleur pour créer la couleur finale +* Combiner l'ancienne et la nouvelle couleur à l'aide d'une opération bit à bit + +Il y a deux types de structures pour configurer le color blending. La première, +`VkPipelineColorBlendAttachmentState`, contient une configuration pour chaque framebuffer et la seconde, +`VkPipelineColorBlendStateCreateInfo` contient les paramètres globaux pour ce color blending. Dans notre cas nous +n'avons qu'un seul framebuffer : + +```c++ +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; +colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; // Optionnel +colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO; // Optionnel +colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; // Optionnel +colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; // Optionnel +colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; // Optionnel +colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; // Optionnel +``` + +Cette structure spécifique de chaque framebuffer vous permet de configurer le color blending. L'opération sera +effectuée à peu près comme ce pseudocode le montre : + +```c++ +if (blendEnable) { + finalColor.rgb = (srcColorBlendFactor * newColor.rgb) (dstColorBlendFactor * oldColor.rgb); + finalColor.a = (srcAlphaBlendFactor * newColor.a) (dstAlphaBlendFactor * oldColor.a); +} else { + finalColor = newColor; +} + +finalColor = finalColor & colorWriteMask; +``` + +Si `blendEnable` vaut `VK_FALSE` la nouvelle couleur du fragment shader est inscrite dans le framebuffer sans +modification et sans considération de la valeur déjà présente dans le framebuffer. Sinon les deux opérations de +mélange sont exécutées pour former une nouvelle couleur. Un AND binaire lui est appliquée avec `colorWriteMask` pour +déterminer les canaux devant passer. + +L'utilisation la plus commune du mélange de couleurs utilise le canal alpha pour déterminer l'opacité du matériau et +donc le mélange lui-même. La couleur finale devrait alors être calculée ainsi : + +```c++ +finalColor.rgb = newAlpha * newColor + (1 - newAlpha) * oldColor; +finalColor.a = newAlpha.a; +``` + +Avec cette méthode la valeur alpha correspond à une pondération pour la nouvelle valeur par rapport à l'ancienne. Les +paramètres suivants permettent de faire exécuter ce calcul : + +```c++ +colorBlendAttachment.blendEnable = VK_TRUE; +colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; +colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; +colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; +colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; +colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; +colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; +``` + +Vous pouvez trouver toutes les opérations possibles dans les énumérations `VkBlendFactor` et `VkBlendOp` dans la +spécification. + +La seconde structure doit posséder une référence aux structures spécifiques des framebuffers. Vous pouvez également y +indiquer des constantes utilisables lors des opérations de mélange que nous venons de voir. + +```c++ +VkPipelineColorBlendStateCreateInfo colorBlending{}; +colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; +colorBlending.logicOpEnable = VK_FALSE; +colorBlending.logicOp = VK_LOGIC_OP_COPY; // Optionnel +colorBlending.attachmentCount = 1; +colorBlending.pAttachments = &colorBlendAttachment; +colorBlending.blendConstants[0] = 0.0f; // Optionnel +colorBlending.blendConstants[1] = 0.0f; // Optionnel +colorBlending.blendConstants[2] = 0.0f; // Optionnel +colorBlending.blendConstants[3] = 0.0f; // Optionnel +``` + +Si vous voulez utiliser la seconde méthode de mélange (la combinaison bit à bit) vous devez indiquer `VK_TRUE` au +membre `logicOpEnable` et déterminer l'opération dans `logicOp`. Activer ce mode de mélange désactive automatiquement +la première méthode aussi radicalement que si vous aviez indiqué `VK_FALSE` au membre `blendEnable` de la +précédente structure pour chaque framebuffer. Le membre `colorWriteMask` sera également utilisé dans ce second mode pour +déterminer les canaux affectés. Il est aussi possible de désactiver les deux modes comme nous l'avons fait ici. Dans +ce cas les résultats des invocations du fragment shader seront écrits directement dans le framebuffer. + +## États dynamiques + +Un petit nombre d'états que nous avons spécifiés dans les structures précédentes peuvent en fait être altérés +sans avoir à recréer la pipeline. On y trouve la taille du viewport, la largeur des lignes et les constantes de mélange. +Pour cela vous devrez remplir la structure `VkPipelineDynamicStateCreateInfo` comme suit : + +```c++ +std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_LINE_WIDTH +}; + +VkPipelineDynamicStateCreateInfo dynamicState{}; +dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; +dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); +dynamicState.pDynamicStates = dynamicStates.data(); +``` + +Les valeurs données lors de la configuration seront ignorées et vous devrez en fournir au moment du rendu. Nous y +reviendrons plus tard. Cette structure peut être remplacée par `nullptr` si vous ne voulez pas utiliser de dynamisme +sur ces états. + +## Pipeline layout + +Les variables `uniform` dans les shaders sont des données globales similaires aux états dynamiques. Elles doivent +être déterminées lors du rendu pour altérer les calculs des shaders sans avoir à les recréer. Elles sont très utilisées +pour fournir les matrices de transformation au vertex shader et pour créer des samplers de texture dans les fragment +shaders. + +Ces variables doivent être configurées lors de la création de la pipeline en créant une variable +de type `VkPipelineLayout`. Même si nous n'en utilisons pas dans nos shaders actuels nous devons en créer un vide. + +Créez un membre donnée pour stocker la structure car nous en aurons besoin plus tard. + +```c++ +VkPipelineLayout pipelineLayout; +``` + +Créons maintenant l'objet dans la fonction `createGraphicsPipline` : + +```c++ +VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; +pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; +pipelineLayoutInfo.setLayoutCount = 0; // Optionnel +pipelineLayoutInfo.pSetLayouts = nullptr; // Optionnel +pipelineLayoutInfo.pushConstantRangeCount = 0; // Optionnel +pipelineLayoutInfo.pPushConstantRanges = nullptr; // Optionnel + +if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { + throw std::runtime_error("échec de la création du pipeline layout!"); +} +``` + +Cette structure informe également sur les _push constants_, une autre manière de passer des valeurs dynamiques au +shaders que nous verrons dans un futur chapitre. Le pipeline layout sera utilisé pendant toute la durée du +programme, nous devons donc le détruire dans la fonction `cleanup` : + +```c++ +void cleanup() { + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + ... +} +``` + +## Conclusion + +Voila tout ce qu'il y a à savoir sur les étapes à fonction fixée! Leur configuration représente un gros travail +mais nous sommes au courant de tout ce qui se passe dans la pipeline graphique, ce qui réduit les chances de +comportement imprévu à cause d'un paramètre par défaut oublié. + +Il reste cependant encore un objet à créer avant du finaliser la pipeline graphique. Cet objet s'appelle +[passe de rendu](!fr/Dessiner_un_triangle/Pipeline_graphique_basique/Render_pass). + +[Code C++](/code/10_fixed_functions.cpp) / +[Vertex shader](/code/09_shader_base.vert) / +[Fragment shader](/code/09_shader_base.frag) diff --git a/fr/03_Dessiner_un_triangle/02_Pipeline_graphique_basique/03_Render_pass.md b/fr/03_Dessiner_un_triangle/02_Pipeline_graphique_basique/03_Render_pass.md new file mode 100644 index 00000000..37270662 --- /dev/null +++ b/fr/03_Dessiner_un_triangle/02_Pipeline_graphique_basique/03_Render_pass.md @@ -0,0 +1,191 @@ +## Préparation + +Avant de finaliser la création de la pipeline nous devons informer Vulkan des attachements des framebuffers utilisés +lors du rendu. Nous devons indiquer combien chaque framebuffer aura de buffers de couleur et de profondeur, combien de +samples il faudra utiliser avec chaque frambuffer et comment les utiliser tout au long des opérations de rendu. Toutes +ces informations sont contenues dans un objet appelé *render pass*. Pour le configurer, créons la fonction +`createRenderPass`. Appelez cette fonction depuis `initVulkan` avant `createGraphicsPipeline`. + +```c++ +void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createGraphicsPipeline(); +} + +... + +void createRenderPass() { + +} +``` + +## Description de l'attachement + +Dans notre cas nous aurons un seul attachement de couleur, et c'est une image de la swap chain. + +```c++ +void createRenderPass() { + VkAttachmentDescription colorAttachment{}; + colorAttachment.format = swapChainImageFormat; + colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; +} +``` + +Le `format` de l'attachement de couleur est le même que le format de l'image de la swap chain. Nous n'utilisons pas +de multisampling pour le moment donc nous devons indiquer que nous n'utilisons qu'un seul sample. + +```c++ +colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; +colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; +``` + +Les membres `loadOp` et `storeOp` définissent ce qui doit être fait avec les données de l'attachement respectivement +avant et après le rendu. Pour `loadOp` nous avons les choix suivants : + +* `VK_ATTACHMENT_LOAD_OP_LOAD` : conserve les données présentes dans l'attachement +* `VK_ATTACHMENT_LOAD_OP_CLEAR` : remplace le contenu par une constante +* `VK_ATTACHMENT_LOAD_OP_DONT_CARE` : ce qui existe n'est pas défini et ne nous intéresse pas + +Dans notre cas nous utiliserons l'opération de remplacement pour obtenir un framebuffer noir avant d'afficher une +nouvelle image. Il n'y a que deux possibilités pour le membre `storeOp` : + +* `VK_ATTACHMENT_STORE_OP_STORE` : le rendu est gardé en mémoire et accessible plus tard +* `VK_ATTACHMENT_STORE_OP_DONT_CARE` : le contenu du framebuffer est indéfini dès la fin du rendu + +Nous voulons voir le triangle à l'écran donc nous voulons l'opération de stockage. + +```c++ +colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; +colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; +``` + +Les membres `loadOp` et `storeOp` s'appliquent aux données de couleur et de profondeur, et `stencilLoadOp` et +`stencilStoreOp` s'appliquent aux données de stencil. Notre application n'utilisant pas de stencil buffer, nous +pouvons indiquer que les données ne nous intéressent pas. + +```c++ +colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; +colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; +``` + +Les textures et les framebuffers dans Vulkan sont représentés par des objets de type `VkImage` possédant un certain +format de pixels. Cependant l'organisation des pixels dans la mémoire peut changer selon ce que vous faites de cette +image. + +Les organisations les plus communes sont : + +* `VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL` : images utilisées comme attachements de couleur +* `VK_IMAGE_LAYOUT_PRESENT_SRC_KHR` : images présentées à une swap chain +* `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL` : image utilisées comme destination d'opérations de copie de mémoire + +Nous discuterons plus précisément de ce sujet dans le chapitre sur les textures. Ce qui compte pour le moment est que +les images doivent changer d'organisation mémoire selon les opérations qui leur sont appliquées au long de l'exécution +de la pipeline. + +Le membre `initialLayout` spécifie l'organisation de l'image avant le début du rendu. Le membre `finalLayout` fournit +l'organisation vers laquelle l'image doit transitionner à la fin du rendu. La valeur `VK_IMAGE_LAYOUT_UNDEFINED` +indique que le format précédent de l'image ne nous intéresse pas, ce qui peut faire perdre les données précédentes. +Mais ce n'est pas un problème puisque nous effaçons de toute façon toutes les données avant le rendu. Puis, afin de +rendre l'image compatible avec la swap chain, nous fournissons `VK_IMAGE_LAYOUT_PRESENT_SRC_KHR` pour `finalLayout`. + +## Subpasses et références aux attachements + +Une unique passe de rendu est composée de plusieurs subpasses. Les subpasses sont des opérations de rendu +dépendant du contenu présent dans le framebuffer quand elles commencent. Elles peuvent consister en des opérations de +post-processing exécutées l'une après l'autre. En regroupant toutes ces opérations en une seule passe, Vulkan peut +alors réaliser des optimisations et conserver de la bande passante pour de potentiellement meilleures performances. +Pour notre triangle nous nous contenterons d'une seule subpasse. + +Chacune d'entre elle référence un ou plusieurs attachements décrits par les structures que nous avons vues +précédemment. Ces références sont elles-mêmes des structures du type `VkAttachmentReference` et ressemblent à cela : + +```c++ +VkAttachmentReference colorAttachmentRef{}; +colorAttachmentRef.attachment = 0; +colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; +``` + +Le paramètre `attachment` spécifie l'attachement à référencer à l'aide d'un indice correspondant à la position de la +structure dans le tableau de descriptions d'attachements. Notre tableau ne consistera qu'en une seule référence donc +son indice est nécessairement `0`. Le membre `layout` donne l'organisation que l'attachement devrait avoir au début d'une +subpasse utilsant cette référence. Vulkan changera automatiquement l'organisation de l'attachement quand la subpasse +commence. Nous voulons que l'attachement soit un color buffer, et pour cela la meilleure performance sera obtenue avec +`VK_IMAGE_LAYOUT_COLOR_OPTIMAL`, comme son nom le suggère. + +La subpasse est décrite dans la structure `VkSubpassDescription` : + +```c++ +VkSubpassDescription subpass{}; +subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; +``` + +Vulkan supportera également des *compute subpasses* donc nous devons indiquer que celle que nous créons est destinée +aux graphismes. Nous spécifions ensuite la référence à l'attachement de couleurs : + +```c++ +subpass.colorAttachmentCount = 1; +subpass.pColorAttachments = &colorAttachmentRef; +``` + +L'indice de cet attachement est indiqué dans le fragment shader avec le `location = 0` dans la directive +`layout(location = 0) out vec4 outColor`. + +Les types d'attachements suivants peuvent être indiqués dans une subpasse : + +* `pInputAttachments` : attachements lus depuis un shader +* `pResolveAttachments` : attachements utilisés pour le multisampling d'attachements de couleurs +* `pDepthStencilAttachment` : attachements pour la profondeur et le stencil +* `pPreserveAttachments` : attachements qui ne sont pas utilisés par cette subpasse mais dont les données doivent +être conservées + +## Passe de rendu + +Maintenant que les attachements et une subpasse simple ont été décrits nous pouvons enfin créer la render pass. +Créez une nouvelle variable du type `VkRenderPass` au-dessus de la variable `pipelineLayout` : + +```c++ +VkRenderPass renderPass; +VkPipelineLayout pipelineLayout; +``` + +L'objet représentant la render pass peut alors être créé en remplissant la structure `VkRenderPassCreateInfo` dans +laquelle nous devons remplir un tableau d'attachements et de subpasses. Les objets `VkAttachmentReference` référencent +les attachements en utilisant les indices de ce tableau. + +```c++ +VkRenderPassCreateInfo renderPassInfo{}; +renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; +renderPassInfo.attachmentCount = 1; +renderPassInfo.pAttachments = &colorAttachment; +renderPassInfo.subpassCount = 1; +renderPassInfo.pSubpasses = &subpass; + +if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { + throw std::runtime_error("échec de la création de la render pass!"); +} +``` + +Comme l'organisation de la pipeline, nous aurons à utiliser la référence à la passe de rendu tout au long du +programme. Nous devons donc la détruire dans la fonction `cleanup` : + +```c++ +void cleanup() { + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + ... +} +``` + +Nous avons eu beaucoup de travail, mais nous allons enfin créer la pipeline graphique et l'utiliser dès le prochain +chapitre! + +[Code C++](/code/11_render_passes.cpp) / +[Vertex shader](/code/09_shader_base.vert) / +[Fragment shader](/code/09_shader_base.frag) diff --git a/fr/03_Dessiner_un_triangle/02_Pipeline_graphique_basique/04_Conclusion.md b/fr/03_Dessiner_un_triangle/02_Pipeline_graphique_basique/04_Conclusion.md new file mode 100644 index 00000000..74d530b1 --- /dev/null +++ b/fr/03_Dessiner_un_triangle/02_Pipeline_graphique_basique/04_Conclusion.md @@ -0,0 +1,107 @@ +Nous pouvons maintenant combiner toutes les structures et tous les objets des chapitres précédentes pour créer la +pipeline graphique! Voici un petit récapitulatif des objets que nous avons : + +* Étapes shader : les modules shader définissent le fonctionnement des étapes programmables de la pipeline graphique +* Étapes à fonction fixée : plusieurs structures paramètrent les étapes à fonction fixée comme l'assemblage des +entrées, le rasterizer, le viewport et le mélange des couleurs +* Organisation de la pipeline : les uniformes et push constants utilisées par les shaders, auxquelles on attribue une +valeur pendant l'exécution de la pipeline +* Render pass : les attachements référencés par la pipeline et leurs utilisations + +Tout cela combiné définit le fonctionnement de la pipeline graphique. Nous pouvons maintenant remplir la structure +`VkGraphicsPipelineCreateInfo` à la fin de la fonction `createGraphicsPipeline`, mais avant les appels à la fonction +`vkDestroyShaderModule` pour ne pas invalider les shaders que la pipeline utilisera. + +Commençons par référencer le tableau de `VkPipelineShaderStageCreateInfo`. + +```c++ +VkGraphicsPipelineCreateInfo pipelineInfo{}; +pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; +pipelineInfo.stageCount = 2; +pipelineInfo.pStages = shaderStages; +``` + +Puis donnons toutes les structure décrivant les étapes à fonction fixée. + +```c++ +pipelineInfo.pVertexInputState = &vertexInputInfo; +pipelineInfo.pInputAssemblyState = &inputAssembly; +pipelineInfo.pViewportState = &viewportState; +pipelineInfo.pRasterizationState = &rasterizer; +pipelineInfo.pMultisampleState = &multisampling; +pipelineInfo.pDepthStencilState = nullptr; // Optionnel +pipelineInfo.pColorBlendState = &colorBlending; +pipelineInfo.pDynamicState = nullptr; // Optionnel +``` + +Après cela vient l'organisation de la pipeline, qui est une référence à un objet Vulkan plutôt qu'une structure. + +```c++ +pipelineInfo.layout = pipelineLayout; +``` + +Finalement nous devons fournir les références à la render pass et aux indices des subpasses. Il est aussi possible +d'utiliser d'autres render passes avec cette pipeline mais elles doivent être compatibles avec `renderPass`. La +signification de compatible est donnée +[ici](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap8.html#renderpass-compatibility), mais nous +n'utiliserons pas cette possibilité dans ce tutoriel. + +```c++ +pipelineInfo.renderPass = renderPass; +pipelineInfo.subpass = 0; +``` + +Il nous reste en fait deux paramètres : `basePipelineHandle` et `basePipelineIndex`. Vulkan vous permet de créer une +nouvelle pipeline en "héritant" d'une pipeline déjà existante. L'idée derrière cette fonctionnalité est qu'il +est moins coûteux de créer une pipeline à partir d'une qui existe déjà, mais surtout que passer d'une pipeline à une +autre est plus rapide si elles ont un même parent. Vous pouvez spécifier une pipeline de deux manières : soit en +fournissant une référence soit en donnant l'indice de la pipeline à hériter. Nous n'utilisons pas cela donc +nous indiquerons une référence nulle et un indice invalide. Ces valeurs ne sont de toute façon utilisées que si le champ +`flags` de la structure `VkGraphicsPipelineCreateInfo` comporte `VK_PIPELINE_CREATE_DERIVATIVE_BIT`. + +```c++ +pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; // Optionnel +pipelineInfo.basePipelineIndex = -1; // Optionnel +``` + +Préparons-nous pour l'étape finale en créant un membre donnée où stocker la référence à la `VkPipeline` : + +```c++ +VkPipeline graphicsPipeline; +``` + +Et créons enfin la pipeline graphique : + +```c++ +if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { + throw std::runtime_error("échec de la création de la pipeline graphique!"); +} +``` + +La fonction `vkCreateGraphicsPipelines` possède en fait plus de paramètres que les fonctions de création d'objet que +nous avons pu voir jusqu'à présent. Elle peut en effet accepter plusieurs structures `VkGraphicsPipelineCreateInfo` +et créer plusieurs `VkPipeline` en un seul appel. + +Le second paramètre que nous n'utilisons pas ici (mais que nous reverrons dans un chapitre qui lui sera dédié) sert à +fournir un objet `VkPipelineCache` optionnel. Un tel objet peut être stocké et réutilisé entre plusieurs appels de la +fonction et même entre plusieurs exécutions du programme si son contenu est correctement stocké dans un fichier. Cela +permet de grandement accélérer la création des pipelines. + +La pipeline graphique est nécessaire à toutes les opérations d'affichage, nous ne devrons donc la supprimer qu'à la fin +du programme dans la fonction `cleanup` : + +```c++ +void cleanup() { + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + ... +} +``` + +Exécutez votre programme pour vérifier que tout ce travail a enfin résulté dans la création d'une pipeline graphique. +Nous sommes de plus en plus proches d'avoir un dessin à l'écran! Dans les prochains chapitres nous générerons les +framebuffers à partir des images de la swap chain et préparerons les commandes d'affichage. + +[Code C++](/code/12_graphics_pipeline_complete.cpp) / +[Vertex shader](/code/09_shader_base.vert) / +[Fragment shader](/code/09_shader_base.frag) diff --git a/fr/03_Dessiner_un_triangle/03_Effectuer_le_rendu/00_Framebuffers.md b/fr/03_Dessiner_un_triangle/03_Effectuer_le_rendu/00_Framebuffers.md new file mode 100644 index 00000000..76780671 --- /dev/null +++ b/fr/03_Dessiner_un_triangle/03_Effectuer_le_rendu/00_Framebuffers.md @@ -0,0 +1,100 @@ +Nous avons beaucoup parlé de framebuffers dans les chapitres précédents, et nous avons mis en place la render pass +pour qu'elle en accepte un du même format que les images de la swap chain. Pourtant nous n'en avons encore créé aucun. + +Les attachements de différents types spécifiés durant la render pass sont liés en les considérant dans des objets de +type `VkFramebuffer`. Un tel objet référence toutes les `VkImageView` utilisées comme attachements par une passe. +Dans notre cas nous n'en aurons qu'un : un attachement de couleur, qui servira de cible d'affichage uniquement. +Cependant l'image utilisée dépendra de l'image fournie par la swap chain lors de la requête pour l'affichage. Nous +devons donc créer un framebuffer pour chacune des images de la swap chain et utiliser le bon au moment de l'affichage. + +Pour cela créez un autre `std::vector` qui contiendra des framebuffers : + +```c++ +std::vector swapChainFramebuffers; +``` + +Nous allons remplir ce `vector` depuis une nouvelle fonction `createFramebuffers` que nous appellerons depuis +`initVulkan` juste après la création de la pipeline graphique : + +```c++ +void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createGraphicsPipeline(); + createFramebuffers(); +} + +... + +void createFramebuffers() { + +} +``` + +Commencez par redimensionner le conteneur afin qu'il puisse stocker tous les framebuffers : + +```c++ +void createFramebuffers() { + swapChainFramebuffers.resize(swapChainImageViews.size()); +} +``` + +Nous allons maintenant itérer à travers toutes les images et créer un framebuffer à partir de chacune d'entre elles : + +```c++ +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("échec de la création d'un framebuffer!"); + } +} +``` + +Comme vous le pouvez le voir la création d'un framebuffer est assez simple. Nous devons d'abord indiquer avec quelle +`renderPass` le framebuffer doit être compatible. Sachez que si vous voulez utiliser un framebuffer avec plusieurs +render passes, les render passes spécifiées doivent être compatibles entre elles. La compatibilité signifie ici +approximativement qu'elles utilisent le même nombre d'attachements du même type. Ceci implique qu'il ne faut pas +s'attendre à ce qu'une render pass puisse ignorer certains attachements d'un framebuffer qui en aurait trop. + +Les paramètres `attachementCount` et `pAttachments` doivent donner la taille du tableau contenant les `VkImageViews` +qui servent d'attachements. + +Les paramètres `width` et `height` sont évidents. Le membre `layers` correspond au nombres de couches dans les images +fournies comme attachements. Les images de la swap chain n'ont toujours qu'une seule couche donc nous indiquons `1`. + +Nous devons détruire les framebuffers avant les image views et la render pass dans la fonction `cleanup` : + +```c++ +void cleanup() { + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } + + ... +} +``` + +Nous avons atteint le moment où tous les objets sont prêts pour l'affichage. Dans le prochain chapitre nous allons +écrire les commandes d'affichage. + +[Code C++](/code/13_framebuffers.cpp) / +[Vertex shader](/code/09_shader_base.vert) / +[Fragment shader](/code/09_shader_base.frag) diff --git a/fr/03_Dessiner_un_triangle/03_Effectuer_le_rendu/01_Command_buffers.md b/fr/03_Dessiner_un_triangle/03_Effectuer_le_rendu/01_Command_buffers.md new file mode 100644 index 00000000..5d9206c5 --- /dev/null +++ b/fr/03_Dessiner_un_triangle/03_Effectuer_le_rendu/01_Command_buffers.md @@ -0,0 +1,284 @@ +Les commandes Vulkan, comme les opérations d'affichage et de transfert mémoire, ne sont pas réalisées avec des appels de +fonctions. Il faut pré-enregistrer toutes les opérations dans des _command buffers_. L'avantage est que vous pouvez +préparer tout ce travail à l'avance et depuis plusieurs threads, puis vous contenter d'indiquer à Vulkan quel command +buffer doit être exécuté. Cela réduit considérablement la bande passante entre le CPU et le GPU et améliore grandement +les performances. + +## Command pools + +Nous devons créer une *command pool* avant de pouvoir créer les command buffers. Les command pools gèrent la mémoire +utilisée par les buffers, et c'est de fait les command pools qui nous instancient les command buffers. Ajoutez un +nouveau membre donnée à la classe de type `VkCommandPool` : + +```c++ +VkCommandPool commandPool; +``` + +Créez ensuite la fonction `createCommandPool` et appelez-la depuis `initVulkan` après la création du framebuffer. + +```c++ +void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createGraphicsPipeline(); + createFramebuffers(); + createCommandPool(); +} + +... + +void createCommandPool() { + +} +``` + +La création d'une command pool ne nécessite que deux paramètres : + +```c++ +QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); + +VkCommandPoolCreateInfo poolInfo{}; +poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; +poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); +poolInfo.flags = 0; // Optionel +``` + +Les commands buffers sont exécutés depuis une queue, comme la queue des graphismes et de présentation que nous avons +récupérées. Une command pool ne peut allouer des command buffers compatibles qu'avec une seule famille de queues. Nous +allons enregistrer des commandes d'affichage, c'est pourquoi nous avons récupéré une queue de graphismes. + +Il existe deux valeurs acceptées par `flags` pour les command pools : + +* `VK_COMMAND_POOL_CREATE_TRANSIENT_BIT` : informe que les command buffers sont ré-enregistrés très souvent, ce qui +peut inciter Vulkan (et donc le driver) à ne pas utiliser le même type d'allocation +* `VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT` : permet aux command buffers d'être ré-enregistrés individuellement, +ce que les autres configurations ne permettent pas + +Nous n'enregistrerons les command buffers qu'une seule fois au début du programme, nous n'aurons donc pas besoin de ces +fonctionnalités. + +```c++ +if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + throw std::runtime_error("échec de la création d'une command pool!"); +} +``` + +Terminez la création de la command pool à l'aide de la fonction `vkCreateComandPool`. Elle ne comprend pas de +paramètre particulier. Les commandes seront utilisées tout au long du programme pour tout affichage, nous ne devons +donc la détruire que dans la fonction `cleanup` : + +```c++ +void cleanup() { + vkDestroyCommandPool(device, commandPool, nullptr); + + ... +} +``` + +## Allocation des command buffers + +Nous pouvons maintenant allouer des command buffers et enregistrer les commandes d'affichage. Dans la mesure où l'une +des commandes consiste à lier un framebuffer nous devrons les enregistrer pour chacune des images de la swap chain. +Créez pour cela une liste de `VkCommandBuffer` et stockez-la dans un membre donnée de la classe. Les command buffers +sont libérés avec la destruction de leur command pool, nous n'avons donc pas à faire ce travail. + +```c++ +std::vector commandBuffers; +``` + +Commençons maintenant à travailler sur notre fonction `createCommandBuffers` qui allouera et enregistrera les command +buffers pour chacune des images de la swap chain. + +```c++ +void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createGraphicsPipeline(); + createFramebuffers(); + createCommandPool(); + createCommandBuffers(); +} + +... + +void createCommandBuffers() { + commandBuffers.resize(swapChainFramebuffers.size()); +} +``` + +Les command buffers sont alloués par la fonction `vkAllocateCommandBuffers` qui prend en paramètre une structure du +type `VkCommandBufferAllocateInfo`. Cette structure spécifie la command pool et le nombre de buffers à allouer depuis +celle-ci : + +```c++ +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("échec de l'allocation de command buffers!"); +} +``` + +Les command buffers peuvent être *primaires* ou *secondaires*, ce que l'on indique avec le paramètre `level`. Il peut +prendre les valeurs suivantes : + +* `VK_COMMAND_BUFFER_LEVEL_PRIMARY` : peut être envoyé à une queue pour y être exécuté mais ne peut être appelé par +d'autres command buffers +* `VK_COMMAND_BUFFER_LEVEL_SECONDARY` : ne peut pas être directement émis à une queue mais peut être appelé par un autre +command buffer + +Nous n'utiliserons pas la fonctionnalité de command buffer secondaire ici. Sachez que le mécanisme de command buffer +secondaire est à la base de la génération rapie de commandes d'affichage depuis plusieurs threads. + +## Début de l'enregistrement des commandes + +Nous commençons l'enregistrement des commandes en appelant `vkBeginCommandBuffer`. Cette fonction prend une petite +structure du type `VkCommandBufferBeginInfo` en argument, permettant d'indiquer quelques détails sur l'utilisation du +command buffer. + +```c++ +for (size_t i = 0; i < commandBuffers.size(); i++) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = 0; // Optionnel + beginInfo.pInheritanceInfo = nullptr; // Optionel + + if (vkBeginCommandBuffer(commandBuffers[i], &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("erreur au début de l'enregistrement d'un command buffer!"); + } +} +``` + +L'utilisation du command buffer s'indique avec le paramètre `flags`, qui peut prendre les valeurs suivantes : + +* `VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT` : le command buffer sera ré-enregistré après son utilisation, donc +invalidé une fois son exécution terminée +* `VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT` : ce command buffer secondaire sera intégralement exécuté dans une +unique render pass +* `VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT` : le command buffer peut être ré-envoyé à la queue alors qu'il y est +déjà et/ou est en cours d'exécution + +Nous n'avons pas besoin de ces flags ici. + +Le paramètre `pInheritanceInfo` n'a de sens que pour les command buffers secondaires. +Il indique l'état à hériter de l'appel par le command buffer primaire. + +Si un command buffer est déjà prêt un appel à `vkBeginCommandBuffer` le regénèrera implicitement. Il n'est pas possible +d'enregistrer un command buffer en plusieurs fois. + +## Commencer une render pass + +L'affichage commence par le lancement de la render pass réalisé par `vkCmdBeginRenderPass`. La passe est configurée +à l'aide des paramètres remplis dans une structure de type `VkRenderPassBeginInfo`. + +```c++ +VkRenderPassBeginInfo renderPassInfo{}; +renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; +renderPassInfo.renderPass = renderPass; +renderPassInfo.framebuffer = swapChainFramebuffers[i]; +``` + +Ces premiers paramètres sont la render pass elle-même et les attachements à lui fournir. Nous avons créé un +framebuffer pour chacune des images de la swap chain qui spécifient ces images comme attachements de couleur. + +```c++ +renderPassInfo.renderArea.offset = {0, 0}; +renderPassInfo.renderArea.extent = swapChainExtent; +``` + +Les deux paramètres qui suivent définissent la taille de la zone de rendu. Cette zone de rendu définit où les +chargements et stockages shaders se produiront. Les pixels hors de cette région auront une valeur non définie. Elle +doit correspondre à la taille des attachements pour avoir une performance optimale. + +```c++ +VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; +renderPassInfo.clearValueCount = 1; +renderPassInfo.pClearValues = &clearColor; +``` + +Les deux derniers paramètres définissent les valeurs à utiliser pour remplacer le contenu (fonctionnalité que nous +avions activée avec `VK_ATTACHMENT_LOAD_CLEAR`). J'ai utilisé un noir complètement opaque. + +```c++ +vkCmdBeginRenderPass(commandBuffers[i], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); +``` + +La render pass peut maintenant commencer. Toutes les fonctions enregistrables se reconnaisent à leur préfixe `vkCmd`. +Comme elles retournent toutes `void` nous n'avons aucun moyen de gérer d'éventuelles erreurs avant d'avoir fini +l'enregistrement. + +Le premier paramètre de chaque commande est toujours le command buffer qui stockera l'appel. Le second paramètre donne +des détails sur la render pass à l'aide de la structure que nous avons préparée. Le dernier paramètre informe sur la +provenance des commandes pendant l'exécution de la passe. Il peut prendre ces valeurs : + +* `VK_SUBPASS_CONTENTS_INLINE` : les commandes de la render pass seront inclues directement dans le command buffer +(qui est donc primaire) +* `VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFER` : les commandes de la render pass seront fournies par un ou +plusieurs command buffers secondaires + +Nous n'utiliserons pas de command buffer secondaire, nous devons donc fournir la première valeur à la fonction. + +## Commandes d'affichage basiques + +Nous pouvons maintenant activer la pipeline graphique : + +```c++ +vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); +``` + +Le second paramètre indique que la pipeline est bien une pipeline graphique et non de calcul. Nous avons fourni à Vulkan +les opérations à exécuter avec la pipeline graphique et les attachements que le fragment shader devra utiliser. Il ne +nous reste donc plus qu'à lui dire d'afficher un triangle : + +```c++ +vkCmdDraw(commandBuffers[i], 3, 1, 0, 0); +``` + +Le fonction `vkCmdDraw` est assez ridicule quand on sait tout ce qu'elle implique, mais sa simplicité est due +à ce que tout a déjà été préparé en vue de ce moment tant attendu. Elle possède les paramètres suivants en plus du +command buffer concerné : + +* `vertexCount` : même si nous n'avons pas de vertex buffer, nous avons techniquement trois vertices à dessiner +* `instanceCount` : sert au rendu instancié (instanced rendering); indiquez `1` si vous ne l'utilisez pas +* `firstVertex` : utilisé comme décalage dans le vertex buffer et définit ainsi la valeur la plus basse pour +`glVertexIndex` +* `firstInstance` : utilisé comme décalage pour l'instanced rendering et définit ainsi la valeur la plus basse pour +`gl_InstanceIndex` + +## Finitions + +La render pass peut ensuite être terminée : + +```c++ +vkCmdEndRenderPass(commandBuffers[i]); +``` + +Et nous avons fini l'enregistrement du command buffer : + +```c++ +if (vkEndCommandBuffer(commandBuffers[i]) != VK_SUCCESS) { + throw std::runtime_error("échec de l'enregistrement d'un command buffer!"); +} +``` + +Dans le prochain chapitre nous écrirons le code pour la boucle principale. Elle récupérera une image de la swap chain, +exécutera le bon command buffer et retournera l'image complète à la swap chain. + +[Code C++](/code/14_command_buffers.cpp) / +[Vertex shader](/code/09_shader_base.vert) / +[Fragment shader](/code/09_shader_base.frag) diff --git "a/fr/03_Dessiner_un_triangle/03_Effectuer_le_rendu/02_Rendu_et_pr\303\251sentation.md" "b/fr/03_Dessiner_un_triangle/03_Effectuer_le_rendu/02_Rendu_et_pr\303\251sentation.md" new file mode 100644 index 00000000..957236aa --- /dev/null +++ "b/fr/03_Dessiner_un_triangle/03_Effectuer_le_rendu/02_Rendu_et_pr\303\251sentation.md" @@ -0,0 +1,626 @@ +## Mise en place + +Nous en sommes au chapitre où tout s'assemble. Nous allons écrire une fonction `drawFrame` qui sera appelée depuis la +boucle principale et affichera les triangles à l'écran. Créez la fonction et appelez-la depuis `mainLoop` : + +```c++ +void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + drawFrame(); + } +} + +... + +void drawFrame() { + +} +``` + +## Synchronisation + +Le fonction `drawFrame` réalisera les opérations suivantes : + +* Acquérir une image depuis la swap chain +* Exécuter le command buffer correspondant au framebuffer dont l'attachement est l'image obtenue +* Retourner l'image à la swap chain pour présentation + +Chacune de ces actions n'est réalisée qu'avec un appel de fonction. Cependant ce n'est pas aussi simple : les +opérations sont par défaut exécutées de manière asynchrones. La fonction retourne aussitôt que les opérations sont +lancées, et par conséquent l'ordre d'exécution est indéfini. Cela nous pose problème car chacune des opérations que nous +voulons lancer dépendent des résultats de l'opération la précédant. + +Il y a deux manières de synchroniser les évènements de la swap chain : les *fences* et les *sémaphores*. Ces deux objets +permettent d'attendre qu'une opération se termine en relayant un signal émis par un processus généré par la fonction à +l'origine du lancement de l'opération. + +Ils ont cependant une différence : l'état d'une fence peut être accédé depuis le programme à l'aide de fonctions telles +que `vkWaitForFences` alors que les sémaphores ne le permettent pas. Les fences sont généralement utilisées pour +synchroniser votre programme avec les opérations alors que les sémaphores synchronisent les opérations entre elles. Nous +voulons synchroniser les queues, les commandes d'affichage et la présentation, donc les sémaphores nous conviennent le +mieux. + +## Sémaphores + +Nous aurons besoin d'un premier sémaphore pour indiquer que l'acquisition de l'image s'est bien réalisée, puis d'un +second pour prévenir de la fin du rendu et permettre à l'image d'être retournée dans la swap chain. Créez deux membres +données pour stocker ces sémaphores : + +```c++ +VkSemaphore imageAvailableSemaphore; +VkSemaphore renderFinishedSemaphore; +``` + +Pour leur création nous allons avoir besoin d'une dernière fonction `create...` pour cette partie du tutoriel. +Appelez-la `createSemaphores` : + +```c++ +void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createGraphicsPipeline(); + createFramebuffers(); + createCommandPool(); + createCommandBuffers(); + createSemaphores(); +} + +... + +void createSemaphores() { + +} +``` + +La création d'un sémaphore passe par le remplissage d'une structure de type `VkSemaphoreCreateInfo`. Cependant cette +structure ne requiert pour l'instant rien d'autre que le membre `sType` : + +```c++ +void createSemaphores() { + VkSemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; +} +``` + +De futures version de Vulkan ou des extensions pourront à terme donner un intérêt aux membre `flags` et `pNext`, comme +pour d'autres structures. Créez les sémaphores comme suit : + +```c++ +if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphore) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphore) != VK_SUCCESS) { + + throw std::runtime_error("échec de la création des sémaphores!"); +} +``` + +Les sémaphores doivent être détruits à la fin du programme depuis la fonction `cleanup` : + +```c++ +void cleanup() { + vkDestroySemaphore(device, renderFinishedSemaphore, nullptr); + vkDestroySemaphore(device, imageAvailableSemaphore, nullptr); +``` + +## Acquérir une image de la swap chain + +La première opération à réaliser dans `drawFrame` est d'acquérir une image depuis la swap chain. La swap chain étant une +extension nous allons encore devoir utiliser des fonction suffixées de `KHR` : + +```c++ +void drawFrame() { + uint32_t imageIndex; + vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex); +} +``` + +Les deux premiers paramètres de `vkAcquireNextImageKHR` sont le logical device et la swap chain depuis laquelle +récupérer les images. Le troisième paramètre spécifie une durée maximale en nanosecondes avant d'abandonner l'attente +si aucune image n'est disponible. Utiliser la plus grande valeur possible pour un `uint32_t` le désactive. + +Les deux paramètres suivants sont les objets de synchronisation qui doivent être informés de la complétion de +l'opération de récupération. Ce sera à partir du moment où le sémaphore que nous lui fournissons reçoit un signal que +nous pouvons commencer à dessiner. + +Le dernier paramètre permet de fournir à la fonction une variable dans laquelle elle stockera l'indice de l'image +récupérée dans la liste des images de la swap chain. Cet indice correspond à la `VkImage` dans notre `vector` +`swapChainImages`. Nous utiliserons cet indice pour invoquer le bon command buffer. + +## Envoi du command buffer + +L'envoi à la queue et la synchronisation de celle-ci sont configurés à l'aide de paramètres dans la structure +`VkSubmitInfo` que nous allons remplir. + +```c++ +VkSubmitInfo submitInfo{}; +submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + +VkSemaphore waitSemaphores[] = {imageAvailableSemaphore}; +VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; +submitInfo.waitSemaphoreCount = 1; +submitInfo.pWaitSemaphores = waitSemaphores; +submitInfo.pWaitDstStageMask = waitStages; +``` + +Les trois premiers paramètres (sans compter `sType`) fournissent le sémaphore indiquant si l'opération doit attendre et +l'étape du rendu à laquelle s'arrêter. Nous voulons attendre juste avant l'écriture des couleurs sur l'image. Par contre +nous laissons à l'implémentation la possibilité d'exécuter toutes les étapes précédentes d'ici là. Notez que chaque +étape indiquée dans `waitStages` correspond au sémaphore de même indice fourni dans `waitSemaphores`. + +```c++ +submitInfo.commandBufferCount = 1; +submitInfo.pCommandBuffers = &commandBuffers[imageIndex]; +``` + +Les deux paramètres qui suivent indiquent les command buffers à exécuter. Nous devons ici fournir le command buffer +qui utilise l'image de la swap chain que nous venons de récupérer comme attachement de couleur. + +```c++ +VkSemaphore signalSemaphores[] = {renderFinishedSemaphore}; +submitInfo.signalSemaphoreCount = 1; +submitInfo.pSignalSemaphores = signalSemaphores; +``` + +Les paramètres `signalSemaphoreCount` et `pSignalSemaphores` indiquent les sémaphores auxquels indiquer que les command +buffers ont terminé leur exécution. Dans notre cas nous utiliserons notre `renderFinishedSemaphore`. + +```c++ +if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { + throw std::runtime_error("échec de l'envoi d'un command buffer!"); +} +``` + +Nous pouvons maintenant envoyer notre command buffer à la queue des graphismes en utilisant `vkQueueSubmit`. Cette +fonction prend en argument un tableau de structures de type `VkSubmitInfo` pour une question d'efficacité. Le dernier +paramètre permet de fournir une fence optionnelle. Celle-ci sera prévenue de la fin de l'exécution des command +buffers. Nous n'en utilisons pas donc passerons `VK_NULL_HANDLE`. + +## Subpass dependencies + +Les subpasses s'occupent automatiquement de la transition de l'organisation des images. Ces transitions sont contrôlées +par des *subpass dependencies*. Elles indiquent la mémoire et l'exécution entre les subpasses. Nous n'avons certes +qu'une seule subpasse pour le moment, mais les opérations avant et après cette subpasse comptent aussi comme des +subpasses implicites. + +Il existe deux dépendances préexistantes capables de gérer les transitions au début et à la fin de la render pass. Le +problème est que cette première dépendance ne s'exécute pas au bon moment. Elle part du principe que la transition de +l'organisation de l'image doit être réalisée au début de la pipeline, mais dans notre programme l'image n'est pas encore +acquise à ce moment! Il existe deux manières de régler ce problème. Nous pourrions changer `waitStages` pour +`imageAvailableSemaphore` à `VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT` pour être sûrs que la pipeline ne commence pas avant +que l'image ne soit acquise, mais nous perdrions en performance car les shaders travaillant sur les vertices n'ont pas +besoin de l'image. Il faudrait faire quelque chose de plus subtil. Nous allons donc plutôt faire attendre la render +pass à l'étape `VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT` et faire la transition à ce moment. Cela nous donne de +plus une bonne excuse pour s'intéresser au fonctionnement des subpass dependencies. + +Celles-ci sont décrites dans une structure de type `VkSubpassDependency`. Créez en une dans la fonction +`createRenderPass` : + +```c++ +VkSubpassDependency dependency{}; +dependency.srcSubpass = VK_SUBPASS_EXTERNAL; +dependency.dstSubpass = 0; +``` + +Les deux premiers champs permettent de fournir l'indice de la subpasse d'origine et de la subpasse d'arrivée. La valeur +particulière `VK_SUBPASS_EXTERNAL` réfère à la subpass implicite soit avant soit après la render pass, selon que +cette valeur est indiquée dans respectivement `srcSubpass` ou `dstSubpass`. L'indice `0` correspond à notre +seule et unique subpasse. La valeur fournie à `dstSubpass` doit toujours être supérieure à `srcSubpass` car sinon une +boucle infinie peut apparaître (sauf si une des subpasse est `VK_SUBPASS_EXTERNAL`). + +```c++ +dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; +dependency.srcAccessMask = 0; +``` + +Les deux paramètres suivants indiquent les opérations à attendre et les étapes durant lesquelles les opérations à +attendre doivent être considérées. Nous voulons attendre la fin de l'extraction de l'image avant d'y accéder, hors +ceci est déjà configuré pour être synchronisé avec l'étape d'écriture sur l'attachement. C'est pourquoi nous n'avons +qu'à attendre à cette étape. + +```c++ +dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; +dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; +``` + +Nous indiquons ici que les opérations qui doivent attendre pendant l'étape liée à l'attachement de couleur sont celles +ayant trait à l'écriture. Ces paramètres permettent de faire attendre la transition jusqu'à ce qu'elle +soit possible, ce qui correspond au moment où la passe accède à cet attachement puisqu'elle est elle-même configurée +pour attendre ce moment. + +```c++ +renderPassInfo.dependencyCount = 1; +renderPassInfo.pDependencies = &dependency; +``` + +Nous fournissons enfin à la structure ayant trait à la render pass un tableau de configurations pour les subpass +dependencies. + +## Présentation + +La dernière étape pour l'affichage consiste à envoyer le résultat à la swap chain. La présentation est configurée avec +une structure de type `VkPresentInfoKHR`, et nous ferons cela à la fin de la fonction `drawFrame`. + +```c++ +VkPresentInfoKHR presentInfo{}; +presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + +presentInfo.waitSemaphoreCount = 1; +presentInfo.pWaitSemaphores = signalSemaphores; +``` + +Les deux premiers paramètres permettent d'indiquer les sémaphores devant signaler que la présentation peut se dérouler. + +```c++ +VkSwapchainKHR swapChains[] = {swapChain}; +presentInfo.swapchainCount = 1; +presentInfo.pSwapchains = swapChains; +presentInfo.pImageIndices = &imageIndex; +``` + +Les deux paramètres suivants fournissent un tableau contenant notre unique swap chain qui présentera les images et +l'indice de l'image pour celle-ci. + +```c++ +presentInfo.pResults = nullptr; // Optionnel +``` + +Ce dernier paramètre est optionnel. Il vous permet de fournir un tableau de `VkResult` que vous pourrez consulter pour +vérifier que toutes les swap chain ont bien présenté leur image sans problème. Cela n'est pas nécessaire dans notre +cas, car n'utilisant qu'une seule swap chain nous pouvons simplement regarder la valeur de retour de la fonction de +présentation. + +```c++ +vkQueuePresentKHR(presentQueue, &presentInfo); +``` + +La fonction `vkQueuePresentKHR` émet la requête de présentation d'une image par la swap chain. Nous ajouterons la +gestion des erreurs pour `vkAcquireNextImageKHR` et `vkQueuePresentKHR` dans le prochain chapitre car une erreur à ces +étapes n'implique pas forcément que le programme doit se terminer, mais plutôt qu'il doit s'adapter à des changements. + +Si vous avez fait tout ça correctement vous devriez avoir quelque chose comme cela à l'écran quand vous lancez votre +programme : + +![](/images/triangle.png) + +Enfin! Malheureusement si vous essayez de quitter proprement le programme vous obtiendrez un crash et un message +semblable à ceci : + +![](/images/semaphore_in_use.png) + +N'oubliez pas que puisque les opérations dans `drawFrame` sont asynchrones il est quasiment certain que lorsque vous +quittez le programme, celui-ci exécute encore des instructions et cela implique que vous essayez de libérer des +ressources en train d'être utilisées. Ce qui est rarement une bonne idée, surtout avec du bas niveau comme Vulkan. + +Pour régler ce problème nous devons attendre que le logical device finisse l'opération qu'il est en train de +réaliser avant de quitter `mainLoop` et de détruire la fenêtre : + +```c++ +void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + drawFrame(); + } + + vkDeviceWaitIdle(device); +} +``` + +Vous pouvez également attendre la fin d'une opération quelconque depuis une queue spécifique à l'aide de la fonction +`vkQueueWaitIdle`. Ces fonction peuvent par ailleurs être utilisées pour réaliser une synchronisation très basique, +mais très inefficace. Le programme devrait maintenant se terminer sans problème quand vous fermez la fenêtre. + +## Frames en vol + +Si vous lancez l'application avec les validation layers maintenant, vous pouvez soit avoir des erreurs soit vous remarquerez +que l'utilisation de la mémoire augmente, lentement mais sûrement. La raison est que l'application soumet rapidement du +travail dans la fonction `drawframe`, mais que l'on ne vérifie pas si ces rendus sont effectivement terminés. +Si le CPU envoie plus de commandes que le GPU ne peut en exécuter, ce qui est le cas car nous envoyons nos command buffers +de manière totalement débridée, la queue de graphismes va progressivement se remplir de travail à effectuer. +Pire encore, nous utilisons `imageAvailableSemaphore` et `renderFinishedSemaphore` ainsi que nos command buffers pour +plusieurs frames en même temps. + +Le plus simple est d'attendre que le logical device n'aie plus de travail à effectuer avant de lui en envoyer de +nouveau, par exemple à l'aide de `vkQueueIdle` : + +```c++ +void drawFrame() { + ... + + vkQueuePresentKHR(presentQueue, &presentInfo); + + vkQueueWaitIdle(presentQueue); +} +``` + +Cependant cette méthode n'est clairement pas optimale pour le GPU car la pipeline peut en général gérer plusieurs images +à la fois grâce aux architectures massivement parallèles. Les étapes que l'image a déjà passées (par exemple le vertex +shader quand elle en est au fragment shader) peuvent tout à fait être utilisées pour l'image suivante. Nous +allons améliorer notre programme pour qu'il puisse supporter plusieurs images *en vol* (ou *in flight*) tout en +limitant la quantité de commandes dans la queue. + +Commencez par ajouter une constante en haut du programme qui définit le nombre de frames à traiter concurentiellement : + +```c++ +const int MAX_FRAMES_IN_FLIGHT = 2; +``` + +Chaque frame aura ses propres sémaphores : + +```c++ +std::vector imageAvailableSemaphores; +std::vector renderFinishedSemaphores; +``` + +La fonction `createSemaphores` doit être améliorée pour gérer la création de tout ceux-là : + +```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("échec de la création des sémaphores d'une frame!"); + } +} +``` + +Ils doivent également être libérés à la fin du programme : + +```c++ +void cleanup() { + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); + } + + ... +} +``` + +Pour utiliser la bonne paire de sémaphores à chaque fois nous devons garder à portée de main l'indice de la frame en +cours. + +```c++ +size_t currentFrame = 0; +``` + +La fonction `drawFrame` peut maintenant être modifiée pour utiliser les bons objets : + +```c++ +void drawFrame() { + vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + + ... + + VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[currentFrame]}; + + ... + + VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[currentFrame]}; + + ... +} +``` + +Nous ne devons bien sûr pas oublier d'avancer à la frame suivante à chaque fois : + +```c++ +void drawFrame() { + ... + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; +} +``` + +En utilisant l'opérateur de modulo `%` nous pouvons nous assurer que l'indice boucle à chaque fois que +`MAX_FRAMES_IN_FLIGHT` est atteint. + +Bien que nous ayons pas en place les objets facilitant le traitement de plusieurs frames simultanément, encore +maintenant le GPU traite plus de `MAX_FRAMES_IN_FLIGHT` à la fois. Nous n'avons en effet qu'une synchronisation GPU-GPU +mais pas de synchronisation CPU-GPU. Nous n'avons pas de moyen de savoir que le travail sur telle ou telle frame est +fini, ce qui a pour conséquence que nous pouvons nous retrouver à afficher une frame alors qu'elle est encore en +traitement. + +Pour la synchronisation CPU-GPU nous allons utiliser l'autre moyen fourni par Vulkan que nous avons déjà évoqué : les +*fences*. Au lieu d'informer une certaine opération que tel signal devra être attendu avant de continuer, ce que les +sémaphores permettent, les fences permettent au programme d'attendre l'exécution complète d'une opération. Nous allons +créer une fence pour chaque frame : + +```c++ +std::vector imageAvailableSemaphores; +std::vector renderFinishedSemaphores; +std::vector inFlightFences; +size_t currentFrame = 0; +``` + +J'ai choisi de créer les fences avec les sémaphores et de renommer la fonction `createSemaphores` en +`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("échec de la création des objets de synchronisation pour une frame!"); + } + } +} +``` + +La création d'une `VkFence` est très similaire à la création d'un sémaphore. N'oubliez pas de libérer les 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); + } + + ... +} +``` + +Nous voulons maintenant que `drawFrame` utilise les fences pour la synchronisation. L'appel à `vkQueueSubmit` inclut un +paramètre optionnel qui permet de passer une fence. Celle-ci sera informée de la fin de l'exécution du command buffer. +Nous pouvons interpréter ce signal comme la fin du rendu sur la frame. + +```c++ +void drawFrame() { + ... + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) { + throw std::runtime_error("échec de l'envoi d'un command buffer!"); + } + ... +} +``` + +La dernière chose qui nous reste à faire est de changer le début de `drawFrame` pour que la fonction attende le rendu de +la frame précédente : + +```c++ +void drawFrame() { + vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX); + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + ... +} +``` + +La fonction `vkWaitForFences` prend en argument un tableau de fences. Elle attend soit qu'une seule fence soit que +toutes les fences déclarent être signalées avant de retourner. Le choix du mode d'attente se fait selon la valeur du +quatrième paramètre. Avec `VK_TRUE` nous demandons d'attendre toutes les fences, même si cela ne fait bien sûr pas de +différence vu que nous n'avons qu'une seule fence. Comme la fonction `vkAcquireNextImageKHR` cette fonction prend une +durée en argument, que nous ignorons. Nous devons ensuite réinitialiser les fences manuellement à l'aide d'un appel à +la fonction `vkResetFences`. + +Si vous lancez le programme maintenant vous allez constater un comportement étrange. Plus rien ne se passe. Nous attendons qu'une fence soit signalée alors qu'elle n'a +jamais été envoyée à aucune fonction. En effet les fences sont par défaut crées dans le +mode non signalé. Comme nous appelons `vkWaitForFences` avant `vkQueueSubmit` notre +première fence va créer une pause infinie. Pour empêcher cela nous devons initialiser +les fences dans le mode signalé, et ce dès leur création : + +```c++ +void createSyncObjects() { + ... + + VkFenceCreateInfo fenceInfo{}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; + + ... +} +``` + +La fuite de mémoire n'est plus, mais le programme ne fonctionne pas encore correctement. Si `MAX_FRAMES_IN_FLIGHT` est +plus grand que le nombre d'images de la swapchain ou que `vkAcquireNextImageKHR` ne retourne pas les images dans l'ordre, +alors il est possible que nous lancions le rendu dans une image qui est déjà *en vol*. Pour éviter ça, nous devons pour +chaque image de la swapchain si une frame en vol est en train d'utiliser celle-ci. Cette correspondance permettra de suivre +les images en vol par leur fences respective, de cette façon nous aurons immédiatement un objet de synchronisation à attendre +avant qu'une nouvelle frame puisse utiliser cette image. + +Tout d'abord, ajoutez une nouvelle liste nommée `imagesInFlight`: + +```c++ +std::vector inFlightFences; +std::vector imagesInFlight; +size_t currentFrame = 0; +``` + +Préparez-la dans `createSyncObjects`: + +```c++ +void createSyncObjects() { + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + inFlightFences.resize(MAX_FRAMES_IN_FLIGHT); + imagesInFlight.resize(swapChainImages.size(), VK_NULL_HANDLE); + + ... +} +``` + +Initialement aucune frame n'utilise d'image, donc on peut explicitement l'initialiser à *pas de fence*. Maintenant, nous allons modifier +`drawFrame` pour attendre la fin de n'importe quelle frame qui serait en train d'utiliser l'image qu'on nous assigné pour la nouvelle frame. + +```c++ +void drawFrame() { + ... + + vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + + // Vérifier si une frame précédente est en train d'utiliser cette image (il y a une fence à attendre) + if (imagesInFlight[imageIndex] != VK_NULL_HANDLE) { + vkWaitForFences(device, 1, &imagesInFlight[imageIndex], VK_TRUE, UINT64_MAX); + } + // Marque l'image comme étant à nouveau utilisée par cette frame + imagesInFlight[imageIndex] = inFlightFences[currentFrame]; + + ... +} +``` + +Parce que nous avons maintenant plus d'appels à `vkWaitForFences`, les appels à `vkResetFences` doivent être **déplacés**. Le mieux reste +de simplement l'appeler juste avant d'utiliser la fence: + +```c++ +void drawFrame() { + ... + + vkResetFences(device, 1, &inFlightFences[currentFrame]); + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) { + throw std::runtime_error("échec de l'envoi d'un command buffer!"); + } + + ... +} +``` + +Nous avons implémenté tout ce qui est nécessaire à la synchronisation pour certifier qu'il n'y a pas plus de deux frames de travail +dans la queue et que ces frames n'utilise pas accidentellement la même image. Notez qu'il est tout à fait normal pour d'autre parties du code, +comme le nettoyage final, de se reposer sur des mécanismes de synchronisation plus durs comme `vkDeviceWaitIdle`. Vous devriez décider +de la bonne approche à utiliser en vous basant sur vos besoins de performances. + +Pour en apprendre plus sur la synchronisation rendez vous sur +[ces exemples complets](https://github.com/KhronosGroup/Vulkan-Docs/wiki/Synchronization-Examples#swapchain-image-acquire-and-present) +par Khronos. + +## Conclusion + +Un peu plus de 900 lignes plus tard nous avons enfin atteint le niveau où nous voyons des résultats à l'écran!! +Créer un programme avec Vulkan est clairement un énorme travail, mais grâce au contrôle que cet API vous offre vous +pouvez obtenir des performances énormes. Je ne peux que vous recommander de relire tout ce code et de vous assurer que +vous visualisez bien tout les éléments mis en jeu. Nous allons maintenant construire sur ces acquis pour étendre les +fonctionnalités de ce programme. + +Dans le prochain chapitre nous allons voir une autre petite chose nécessaire à tout bon programme Vulkan. + +[Code C++](/code/15_hello_triangle.cpp) / +[Vertex shader](/code/09_shader_base.vert) / +[Fragment shader](/code/09_shader_base.frag) diff --git "a/fr/03_Dessiner_un_triangle/04_Recr\303\251ation_de_la_swap_chain.md" "b/fr/03_Dessiner_un_triangle/04_Recr\303\251ation_de_la_swap_chain.md" new file mode 100644 index 00000000..4f979076 --- /dev/null +++ "b/fr/03_Dessiner_un_triangle/04_Recr\303\251ation_de_la_swap_chain.md" @@ -0,0 +1,279 @@ +## Introduction + +Notre application nous permet maintenant d'afficher correctement un triangle, mais certains cas de figures ne sont pas +encore correctement gérés. Il est possible que la surface d'affichage soit redimensionnée par l'utilisateur et que la +swap chain ne soit plus parfaitement compatible. Nous devons faire en sorte d'être informés de tels changements pour +pouvoir recréer la swap chain. + +## Recréer la swap chain + +Créez la fonction `recreateSwapChain` qui appelle `createSwapChain` et toutes les fonctions de création d'objets +dépendants de la swap chain ou de la taille de la fenêtre. + +```c++ +void recreateSwapChain() { + vkDeviceWaitIdle(device); + + createSwapChain(); + createImageViews(); + createRenderPass(); + createGraphicsPipeline(); + createFramebuffers(); + createCommandBuffers(); +} +``` + +Nous appelons d'abord `vkDeviceIdle` car nous ne devons surtout pas toucher à des ressources en cours d'utilisation. La +première chose à faire est bien sûr de recréer la swap chain. Les image views doivent être recrées également car +elles dépendent des images de la swap chain. La render pass doit être recrée car elle dépend du format des images de +la swap chain. Il est rare que le format des images de la swap chain soit altéré mais il n'est pas officiellement +garanti qu'il reste le même, donc nous gérerons ce cas là. La pipeline dépend de la taille des images pour la +configuration des rectangles de viewport et de ciseau, donc nous devons recréer la pipeline graphique. Il est possible +d'éviter cela en faisant de la taille de ces rectangles des états dynamiques. Finalement, les framebuffers et les +command buffers dépendent des images de la swap chain. + +Pour être certains que les anciens objets sont bien détruits avant d'en créer de nouveaux, nous devrions créer une +fonction dédiée à cela et que nous appellerons depuis `recreateSwapChain`. Créez donc `cleanupSwapChain` : + +```c++ +void cleanupSwapChain() { + +} + +void recreateSwapChain() { + vkDeviceWaitIdle(device); + + cleanupSwapChain(); + + createSwapChain(); + createImageViews(); + createRenderPass(); + createGraphicsPipeline(); + createFramebuffers(); + createCommandBuffers(); +} +``` + +Nous allons déplacer le code de suppression depuis `cleanup` jusqu'à `cleanupSwapChain` : + +```c++ +void cleanupSwapChain() { + for (size_t i = 0; i < swapChainFramebuffers.size(); i++) { + vkDestroyFramebuffer(device, swapChainFramebuffers[i], nullptr); + } + + vkFreeCommandBuffers(device, commandPool, static_cast(commandBuffers.size()), commandBuffers.data()); + + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + + for (size_t i = 0; i < swapChainImageViews.size(); i++) { + vkDestroyImageView(device, swapChainImageViews[i], nullptr); + } + + vkDestroySwapchainKHR(device, swapChain, nullptr); +} +``` + +Nous pouvons ensuite appeler cette nouvelle fonction depuis `cleanup` pour éviter la redondance de code : + +```c++ +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(); +} +``` + +Nous pourrions recréer la command pool à partir de rien mais ce serait du gâchis. J'ai préféré libérer les command +buffers existants à l'aide de la fonction `vkFreeCommandBuffers`. Nous pouvons de cette manière réutiliser la même +command pool mais changer les command buffers. + +Pour bien gérer le redimensionnement de la fenêtre nous devons récupérer la taille actuelle du framebuffer qui lui est +associé pour s'assurer que les images de la swap chain ont bien la nouvelle taille. Pour cela changez +`chooseSwapExtent` afin que cette fonction prenne en compte la nouvelle taille réelle : + +```c++ +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) + }; + + ... + } +} +``` + +C'est tout ce que nous avons à faire pour recréer la swap chain! Le problème cependant est que nous devons arrêter +complètement l'affichage pendant la recréation alors que nous pourrions éviter que les frames en vol soient perdues. +Pour cela vous devez passer l'ancienne swap chain en paramètre à `oldSwapChain` dans la structure +`VkSwapchainCreateInfoKHR` et détruire cette ancienne swap chain dès que vous ne l'utilisez plus. + +## Swap chain non-optimales ou dépassées + +Nous devons maintenant déterminer quand recréer la swap chain et donc quand appeler `recreateSwapChain`. Heureusement +pour nous Vulkan nous indiquera quand la swap chain n'est plus adéquate au moment de la présentation. Les fonctions +`vkAcquireNextImageKHR` et `vkQueuePresentKHR` peuvent pour cela retourner les valeurs suivantes : + +* `VK_ERROR_OUT_OF_DATE_KHR` : la swap chain n'est plus compatible avec la surface de fenêtre et ne peut plus être +utilisée pour l'affichage, ce qui arrive en général avec un redimensionnement de la fenêtre +* `VK_SUBOPTIMAL_KHR` : la swap chain peut toujours être utilisée pour présenter des images avec succès, mais les +caractéristiques de la surface de fenêtre ne correspondent plus à celles de la swap chain + +```c++ +VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_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("échec de la présentation d'une image à la swap chain!"); +} +``` + +Si la swap chain se trouve être dépassée quand nous essayons d'acquérir une nouvelle image il ne nous est plus possible +de présenter un quelconque résultat. Nous devons de ce fait aussitôt recréer la swap chain et tenter la présentation +avec la frame suivante. + +Vous pouvez aussi décider de recréer la swap chain si sa configuration n'est plus optimale, mais j'ai choisi de ne pas +le faire ici car nous avons de toute façon déjà acquis l'image. Ainsi `VK_SUCCES` et `VK_SUBOPTIMAL_KHR` sont considérés +comme des indicateurs de succès. + +```c++ +result = vkQueuePresentKHR(presentQueue, &presentInfo); + +if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { + recreateSwapChain(); +} else if (result != VK_SUCCESS) { + throw std::runtime_error("échec de la présentation d'une image!"); +} + +currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; +``` + +La fonction `vkQueuePresentKHR` retourne les mêmes valeurs avec la même signification. Dans ce cas nous recréons la +swap chain si elle n'est plus optimale car nous voulons les meilleurs résultats possibles. + +## Explicitement gérer les redimensionnements + +Bien que la plupart des drivers émettent automatiquement le code `VK_ERROR_OUT_OF_DATE_KHR` après qu'une fenêtre est +redimensionnée, cela n'est pas garanti par le standard. Par conséquent nous devons explictement gérer ces cas de +figure. Ajoutez une nouvelle variable qui indiquera que la fenêtre a été redimensionnée : + +```c++ +std::vector inFlightFences; +size_t currentFrame = 0; + +bool framebufferResized = false; +``` + +La fonction `drawFrame` doit ensuite être modifiée pour prendre en compte cette nouvelle variable : + +```c++ +if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { + framebufferResized = false; + recreateSwapChain(); +} else if (result != VK_SUCCESS) { + ... +} +``` + +Il est important de faire cela après `vkQueuePresentKHR` pour que les sémaphores soient dans un état correct. Pour +détecter les redimensionnements de la fenêtre nous n'avons qu'à mettre en place `glfwSetFrameBufferSizeCallback` +qui nous informera d'un changement de la taille associée à la fenêtre : + +```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) { + +} +``` + +Nous devons utiliser une fonction statique car GLFW ne sait pas correctement appeler une fonction membre d'une classe +avec `this`. + +Nous récupérons une référence à la `GLFWwindow` dans la fonction de rappel que nous fournissons. De plus nous pouvons +paramétrer un pointeur de notre choix qui sera accessible à toutes nos fonctions de rappel. Nous pouvons y mettre la +classe elle-même. + +```c++ +window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); +glfwSetWindowUserPointer(window, this); +glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); +``` + +De cette manière nous pouvons changer la valeur de la variable servant d'indicateur des redimensionnements : + +```c++ +static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; +} +``` + +Lancez maintenant le programme et changez la taille de la fenêtre pour voir si tout se passe comme prévu. + +## Gestion de la minimisation de la fenêtre + +Il existe un autre cas important où la swap chain peut devenir invalide : si la fenêtre est minimisée. Ce cas est +particulier car il résulte en un framebuffer de taille `0`. Dans ce tutoriel nous mettrons en pause le programme +jusqu'à ce que la fenêtre soit remise en avant-plan. À ce moment-là nous recréerons la swap chain. + +```c++ +void recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + vkDeviceWaitIdle(device); + + ... +} +``` + +L'appel initial à `glfwGetFramebufferSize` prend en charge le cas où la taille est déjà correcte et `glfwWaitEvents` n'aurait rien à attendre. + +Félicitations, vous avez codé un programme fonctionnel avec Vulkan! Dans le prochain chapitre nous allons supprimer les +sommets du vertex shader et mettre en place un vertex buffer. + +[Code C++](/code/16_swap_chain_recreation.cpp) / +[Vertex shader](/code/09_shader_base.vert) / +[Fragment shader](/code/09_shader_base.frag) diff --git "a/fr/04_Vertex_buffers/00_Description_des_entr\303\251es_des_sommets.md" "b/fr/04_Vertex_buffers/00_Description_des_entr\303\251es_des_sommets.md" new file mode 100644 index 00000000..58a4d6f5 --- /dev/null +++ "b/fr/04_Vertex_buffers/00_Description_des_entr\303\251es_des_sommets.md" @@ -0,0 +1,217 @@ +## Introduction + +Dans les quatre prochains chapitres nous allons remplacer les sommets inscrits dans le vertex shader par un vertex +buffer stocké dans la mémoire de la carte graphique. Nous commencerons par une manière simple de procéder en créant un +buffer manipulable depuis le CPU et en y copiant des données avec `memcpy`. Puis nous verrons comment avantageusement +utiliser un *staging buffer* pour accéder à de la mémoire de haute performance. + +## Vertex shader + +Premièrement, changeons le vertex shader en retirant les coordonnées des sommets de son code. Elles seront maintenant +stockés dans une variable. Elle sera liée au contenu du vertex buffer, ce qui est indiqué par le mot-clef `in`. Faisons +de même avec la couleur. + +```glsl +#version 450 + +layout(location = 0) in vec2 inPosition; +layout(location = 1) in vec3 inColor; + +layout(location = 0) out vec3 fragColor; + +out gl_PerVertex { + vec4 gl_Position; +}; + +void main() { + gl_Position = vec4(inPosition, 0.0, 1.0); + fragColor = inColor; +} +``` + +Les variables `inPosition` et `inColor` sont des *vertex attributes*. Ce sont des propriétés spécifiques du sommet à +l'origine de l'invocation du shader. Ces données peuvent être de différentes natures, des couleurs aux coordonnées en +passant par des coordonnées de texture. Recompilez ensuite le vertex shader. + +Tout comme pour `fragColor`, les annotations de type `layout(location=x)` assignent un indice à l'entrée. Cet indice +est utilisé depuis le code C++ pour les reconnaître. Il est important de savoir que certains types - comme les vecteurs +de flottants de double précision (64 bits) - prennent deux emplacements. Voici un exemple d'une telle situation, où il +est nécessaire de prévoir un écart entre deux entrés : + +```glsl +layout(location = 0) in dvec3 inPosition; +layout(location = 2) in vec3 inColor; +``` + +Vous pouvez trouver plus d'information sur les qualificateurs d'organisation sur +[le wiki](https://www.khronos.org/opengl/wiki/Layout_Qualifier_(GLSL)). + +## Sommets + +Nous déplaçons les données des sommets depuis le code du shader jusqu'au code C++. Commencez par inclure la librairie +GLM, afin d'utiliser des vecteurs et des matrices. Nous allons utiliser ces types pour les vecteurs de position et de +couleur. + +```c++ +#include +``` + +Créez une nouvelle structure appelée `Vertex`. Elle possède deux attributs que nous utiliserons pour le vertex shader : + +```c++ +struct Vertex { + glm::vec2 pos; + glm::vec3 color; +}; +``` + +GLM nous fournit des types très pratiques simulant les types utilisés par GLSL. + +```c++ +const std::vector vertices = { + {{0.0f, -0.5f}, {1.0f, 0.0f, 0.0f}}, + {{0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}}, + {{-0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}} +}; +``` + +Nous utiliserons ensuite un tableau de structures pour représenter un ensemble de sommets. Nous utiliserons les mêmes +couleurs et les mêmes positions qu'avant, mais elles seront combinées en un seul tableau d'objets. + +## Lier les descriptions + +La prochaine étape consiste à indiquer à Vulkan comment passer ces données au shader une fois qu'elles sont +stockées dans le GPU. Nous verrons plus tard comment les y stocker. Il y a deux types de structures que nous allons +devoir utiliser. + +Pour la première, appelée `VkVertexInputBindingDescription`, nous allons ajouter une fonction à `Vertex` qui renverra +une instance de cette structure. + +```c++ +struct Vertex { + glm::vec2 pos; + glm::vec3 color; + + static VkVertexInputBindingDescription getBindingDescription() { + VkVertexInputBindingDescription bindingDescription{}; + + return bindingDescription; + } +}; +``` + +Un *vertex binding* décrit la lecture des données stockées en mémoire. Elle fournit le nombre d'octets entre les jeux de +données et la manière de passer d'un ensemble de données (par exemple une coordonnée) au suivant. Elle permet à Vulkan +de savoir comment extraire chaque jeu de données correspondant à une invocation du vertex shader du vertex buffer. + +```c++ +VkVertexInputBindingDescription bindingDescription{}; +bindingDescription.binding = 0; +bindingDescription.stride = sizeof(Vertex); +bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; +``` + +Nos données sont compactées en un seul tableau, nous n'aurons besoin que d'un seul vertex binding. Le membre `binding` +indique l'indice du vertex binding dans le tableau des bindings. Le paramètre `stride` fournit le nombre d'octets +séparant les débuts de deux ensembles de données, c'est à dire l'écart entre les données devant ếtre fournies à une +invocation de vertex shader et celles devant être fournies à la suivante. Enfin `inputRate` peut prendre les valeurs +suivantes : + +* `VK_VERTEX_INPUT_RATE_VERTEX` : Passer au jeu de données suivante après chaque sommet +* `VK_VERTEX_INPUT_RATE_INSTANCE` : Passer au jeu de données suivantes après chaque instance + +Nous n'utilisons pas d'*instanced rendering* donc nous utiliserons `VK_VERTEX_INPUT_RATE_VERTEX`. + +## Description des attributs + +La seconde structure dont nous avons besoin est `VkVertexInputAttributeDescription`. Nous allons également en créer deux +instances depuis une fonction membre de `Vertex` : + +```c++ +#include + +... + +static std::array getAttributeDescriptions() { + std::array attributeDescriptions{}; + + return attributeDescriptions; +} +``` + +Comme le prototype le laisse entendre, nous allons avoir besoin de deux de ces structures. Elles décrivent chacunes +l'origine et la nature des données stockées dans une variable shader annotée du `location=x`, et la manière d'en +déterminer les valeurs depuis les données extraites par le binding. Comme nous avons deux de +ces variables, nous avons besoin de deux de ces structures. Voici ce qu'il faut remplir pour la position. + +```c++ +attributeDescriptions[0].binding = 0; +attributeDescriptions[0].location = 0; +attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT; +attributeDescriptions[0].offset = offsetof(Vertex, pos); +``` + +Le paramètre `binding` informe Vulkan de la provenance des données du sommet qui mené à l'invocation du vertex shader, +en lui fournissant le vertex binding qui les a extraites. Le paramètre `location` correspond à la valeur donnée à la +directive `location` dans le code du vertex shader. Dans notre cas l'entrée `0` correspond à la position du sommet +stockée dans un vecteur de floats de 32 bits. + +Le paramètre `format` permet donc de décrire le type de donnée de l'attribut. Étonnement les formats doivent être +indiqués avec des valeurs énumérées dont les noms semblent correspondre à des gradients de couleur : + +* `float` : `VK_FORMAT_R32_SFLOAT` +* `vec2` : `VK_FORMAT_R32G32_SFLOAT` +* `vec3` : `VK_FORMAT_R32G32B32_SFLOAT` +* `vec4` : `VK_FORMAT_R32G32B32A32_SFLOAT` + +Comme vous pouvez vous en douter il faudra utiliser le format dont le nombre de composants de couleurs correspond au +nombre de données à transmettre. Il est autorisé d'utiliser plus de données que ce qui est prévu dans le shader, et ces +données surnuméraires seront silencieusement ignorées. Si par contre il n'y a pas assez de valeurs les valeurs suivantes +seront utilisées par défaut pour les valeurs manquantes : 0, 0 et 1 pour les deuxième, troisième et quatrième +composantes. Il n'y a pas de valeur par défaut pour le premier membre car ce cas n'est pas autorisé. Les types +(`SFLOAT`, `UINT` et `SINT`) et le nombre de bits doivent par contre correspondre parfaitement à ce qui est indiqué dans +le shader. Voici quelques exemples : + +* `ivec2` correspond à `VK_FORMAT_R32G32_SINT` et est un vecteur à deux composantes d'entiers signés de 32 bits +* `uvec4` correspond à `VK_FORMAT_R32G32B32A32_UINT` et est un vecteur à quatre composantes d'entiers non signés de 32 +bits +* `double` correspond à `VK_FORMAT_R64_SFLOAT` et est un float à précision double (donc de 64 bits) + +Le paramètre `format` définit implicitement la taille en octets des données. Mais le binding extrait dans notre cas deux +données pour chaque sommet : la position et la couleur. Pour savoir quels octets doivent être mis dans la variable à +laquelle la structure correspond, le paramètre `offset` permet d'indiquer de combien d'octets il faut se décaler dans +les données extraites pour se trouver au début de la variable. Ce décalage est calculé automatiquement par la macro +`offsetof`. + +L'attribut de couleur est décrit de la même façon. Essayez de le remplir avant de regarder la solution ci-dessous. + +```c++ +attributeDescriptions[1].binding = 0; +attributeDescriptions[1].location = 1; +attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; +attributeDescriptions[1].offset = offsetof(Vertex, color); +``` + +## Entrée des sommets dans la pipeline + +Nous devons maintenant mettre en place la réception par la pipeline graphique des données des sommets. Nous allons +modifier une structure dans `createGraphicsPipeline`. Trouvez `vertexInputInfo` et ajoutez-y les références aux deux +structures de description que nous venons de créer : + +```c++ +auto bindingDescription = Vertex::getBindingDescription(); +auto attributeDescriptions = Vertex::getAttributeDescriptions(); + +vertexInputInfo.vertexBindingDescriptionCount = 1; +vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()); +vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; +vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); +``` + +La pipeline peut maintenant accepter les données des vertices dans le format que nous utilisons et les fournir au vertex +shader. Si vous lancez le programme vous verrez que les validation layers rapportent qu'aucun vertex buffer n'est mis +en place. Nous allons donc créer un vertex buffer et y placer les données pour que le GPU puisse les utiliser. + +[Code C++](/code/17_vertex_input.cpp) / +[Vertex shader](/code/17_shader_vertexbuffer.vert) / +[Fragment shader](/code/17_shader_vertexbuffer.frag) diff --git "a/fr/04_Vertex_buffers/01_Cr\303\251ation_de_vertex_buffers.md" "b/fr/04_Vertex_buffers/01_Cr\303\251ation_de_vertex_buffers.md" new file mode 100644 index 00000000..263d587e --- /dev/null +++ "b/fr/04_Vertex_buffers/01_Cr\303\251ation_de_vertex_buffers.md" @@ -0,0 +1,315 @@ +## Introduction + +Les buffers sont pour Vulkan des emplacements mémoire qui peuvent permettre de stocker des données quelconques sur la +carte graphique. Nous pouvons en particulier y placer les données représentant les sommets, et c'est ce que nous allons +faire dans ce chapitre. Nous verrons plus tard d'autres utilisations répandues. Au contraire des autres objets que nous +avons rencontré les buffers n'allouent pas eux-mêmes de mémoire. Il nous faudra gérer la mémoire à la main. + +## Création d'un buffer + +Créez la fonction `createVertexBuffer` et appelez-la depuis `initVulkan` juste avant `createCommandBuffers`. + +```c++ +void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createGraphicsPipeline(); + createFramebuffers(); + createCommandPool(); + createVertexBuffer(); + createCommandBuffers(); + createSyncObjects(); +} + +... + +void createVertexBuffer() { + +} +``` + +Pour créer un buffer nous allons devoir remplir une structure de type `VkBufferCreateInfo`. + +```c++ +VkBufferCreateInfo bufferInfo{}; +bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; +bufferInfo.size = sizeof(vertices[0]) * vertices.size(); +``` + +Le premier champ de cette structure s'appelle `size`. Il spécifie la taille du buffer en octets. Nous pouvons utiliser +`sizeof` pour déterminer la taille de notre tableau de valeur. + +```c++ +bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; +``` + +Le deuxième champ, appelé `usage`, correspond à l'utilisation type du buffer. Nous pouvons indiquer plusieurs valeurs +représentant les utilisations possibles. Dans notre cas nous ne mettons que la valeur qui correspond à un vertex buffer. + +```c++ +bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; +``` + +De la même manière que les images de la swap chain, les buffers peuvent soit être gérés par une queue family, ou bien +être partagés entre plusieurs queue families. Notre buffer ne sera utilisé que par la queue des graphismes, nous +pouvons donc rester en mode exclusif. + +Le paramètre `flags` permet de configurer le buffer tel qu'il puisse être constitué de plusieurs emplacements distincts +dans la mémoire. Nous n'utiliserons pas cette fonctionnalité, laissez `flags` à `0`. + +Nous pouvons maintenant créer le buffer en appelant `vkCreateBuffer`. Définissez un membre donnée pour stocker ce +buffer : + +```c++ +VkBuffer vertexBuffer; + +... + +void createVertexBuffer() { + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = sizeof(vertices[0]) * vertices.size(); + bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; + bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + if (vkCreateBuffer(device, &bufferInfo, nullptr, &vertexBuffer) != VK_SUCCESS) { + throw std::runtime_error("echec de la creation d'un vertex buffer!"); + } +} +``` + +Le buffer doit être disponible pour toutes les opérations de rendu, nous ne pouvons donc le détruire qu'à la fin du +programme, et ce dans `cleanup` car il ne dépend pas de la swap chain. + +```c++ +void cleanup() { + cleanupSwapChain(); + + vkDestroyBuffer(device, vertexBuffer, nullptr); + + ... +} +``` + +## Fonctionnalités nécessaires de la mémoire + +Le buffer a été créé mais il n'est lié à aucune forme de mémoire. La première étape de l'allocation de mémoire consiste +à récupérer les fonctionnalités dont le buffer a besoin à l'aide de la fonction `vkGetBufferMemoryRequirements`. + +```c++ +VkMemoryRequirements memRequirements; +vkGetBufferMemoryRequirements(device, vertexBuffer, &memRequirements); +``` + +La structure que la fonction nous remplit possède trois membres : + +* `size` : le nombre d'octets dont le buffer a besoin, ce qui peut différer de ce que nous avons écrit en préparant le +buffer +* `alignment` : le décalage en octets entre le début de la mémoire allouée pour lui et le début des données du buffer, +ce que le driver détermine avec les valeurs que nous avons fournies dans `usage` et `flags` +* `memoryTypeBits` : champs de bits combinant les types de mémoire qui conviennent au buffer + +Les cartes graphiques offrent plusieurs types de mémoire. Ils diffèrent en performance et en opérations disponibles. +Nous devons considérer ce dont le buffer a besoin en même temps que ce dont nous avons besoin pour sélectionner le +meilleur type de mémoire possible. Créons une fonction `findMemoryType` pour y isoler cette logique. + +```c++ +uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) { + +} +``` + +Nous allons commencer cette fonction en récupérant les différents types de mémoire que la carte graphique peut nous +offrir. + +```c++ +VkPhysicalDeviceMemoryProperties memProperties; +vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties); +``` + +La structure `VkPhysicalDeviceMemoryProperties` comprend deux tableaux appelés `memoryHeaps` et `memoryTypes`. Une pile +de mémoire (memory heap en anglais) correspond aux types physiques de mémoire. Par exemple la VRAM est une pile, de même +que la RAM utilisée comme zone de swap si la VRAM est pleine en est une autre. Tous les autres types de mémoire stockés +dans `memoryTypes` sont répartis dans ces piles. Nous n'allons pas utiliser la pile comme facteur de choix, mais vous +pouvez imaginer l'impact sur la performance que cette distinction peut avoir. + +Trouvons d'abord un type de mémoire correspondant au buffer : + +```c++ +for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { + if (typeFilter & (1 << i)) { + return i; + } +} + +throw std::runtime_error("aucun type de memoire ne satisfait le buffer!"); +``` + +Le paramètre `typeFilter` nous permettra d'indiquer les types de mémoire nécessaires au buffer lors de l'appel à la +fonction. Ce champ de bit voit son n-ième bit mis à `1` si le n-ième type de mémoire disponible lui convient. Ainsi +nous pouvons itérer sur les bits de `typeFilter` pour trouver les types de mémoire qui lui correspondent. + +Cependant cette vérification ne nous est pas suffisante. Nous devons vérifier que la mémoire est accesible depuis le CPU +afin de pouvoir y écrire les données des vertices. Nous devons pour cela vérifier que le champ de bits `properyFlags` +comprend au moins `VK_MEMORY_PROPERTY_HOSY_VISIBLE_BIT`, de même que `VK_MEMORY_PROPERTY_HOSY_COHERENT_BIT`. Nous +verrons pourquoi cette deuxième valeur est nécessaire quand nous lierons de la mémoire au buffer. + +Nous placerons ces deux valeurs dans le paramètre `properties`. Nous pouvons changer la boucle pour qu'elle prenne en +compte le champ de bits : + +```c++ +for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { + return i; + } +} +``` + +Le ET bit à bit fournit une valeur non nulle si et seulement si au moins l'une des propriétés est supportée. Nous ne +pouvons nous satisfaire de cela, c'est pourquoi il est nécessaire de comparer le résultat au champ de bits complet. Si +ce résultat nous convient, nous pouvons retourner l'indice de la mémoire et utiliser cet emplacement. Si aucune mémoire +ne convient nous levons une exception. + +## Allocation de mémoire + +Maintenant que nous pouvons déterminer un type de mémoire nous convenant, nous pouvons y allouer de la mémoire. Nous +devons pour cela remplir la structure `VkMemoryAllocateInfo`. + +```c++ +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); +``` + +Pour allouer de la mémoire il nous suffit d'indiquer une taille et un type, ce que nous avons déjà déterminé. Créez un +membre donnée pour contenir la référence à l'espace mémoire et allouez-le à l'aide de `vkAllocateMemory`. + +```c++ +VkBuffer vertexBuffer; +VkDeviceMemory vertexBufferMemory; + +... +if (vkAllocateMemory(device, &allocInfo, nullptr, &vertexBufferMemory) != VK_SUCCESS) { + throw std::runtime_error("echec d'une allocation de memoire!"); +} +``` + +Si l'allocation a réussi, nous pouvons associer cette mémoire au buffer avec la fonction `vkBindBufferMemory` : + +```c++ +vkBindBufferMemory(device, vertexBuffer, vertexBufferMemory, 0); +``` + +Les trois premiers paramètres sont évidents. Le quatrième indique le décalage entre le début de la mémoire et le début +du buffer. Nous avons alloué cette mémoire spécialement pour ce buffer, nous pouvons donc mettre `0`. Si vous décidez +d'allouer un grand espace mémoire pour y mettre plusieurs buffers, sachez qu'il faut que ce nombre soit divisible par +`memRequirements.alignement`. Notez que cette stratégie est la manière recommandée de gérer la mémoire des GPUs (voyez +[cet article](https://developer.nvidia.com/vulkan-memory-management)). + +Il est évident que cette allocation dynamique de mémoire nécessite que nous libérions l'emplacement nous-mêmes. Comme la +mémoire est liée au buffer, et que le buffer sera nécessaire à toutes les opérations de rendu, nous ne devons la libérer +qu'à la fin du programme. + + +```c++ +void cleanup() { + cleanupSwapChain(); + + vkDestroyBuffer(device, vertexBuffer, nullptr); + vkFreeMemory(device, vertexBufferMemory, nullptr); +``` + +## Remplissage du vertex buffer + +Il est maintenant temps de placer les données des vertices dans le buffer. Nous allons +[mapper la mémoire](https://en.wikipedia.org/wiki/Memory-mapped_I/O) dans un emplacement accessible par le CPU à l'aide +de la fonction `vkMapMemory`. + +```c++ +void* data; +vkMapMemory(device, vertexBufferMemory, 0, bufferInfo.size, 0, &data); +``` + +Cette fonction nous permet d'accéder à une région spécifique d'une ressource. Nous devons pour cela indiquer un décalage +et une taille. Nous mettons ici respectivement `0` et `bufferInfo.size`. Il est également possible de fournir la valeur +`VK_WHOLE_SIZE` pour mapper d'un coup toute la ressource. L'avant-dernier paramètre est un champ de bits pour l'instant +non implémenté par Vulkan. Il est impératif de la laisser à `0`. Enfin, le dernier paramètre permet de fournir un +pointeur vers la mémoire ainsi mappée. + +```c++ +void* data; +vkMapMemory(device, vertexBufferMemory, 0, bufferInfo.size, 0, &data); + memcpy(data, vertices.data(), (size_t) bufferInfo.size); +vkUnmapMemory(device, vertexBufferMemory); +``` + +Vous pouvez maintenant utiliser `memcpy` pour copier les vertices dans la mémoire, puis démapper le buffer à l'aide de +`vkUnmapMemory`. Malheureusement le driver peut décider de cacher les données avant de les copier dans le buffer. Il est +aussi possible que les données soient copiées mais que ce changement ne soit pas visible immédiatement. Il y a deux +manières de régler ce problème : + +* Utiliser une pile de mémoire cohérente avec la RAM, ce qui est indiqué par `VK_MEMORY_PROPERTY_HOST_COHERENT_BIT` +* Appeler `vkFlushMappedMemoryRanges` après avoir copié les données, puis appeler `vkInvalidateMappedMemory` avant +d'accéder à la mémoire + +Nous utiliserons la première approche qui nous assure une cohérence permanente. Cette méthode est moins performante que +le flushing explicite, mais nous verrons dès le prochain chapitre que cela n'a aucune importance car nous changerons +complètement de stratégie. + +Par ailleurs, notez que l'utilisation d'une mémoire cohérente ou le flushing de la mémoire ne garantissent que le fait +que le driver soit au courant des modifications de la mémoire. La seule garantie est que le déplacement se finisse d'ici +le prochain appel à `vkQueueSubmit`. + +Remarquez également l'utilisation de `memcpy` qui indique la compatibilité bit-à-bit des structures avec la +représentation sur la carte graphique. + +## Lier le vertex buffer + +Il ne nous reste qu'à lier le vertex buffer pour les opérations de rendu. Nous allons pour cela compléter la fonction +`createCommandBuffers`. + +```c++ +vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); + +VkBuffer vertexBuffers[] = {vertexBuffer}; +VkDeviceSize offsets[] = {0}; +vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets); + +vkCmdDraw(commandBuffers[i], static_cast(vertices.size()), 1, 0, 0); +``` + +La fonction `vkCmdBindVertexBuffers` lie des vertex buffers aux bindings. Les deuxième et troisième paramètres indiquent +l'indice du premier binding auquel le buffer correspond et le nombre de bindings qu'il contiendra. L'avant-dernier +paramètre est le tableau de vertex buffers à lier, et le dernier est un tableau de décalages en octets entre le début +d'un buffer et le début des données. Il est d'ailleurs préférable d'appeler `vkCmdDraw` avec la taille du tableau de +vertices plutôt qu'avec un nombre écrit à la main. + +Lancez maintenant le programme; vous devriez voir le triangle habituel apparaître à l'écran. + +![](/images/triangle.png) + +Essayez de colorer le vertex du haut en blanc et relancez le programme : + +```c++ +const std::vector vertices = { + {{0.0f, -0.5f}, {1.0f, 1.0f, 1.0f}}, + {{0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}}, + {{-0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}} +}; +``` + +![](/images/triangle_white.png) + +Dans le prochain chapitre nous verrons une autre manière de copier les données vers un buffer. Elle est plus performante +mais nécessite plus de travail. + +[Code C++](/code/18_vertex_buffer.cpp) / +[Vertex shader](/code/17_shader_vertexbuffer.vert) / +[Fragment shader](/code/17_shader_vertexbuffer.frag) diff --git "a/fr/04_Vertex_buffers/02_Buffer_interm\303\251diaire.md" "b/fr/04_Vertex_buffers/02_Buffer_interm\303\251diaire.md" new file mode 100644 index 00000000..b6f2fd2a --- /dev/null +++ "b/fr/04_Vertex_buffers/02_Buffer_interm\303\251diaire.md" @@ -0,0 +1,247 @@ +## Introduction + +Nous avons maintenant un vertex buffer fonctionnel. Par contre il n'est pas dans la mémoire la plus optimale posible +pour la carte graphique. Il serait préférable d'utiliser une mémoire `VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT`, +mais de telles mémoires ne sont pas accessibles depuis le CPU. Dans ce chapitre nous allons créer deux vertex buffers. +Le premier, un buffer intermédiaire (*staging buffer*), sera stocké dans de la mémoire accessible depuis le CPU, et +nous y mettrons nos données. Le second sera directement dans la carte graphique, et nous y copierons les données des +vertices depuis le buffer intermédiaire. + +## Queue de transfert + +La commande de copie des buffers provient d'une queue family qui supporte les opérations de transfert, ce qui est +indiqué par `VK_QUEUE_TRANFER_BIT`. Une bonne nouvelle : toute queue qui supporte les graphismes ou le calcul doit +supporter les transferts. Par contre il n'est pas obligatoire pour ces queues de l'indiquer dans le champ de bit qui les +décrit. + +Si vous aimez la difficulté, vous pouvez préférer l'utilisation d'une queue spécifique aux opérations de transfert. Vous +aurez alors ceci à changer : + +* Modifier la structure `QueueFamilyIndices` et la fonction `findQueueFamilies` pour obtenir une queue family dont la +description comprend `VK_QUEUE_TRANSFER_BIT` mais pas `VK_QUEUE_GRAPHICS_BIT` +* Modifier `createLogicalDevice` pour y récupérer une référence à une queue de transfert +* Créer une command pool pour les command buffers envoyés à la queue de transfert +* Changer la valeur de `sharingMode` pour les ressources qui le demandent à `VK_SHARING_MODE_CONCURRENT`, et indiquer à +la fois la queue des graphismes et la queue ds transferts +* Émettre toutes les commandes de transfert telles `vkCmdCopyBuffer` - nous allons l'utiliser dans ce chapitre - à la +queue de transfert au lieu de la queue des graphismes + +Cela représente pas mal de travail, mais vous en apprendrez beaucoup sur la gestion des resources entre les queue +families. + +## Abstraction de la création des buffers + +Comme nous allons créer plusieurs buffers, il serait judicieux de placer la logique dans une fonction. Appelez-la +`createBuffer` et déplacez-y le code suivant : + +```c++ +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("echec de la creation d'un 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("echec de l'allocation de memoire!"); + } + + vkBindBufferMemory(device, buffer, bufferMemory, 0); +} +``` + +Cette fonction nécessite plusieurs paramètres, tels que la taille du buffer, les propriétés dont nous avons besoin et +l'utilisation type du buffer. La fonction a deux résultats, elle fonctionne donc en modifiant la valeur des deux +derniers paramètres, dans lesquels elle place les référernces aux objets créés. + +Vous pouvez maintenant supprimer la création du buffer et l'allocation de la mémoire de `createVertexBuffer` et +remplacer tout ça par un appel à votre nouvelle fonction : + +```c++ +void createVertexBuffer() { + VkDeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + createBuffer(bufferSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, vertexBuffer, vertexBufferMemory); + + void* data; + vkMapMemory(device, vertexBufferMemory, 0, bufferSize, 0, &data); + memcpy(data, vertices.data(), (size_t) bufferSize); + vkUnmapMemory(device, vertexBufferMemory); +} +``` + +Lancez votre programme et assurez-vous que tout fonctionne toujours aussi bien. + +## Utiliser un buffer intermédiaire + +Nous allons maintenant faire en sorte que `createVertexBuffer` utilise d'abord un buffer visible pour copier les +données sur la carte graphique, puis qu'il utilise de la mémoire locale à la carte graphique pour le véritable buffer. + +```c++ +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); +} +``` + +Nous utilisons ainsi un nouveau `stagingBuffer` lié à la `stagingBufferMemory` pour transmettre les données à la carte +graphique. Dans ce chapitre nous allons utiliser deux nouvelles valeurs pour les utilisations des buffers : + +* `VK_BUFFER_USAGE_TRANSFER_SCR_BIT` : le buffer peut être utilisé comme source pour un transfert de mémoire +* `VK_BUFFER_USAGE_TRANSFER_DST_BIT` : le buffer peut être utilisé comme destination pour un transfert de mémoire + +Le `vertexBuffer` est maintenant alloué à partir d'un type de mémoire local au device, ce qui implique en général que +nous ne pouvons pas utiliser `vkMapMemory`. Nous pouvons cependant bien sûr y copier les données depuis le buffer +intermédiaire. Nous pouvons indiquer que nous voulons transmettre des données entre ces buffers à l'aide des valeurs +que nous avons vues juste au-dessus. Nous pouvons combiner ces informations avec par exemple +`VK_BUFFER_USAGE_VERTEX_BUFFER_BIT`. + +Nous allons maintenant écrire la fonction `copyBuffer`, qui servira à recopier le contenu du buffer intermédiaire dans +le véritable buffer. + +```c++ +void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { + +} +``` + +Les opérations de transfert de mémoire sont réalisées à travers un command buffer, comme pour l'affichage. Nous devons +commencer par allouer des command buffers temporaires. Vous devriez d'ailleurs utiliser une autre command pool pour +tous ces command buffer temporaires, afin de fournir à l'implémentation une occasion d'optimiser la gestion de la +mémoire séparément des graphismes. Si vous le faites, utilisez `VK_COMMAND_POOL_CREATE_TRANSIENT_BIT` pendant la +création de la command pool, car les commands buffers ne seront utilisés qu'une seule fois. + +```c++ +void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { + 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); +} +``` + +Enregistrez ensuite le command buffer : + +```c++ +VkCommandBufferBeginInfo beginInfo{}; +beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; +beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + +vkBeginCommandBuffer(commandBuffer, &beginInfo); +``` + +Nous allons utiliser le command buffer une fois seulement, et attendre que la copie soit +terminée avant de sortir de la fonction. Il est alors préférable d'informer le driver de cela à l'aide de +`VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT`. + +```c++ +VkBufferCopy copyRegion{}; +copyRegion.srcOffset = 0; // Optionnel +copyRegion.dstOffset = 0; // Optionnel +copyRegion.size = size; +vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); +``` + +La copie est réalisée à l'aide de la commande `vkCmdCopyBuffer`. Elle prend les buffers de source et d'arrivée comme +arguments, et un tableau des régions à copier. Ces régions sont décrites dans des structures de type `VkBufferCopy`, qui +consistent en un décalage dans le buffer source, le nombre d'octets à copier et le décalage dans le buffer d'arrivée. Il +n'est ici pas possible d'indiquer `VK_WHOLE_SIZE`. + +```c++ +vkEndCommandBuffer(commandBuffer); +``` + +Ce command buffer ne sert qu'à réaliser les copies des buffers, nous pouvons donc arrêter l'enregistrement dès +maintenant. Exécutez le command buffer pour compléter le transfert : + +```c++ +VkSubmitInfo submitInfo{}; +submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; +submitInfo.commandBufferCount = 1; +submitInfo.pCommandBuffers = &commandBuffer; + +vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE); +vkQueueWaitIdle(graphicsQueue); +``` + +Au contraire des commandes d'affichage très complexes, il n'y a pas de synchronisation particulière à mettre en place. +Nous voulons simplement nous assurer que le transfert se réalise immédiatement. Deux possibilités s'offrent alors à +nous : utiliser une fence et l'attendre avec `vkWaitForFences`, ou simplement attendre avec `vkQueueWaitIdle` que la +queue des transfert soit au repos. Les fences permettent de préparer de nombreux transferts pour qu'ils s'exécutent +concurentiellement, et offrent au driver encore une manière d'optimiser le travail. L'autre méthode a l'avantage de la +simplicité. Implémentez le système de fence si vous le désirez, mais cela vous obligera à modifier l'organisation de ce +module. + +```c++ +vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); +``` + +N'oubliez pas de libérer le command buffer utilisé pour l'opération de transfert. + +Nous pouvons maintenant appeler `copyBuffer` depuis la fonction `createVertexBuffer` pour que les sommets soient enfin +stockées dans la mémoire locale. + +```c++ +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); +``` + +Maintenant que les données sont dans la carte graphique, nous n'avons plus besoin du buffer intermédiaire, et devons +donc le détruire. + +```c++ + ... + + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); +} +``` + +Lancez votre programme pour vérifier que vous voyez toujours le même triangle. L'amélioration n'est peut-être pas +flagrante, mais il est clair que la mémoire permet d'améliorer les performances, préparant ainsi le terrain +pour le chargement de géométrie plus complexe. + +## Conclusion + +Notez que dans une application réelle, vous ne devez pas allouer de la mémoire avec `vkAllocateMemory` pour chaque +buffer. De toute façon le nombre d'appel à cette fonction est limité, par exemple à 4096, et ce même sur des cartes +graphiques comme les GTX 1080. La bonne pratique consiste à allouer une grande zone de mémoire et d'utiliser un +gestionnaire pour créer des décalages pour chacun des buffers. Il est même préférable d'utiliser un buffer pour +plusieurs types de données (sommets et uniformes par exemple) et de séparer ces types grâce à des indices dans le +buffer (voyez encore [ce même article](https://developer.nvidia.com/vulkan-memory-management)). + +Vous pouvez implémenter votre propre solution, ou bien utiliser la librairie +[VulkanMemoryAllocator](https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator) crée par GPUOpen. Pour ce +tutoriel, ne vous inquiétez pas pour cela car nous n'atteindrons pas cette limite. + +[Code C++](/code/19_staging_buffer.cpp) / +[Vertex shader](/code/17_shader_vertexbuffer.vert) / +[Fragment shader](/code/17_shader_vertexbuffer.frag) diff --git a/fr/04_Vertex_buffers/03_Index_buffer.md b/fr/04_Vertex_buffers/03_Index_buffer.md new file mode 100644 index 00000000..2e933ad6 --- /dev/null +++ b/fr/04_Vertex_buffers/03_Index_buffer.md @@ -0,0 +1,151 @@ +## Introduction + +Les modèles 3D que vous serez susceptibles d'utiliser dans des applications réelles partagerons le plus souvent des +vertices communs à plusieurs triangles. Cela est d'ailleurs le cas avec un simple rectangle : + +![](/images/vertex_vs_index.svg) + +Un rectangle est composé de triangles, ce qui signifie que nous aurions besoin d'un vertex buffer avec 6 vertices. Mais +nous dupliquerions alors des vertices, aboutissant à un gachis de mémoire. Dans des modèles plus complexes, les vertices +sont en moyenne en contact avec 3 triangles, ce qui serait encore pire. La solution consiste à utiliser un index buffer. + +Un index buffer est essentiellement un tableau de références vers le vertex buffer. Il vous permet de réordonner ou de +dupliquer les données de ce buffer. L'image ci-dessus démontre l'utilité de cette méthode. + +## Création d'un index buffer + +Dans ce chapitre, nous allons ajouter les données nécessaires à l'affichage d'un rectangle. Nous allons ainsi rajouter +une coordonnée dans le vertex buffer et créer un index buffer. Voici les données des sommets au complet : + +```c++ +const std::vector vertices = { + {{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}}, + {{0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}}, + {{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}, + {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}} +}; +``` + +Le coin en haut à gauche est rouge, celui en haut à droite est vert, celui en bas à droite est bleu et celui en bas à +gauche est blanc. Les couleurs seront dégradées par l'interpolation du rasterizer. Nous allons maintenant créer le +tableau `indices` pour représenter l'index buffer. Son contenu correspond à ce qui est présenté dans l'illustration. + +```c++ +const std::vector indices = { + 0, 1, 2, 2, 3, 0 +}; +``` + +Il est possible d'utiliser `uint16_t` ou `uint32_t` pour les valeurs de l'index buffer, en fonction du nombre d'éléments +dans `vertices`. Nous pouvons nous contenter de `uint16_t` car nous n'utilisons pas plus de 65535 sommets différents. + +Comme les données des sommets, nous devons placer les indices dans un `VkBuffer` pour que le GPU puisse y avoir accès. +Créez deux membres donnée pour référencer les ressources du futur index buffer : + +```c++ +VkBuffer vertexBuffer; +VkDeviceMemory vertexBufferMemory; +VkBuffer indexBuffer; +VkDeviceMemory indexBufferMemory; +``` + +La fonction `createIndexBuffer` est quasiment identique à `createVertexBuffer` : + +```c++ +void initVulkan() { + ... + createVertexBuffer(); + createIndexBuffer(); + ... +} + +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); +} +``` + +Il n'y a que deux différences : `bufferSize` correspond à la taille du tableau multiplié par `sizeof(uint16_t)`, et +`VK_BUFFER_USAGE_VERTEX_BUFFER_BIT` est remplacé par `VK_BUFFER_USAGE_INDEX_BUFFER_BIT`. À part ça tout est +identique : nous créons un buffer intermédiaire puis le copions dans le buffer final local au GPU. + +L'index buffer doit être libéré à la fin du programme depuis `cleanup`. + +```c++ +void cleanup() { + cleanupSwapChain(); + + vkDestroyBuffer(device, indexBuffer, nullptr); + vkFreeMemory(device, indexBufferMemory, nullptr); + + vkDestroyBuffer(device, vertexBuffer, nullptr); + vkFreeMemory(device, vertexBufferMemory, nullptr); + + ... +} +``` + +## Utilisation d'un index buffer + +Pour utiliser l'index buffer lors des opérations de rendu nous devons modifier un petit peu `createCommandBuffers`. Tout +d'abord il nous faut lier l'index buffer. La différence est qu'il n'est pas possible d'avoir plusieurs index buffers. De +plus il n'est pas possible de subdiviser les sommets en leurs coordonnées, ce qui implique que la modification d'une +seule coordonnée nécessite de créer un autre sommet le vertex buffer. + +```c++ +vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets); + +vkCmdBindIndexBuffer(commandBuffers[i], indexBuffer, 0, VK_INDEX_TYPE_UINT16); +``` + +Un index buffer est lié par la fonction `vkCmdBindIndexBuffer`. Elle prend en paramètres le buffer, le décalage dans ce +buffer et le type de donnée. Pour nous ce dernier sera `VK_INDEX_TYPE_UINT16`. + +Simplement lier le vertex buffer ne change en fait rien. Il nous faut aussi mettre à jour les commandes d'affichage +pour indiquer à Vulkan comment utiliser le buffer. Supprimez l'appel à `vkCmdDraw`, et remplacez-le par +`vkCmdDrawIndexed` : + +```c++ +vkCmdDrawIndexed(commandBuffers[i], static_cast(indices.size()), 1, 0, 0, 0); +``` + +Le deuxième paramètre indique le nombre d'indices. Le troisième est le nombre d'instances à invoquer (ici `1` car nous +n'utilisons par cette technique). Le paramètre suivant est un décalage dans l'index buffer, sachant qu'ici il ne +fonctionne pas en octets mais en indices. L'avant-dernier paramètre permet de fournir une valeur qui sera ajoutée à tous +les indices juste avant de les faire correspondre aux vertices. Enfin, le dernier paramètre est un décalage pour le +rendu instancié. + +Lancez le programme et vous devriez avoir ceci : + +![](/images/indexed_rectangle.png) + +Vous savez maintenant économiser la mémoire en réutilisant les vertices à l'aide d'un index buffer. Cela deviendra +crucial pour les chapitres suivants dans lesquels vous allez apprendre à charger des modèles complexes. + +Nous avons déjà évoqué le fait que le plus de buffers possibles devraient être stockés dans un seul emplacement +mémoire. Il faudrait dans l'idéal allez encore plus loin : +[les développeurs des drivers recommandent](https://developer.nvidia.com/vulkan-memory-management) également que vous +placiez plusieurs buffers dans un seul et même `VkBuffer`, et que vous utilisiez des décalages pour les différencier +dans les fonctions comme `vkCmdBindVertexBuffers`. Cela simplifie la mise des données dans des caches car elles sont +regroupées en un bloc. Il devient même possible d'utiliser la même mémoire pour plusieurs ressources si elles ne sont +pas utilisées en même temps et si elles sont proprement mises à jour. Cette pratique s'appelle d'ailleurs *aliasing*, et +certaines fonctions Vulkan possèdent un paramètre qui permet au développeur d'indiquer s'il veut utiliser la technique. + +[Code C++](/code/20_index_buffer.cpp) / +[Vertex shader](/code/17_shader_vertexbuffer.vert) / +[Fragment shader](/code/17_shader_vertexbuffer.frag) diff --git a/fr/05_Uniform_buffers/00_Descriptor_layout_et_buffer.md b/fr/05_Uniform_buffers/00_Descriptor_layout_et_buffer.md new file mode 100644 index 00000000..0a5753b2 --- /dev/null +++ b/fr/05_Uniform_buffers/00_Descriptor_layout_et_buffer.md @@ -0,0 +1,396 @@ +## Introduction + +Nous pouvons maintenant passer des données à chaque groupe d'invocation de vertex shaders. Mais qu'en est-il des +variables globales? Nous allons enfin passer à la 3D, et nous avons besoin d'une matrice model-view-projection. Nous +pourrions la transmettre avec les vertices, mais cela serait un gachis de mémoire et, de plus, nous devrions mettre à +jour le vertex buffer à chaque frame, alors qu'il est très bien rangé dans se mémoire à hautes performances. + +La solution fournie par Vulkan consiste à utiliser des *descripteurs de ressource* (ou *resource descriptors*), qui +font correspondre des données en mémoire à une variable shader. Un descripteur permet à des shaders d'accéder +librement à des ressources telles que les buffers ou les *images*. Attention, Vulkan donne un sens particulier au +terme image. Nous verrons cela bientôt. Nous allons pour l'instant créer un buffer qui contiendra les matrices de +transformation. Nous ferons en sorte que le vertex shader puisse y accéder. Il y a trois parties à l'utilisation d'un +descripteur de ressources : + +* Spécifier l'organisation des descripteurs durant la création de la pipeline +* Allouer un set de descripteurs depuis une pool de descripteurs (encore un objet de gestion de mémoire) +* Lier le descripteur pour les opérations de rendu + +L'*organisation du descripteur* (descriptor layout) indique le type de ressources qui seront accédées par la +pipeline. Cela ressemble sur le principe à indiquer les attachements accédés. Un *set de descripteurs* (descriptor +set) spécifie le buffer ou l'image qui sera lié à ce descripteur, de la même manière qu'un framebuffer doit indiquer +les ressources qui le composent. + +Il existe plusieurs types de descripteurs, mais dans ce chapitre nous ne verrons que les *uniform buffer objects* (UBO). +Nous en verrons d'autres plus tard, et leur utilisation sera très similaire. Rentrons dans le vif du sujet et supposons +maintenant que nous voulons que toutes les invocations du vertex shader que nous avons codé accèdent à la structure C +suivante : + +```c++ +struct UniformBufferObject { + glm::mat4 model; + glm::mat4 view; + glm::mat4 proj; +}; +``` + +Nous devons la copier dans un `VkBuffer` pour pouvoir y accéder à l'aide d'un descripteur UBO depuis le vertex shader. +De son côté le vertex shader y fait référence ainsi : + +```glsl +layout(binding = 0) uniform UniformBufferObject { + mat4 model; + mat4 view; + mat4 proj; +} ubo; + +void main() { + gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 0.0, 1.0); + fragColor = inColor; +} +``` + +Nous allons mettre à jour les matrices model, view et projection à chaque frame pour que le rectangle tourne sur +lui-même et donne un effet 3D à la scène. + +## Vertex shader + +Modifiez le vertex shader pour qu'il inclue l'UBO comme dans l'exemple ci-dessous. Je pars du principe que vous +connaissez les transformations MVP. Si ce n'est pourtant pas le cas, vous pouvez vous rendre sur +[ce site](https://www.opengl-tutorial.org/fr/beginners-tutorials/tutorial-3-matrices/) déjà mentionné dans le premier chapitre. + +```glsl +#version 450 + +layout(binding = 0) uniform UniformBufferObject { + mat4 model; + mat4 view; + mat4 proj; +} ubo; + +layout(location = 0) in vec2 inPosition; +layout(location = 1) in vec3 inColor; + +layout(location = 0) out vec3 fragColor; + +out gl_PerVertex { + vec4 gl_Position; +}; + +void main() { + gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 0.0, 1.0); + fragColor = inColor; +} +``` + +L'ordre des variables `in`, `out` et `uniform` n'a aucune importance. La directive `binding` est assez semblable à +`location` ; elle permet de fournir l'indice du binding. Nous allons l'indiquer dans l'organisation du descripteur. +Notez le changement dans la ligne calculant `gl_Position`, qui prend maintenant en compte la matrice MVP. La dernière +composante du vecteur ne sera plus à `0`, car elle sert à diviser les autres coordonnées en fonction de leur distance à +la caméra pour créer un effet de profondeur. + +## Organisation du set de descripteurs + +La prochaine étape consiste à définir l'UBO côté C++. Nous devons aussi informer Vulkan que nous voulons l'utiliser +dans le vertex shader. + +```c++ +struct UniformBufferObject { + glm::mat4 model; + glm::mat4 view; + glm::mat4 proj; +}; +``` + +Nous pouvons faire correspondre parfaitement la déclaration en C++ avec celle dans le shader grâce à GLM. De plus les +matrices sont stockées d'une manière compatible bit à bit avec l'interprétation de ces données par les shaders. Nous +pouvons ainsi utiliser `memcpy` sur une structure `UniformBufferObject` vers un `VkBuffer`. + +Nous devons fournir des informations sur chacun des descripteurs utilisés par les shaders lors de la création de la +pipeline, similairement aux entrées du vertex shader. Nous allons créer une fonction pour gérer toute cette information, +et ainsi pour créer le set de descripteurs. Elle s'appelera `createDescriptorSetLayout` et sera appelée juste avant la +finalisation de la création de la pipeline. + +```c++ +void initVulkan() { + ... + createDescriptorSetLayout(); + createGraphicsPipeline(); + ... +} + +... + +void createDescriptorSetLayout() { + +} +``` + +Chaque `binding` doit être décrit à l'aide d'une structure de type `VkDescriptorSetLayoutBinding`. + +```c++ +void createDescriptorSetLayout() { + VkDescriptorSetLayoutBinding uboLayoutBinding{}; + uboLayoutBinding.binding = 0; + uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + uboLayoutBinding.descriptorCount = 1; +} +``` + +Les deux premiers champs permettent de fournir la valeur indiquée dans le shader avec `binding` et le type de +descripteur auquel il correspond. Il est possible que la variable côté shader soit un tableau d'UBO, et dans ce cas +il faut indiquer le nombre d'éléments qu'il contient dans le membre `descriptorCount`. Cette possibilité pourrait être +utilisée pour transmettre d'un coup toutes les transformations spécifiques aux différents éléments d'une structure +hiérarchique. Nous n'utilisons pas cette possiblité et indiquons donc `1`. + +```c++ +uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; +``` + +Nous devons aussi informer Vulkan des étapes shaders qui accèderont à cette ressource. Le champ de bits `stageFlags` +permet de combiner toutes les étapes shader concernées. Vous pouvez aussi fournir la valeur +`VK_SHADER_STAGE_ALL_GRAPHICS`. Nous mettons uniquement `VK_SHADER_STAGE_VERTEX_BIT`. + +```c++ +uboLayoutBinding.pImmutableSamplers = nullptr; // Optionnel +``` + +Le champ `pImmutableSamplers` n'a de sens que pour les descripteurs liés aux samplers d'images. Nous nous attaquerons à +ce sujet plus tard. Vous pouvez le mettre à `nullptr`. + +Tous les liens des descripteurs sont ensuite combinés en un seul objet `VkDescriptorSetLayout`. Créez pour cela un +nouveau membre donnée : + +```c++ +VkDescriptorSetLayout descriptorSetLayout; +VkPipelineLayout pipelineLayout; +``` + +Nous pouvons créer cet objet à l'aide de la fonction `vkCreateDescriptorSetLayout`. Cette fonction prend en argument une +structure de type `VkDescriptorSetLayoutCreateInfo`. Elle contient un tableau contenant les structures qui décrivent les +bindings : + +```c++ +VkDescriptorSetLayoutCreateInfo layoutInfo{}; +layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; +layoutInfo.bindingCount = 1; +layoutInfo.pBindings = &uboLayoutBinding; + +if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &descriptorSetLayout) != VK_SUCCESS) { + throw std::runtime_error("echec de la creation d'un set de descripteurs!"); +} +``` + +Nous devons fournir cette structure à Vulkan durant la création de la pipeline graphique. Ils sont transmis par la +structure `VkPipelineLayoutCreateInfo`. Modifiez ainsi la création de cette structure : + +```c++ +VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; +pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; +pipelineLayoutInfo.setLayoutCount = 1; +pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout; +``` + +Vous vous demandez peut-être pourquoi il est possible de spécifier plusieurs set de descripteurs dans cette structure, +dans la mesure où un seul inclut tous les `bindings` d'une pipeline. Nous y reviendrons dans le chapitre suivant, quand +nous nous intéresserons aux pools de descripteurs. + +L'objet que nous avons créé ne doit être détruit que lorsque le programme se termine. + +```c++ +void cleanup() { + cleanupSwapChain(); + + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); + + ... +} +``` + +## Uniform buffer + +Dans le prochain chapitre nous référencerons le buffer qui contient les données de l'UBO. Mais nous devons bien sûr +d'abord créer ce buffer. Comme nous allons accéder et modifier les données du buffer à chaque frame, il est assez +inutile d'utiliser un buffer intermédiaire. Ce serait même en fait contre-productif en terme de performances. + +Comme des frames peuvent être "in flight" pendant que nous essayons de modifier le contenu du buffer, nous allons avoir +besoin de plusieurs buffers. Nous pouvons soit en avoir un par frame, soit un par image de la swap chain. Comme nous +avons un command buffer par image nous allons utiliser cette seconde méthode. + +Pour cela créez les membres données `uniformBuffers` et `uniformBuffersMemory` : + +```c++ +VkBuffer indexBuffer; +VkDeviceMemory indexBufferMemory; + +std::vector uniformBuffers; +std::vector uniformBuffersMemory; +``` + +Créez ensuite une nouvelle fonction appelée `createUniformBuffers` et appelez-la après `createIndexBuffers`. Elle +allouera les buffers : + +```c++ +void initVulkan() { + ... + createVertexBuffer(); + createIndexBuffer(); + createUniformBuffers(); + ... +} + +... + +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]); + } +} +``` + +Nous allons créer une autre fonction qui mettra à jour le buffer en appliquant à son contenu une transformation à chaque +frame. Nous n'utiliserons donc pas `vkMapMemory` ici. Le buffer doit être détruit à la fin du programme. Mais comme il +dépend du nombre d'images de la swap chain, et que ce nombre peut évoluer lors d'une reécration, nous devons le +supprimer depuis `cleanupSwapChain` : + +```c++ +void cleanupSwapChain() { + ... + + for (size_t i = 0; i < swapChainImages.size(); i++) { + vkDestroyBuffer(device, uniformBuffers[i], nullptr); + vkFreeMemory(device, uniformBuffersMemory[i], nullptr); + } + + ... +} +``` + +Nous devons également le recréer depuis `recreateSwapChain` : + +```c++ +void recreateSwapChain() { + ... + createFramebuffers(); + createUniformBuffers(); + createCommandBuffers(); +} +``` + +## Mise à jour des données uniformes + +Créez la fonction `updateUniformBuffer` et appelez-la dans `drawFrame`, juste après que nous avons déterminé l'image de +la swap chain que nous devons acquérir : + +```c++ +void drawFrame() { + ... + + uint32_t imageIndex; + VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + + ... + + updateUniformBuffer(imageIndex); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + + ... +} + +... + +void updateUniformBuffer(uint32_t currentImage) { + +} +``` + +Cette fonction générera une rotation à chaque frame pour que la géométrie tourne sur elle-même. Pour ces fonctionnalités +mathématiques nous devons inclure deux en-têtes : + +```c++ +#define GLM_FORCE_RADIANS +#include +#include + +#include +``` + +Le header `` expose des fonctions comme `glm::rotate`, `glm:lookAt` ou `glm::perspective`, +dont nous avons besoin pour implémenter la 3D. La macro `GLM_FORCE_RADIANS` permet d'éviter toute confusion sur la +représentation des angles. + +Pour que la rotation s'exécute à une vitesse indépendante du FPS, nous allons utiliser les fonctionnalités de mesure +précise de la librairie standrarde C++. Incluez donc `` : + +```c++ +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(); +} +``` + +Nous commençons donc par écrire la logique de calcul du temps écoulé, mesuré en secondes et stocké dans un `float`. + +Nous allons ensuite définir les matrices model, view et projection stockées dans l'UBO. La rotation sera implémentée +comme une simple rotation autour de l'axe Z en fonction de la variable `time` : + +```c++ +UniformBufferObject ubo{}; +ubo.model = glm::rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); +``` + +La fonction `glm::rotate` accepte en argument une matrice déjà existante, un angle de rotation et un axe de rotation. Le +constructeur `glm::mat4(1.0)` crée une matrice identité. Avec la multiplication `time * glm::radians(90.0f)` la +géométrie tournera de 90 degrés par seconde. + +```c++ +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)); +``` + +Pour la matrice view, j'ai décidé de la générer de telle sorte que nous regardions le rectangle par dessus avec une +inclinaison de 45 degrés. La fonction `glm::lookAt` prend en arguments la position de l'oeil, la cible du regard et +l'axe servant de référence pour le haut. + +```c++ +ubo.proj = glm::perspective(glm::radians(45.0f), swapChainExtent.width / (float) swapChainExtent.height, 0.1f, 10.0f); +``` + +J'ai opté pour un champ de vision de 45 degrés. Les autres paramètres de `glm::perspective` sont le ratio et les plans +near et far. Il est important d'utiliser l'étendue actuelle de la swap chain pour calculer le ratio, afin d'utiliser les +valeurs qui prennent en compte les redimensionnements de la fenêtre. + +```c++ +ubo.proj[1][1] *= -1; +``` + +GLM a été conçue pour OpenGL, qui utilise les coordonnées de clip et de l'axe Y à l'envers. La manière la plus simple de +compenser cela consiste à changer le signe de l'axe Y dans la matrice de projection. + +Maintenant que toutes les transformations sont définies nous pouvons copier les données dans le buffer uniform actuel. +Nous utilisons la première technique que nous avons vue pour la copie de données dans un buffer. + +```c++ +void* data; +vkMapMemory(device, uniformBuffersMemory[currentImage], 0, sizeof(ubo), 0, &data); + memcpy(data, &ubo, sizeof(ubo)); +vkUnmapMemory(device, uniformBuffersMemory[currentImage]); +``` + +Utiliser un UBO de cette manière n'est pas le plus efficace pour transmettre des données fréquemment mises à jour. Une +meilleure pratique consiste à utiliser les *push constants*, que nous aborderons peut-être dans un futur chapitre. + +Dans un avenir plus proche nous allons lier les sets de descripteurs au `VkBuffer` contenant les données des matrices, +afin que le vertex shader puisse y avoir accès. + +[Code C++](/code/21_descriptor_layout.cpp) / +[Vertex shader](/code/21_shader_ubo.vert) / +[Fragment shader](/code/21_shader_ubo.frag) diff --git a/fr/05_Uniform_buffers/01_Descriptor_pool_et_sets.md b/fr/05_Uniform_buffers/01_Descriptor_pool_et_sets.md new file mode 100644 index 00000000..d4763b92 --- /dev/null +++ b/fr/05_Uniform_buffers/01_Descriptor_pool_et_sets.md @@ -0,0 +1,399 @@ +## Introduction + +L'objet `VkDescriptorSetLayout` que nous avons créé dans le chapitre précédent décrit les descripteurs que nous devons +lier pour les opérations de rendu. Dans ce chapitre nous allons créer les véritables sets de descripteurs, un pour +chaque `VkBuffer`, afin que nous puissions chacun les lier au descripteur de l'UBO côté shader. + +## Pool de descripteurs + +Les sets de descripteurs ne peuvent pas être crées directement. Il faut les allouer depuis une pool, comme les command +buffers. Nous allons créer la fonction `createDescriptorPool` pour générer une pool de descripteurs. + +```c++ +void initVulkan() { + ... + createUniformBuffer(); + createDescriptorPool(); + ... +} + +... + +void createDescriptorPool() { + +} +``` + +Nous devons d'abord indiquer les types de descripteurs et combien sont compris dans les sets. Nous utilisons pour cela +une structure du type `VkDescriptorPoolSize` : + +```c++ +VkDescriptorPoolSize poolSize{}; +poolSize.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; +poolSize.descriptorCount = static_cast(swapChainImages.size()); +``` + +Nous allons allouer un descripteur par frame. Cette structure doit maintenant être référencée dans la structure +principale `VkDescriptorPoolCreateInfo`. + +```c++ +VkDescriptorPoolCreateInfo poolInfo{}; +poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; +poolInfo.poolSizeCount = 1; +poolInfo.pPoolSizes = &poolSize; +``` + +Nous devons aussi spécifier le nombre maximum de sets de descripteurs que nous sommes susceptibles d'allouer. + +```c++ +poolInfo.maxSets = static_cast(swapChainImages.size()); +``` + +La structure possède un membre optionnel également présent pour les command pools. Il permet d'indiquer que les +sets peuvent être libérés indépendemment les uns des autres avec la valeur +`VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT`. Comme nous n'allons pas toucher aux descripteurs pendant que le +programme s'exécute, nous n'avons pas besoin de l'utiliser. Indiquez `0` pour ce champ. + +```c++ +VkDescriptorPool descriptorPool; + +... + +if (vkCreateDescriptorPool(device, &poolInfo, nullptr, &descriptorPool) != VK_SUCCESS) { + throw std::runtime_error("echec de la creation de la pool de descripteurs!"); +} +``` + +Créez un nouveau membre donnée pour référencer la pool, puis appelez `vkCreateDescriptorPool`. La pool doit être +recrée avec la swap chain.. + +```c++ +void cleanupSwapChain() { + ... + for (size_t i = 0; i < swapChainImages.size(); i++) { + vkDestroyBuffer(device, uniformBuffers[i], nullptr); + vkFreeMemory(device, uniformBuffersMemory[i], nullptr); + } + + vkDestroyDescriptorPool(device, descriptorPool, nullptr); + + ... +} +``` + +Et recréée dans `recreateSwapChain` : + +```c++ +void recreateSwapChain() { + ... + createUniformBuffers(); + createDescriptorPool(); + createCommandBuffers(); +} +``` + +## Set de descripteurs + +Nous pouvons maintenant allouer les sets de descripteurs. Créez pour cela la fonction `createDescriptorSets` : + +```c++ +void initVulkan() { + ... + createDescriptorPool(); + createDescriptorSets(); + ... +} + +... + +void createDescriptorSets() { + +} +``` + +L'allocation de cette ressource passe par la création d'une structure de type `VkDescriptorSetAllocateInfo`. Vous devez +bien sûr y indiquer la pool d'où les allouer, de même que le nombre de sets à créer et l'organisation qu'ils doivent +suivre. + +```c++ +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(); +``` + +Dans notre cas nous allons créer autant de sets qu'il y a d'images dans la swap chain. Ils auront tous la même +organisation. Malheureusement nous devons copier la structure plusieurs fois car la fonction que nous allons utiliser +prend en argument un tableau, dont le contenu doit correspondre indice à indice aux objets à créer. + +Ajoutez un membre donnée pour garder une référence aux sets, et allouez-les avec `vkAllocateDescriptorSets` : + +```c++ +VkDescriptorPool descriptorPool; +std::vector descriptorSets; + +... + +descriptorSets.resize(swapChainImages.size()); +if (vkAllocateDescriptorSets(device, &allocInfo, descriptorSets.data()) != VK_SUCCESS) { + throw std::runtime_error("echec de l'allocation d'un set de descripteurs!"); +} +``` + +Il n'est pas nécessaire de détruire les sets de descripteurs explicitement, car leur libération est induite par la +destruction de la pool. L'appel à `vkAllocateDescriptorSets` alloue donc tous les sets, chacun possédant un unique +descripteur d'UBO. + +```c++ +void cleanup() { + ... + vkDestroyDescriptorPool(device, descriptorPool, nullptr); + + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); + ... +} +``` + +Nous avons créé les sets mais nous n'avons pas paramétré les descripteurs. Nous allons maintenant créer une boucle pour +rectifier ce problème : + +```c++ +for (size_t i = 0; i < swapChainImages.size(); i++) { + +} +``` + +Les descripteurs référant à un buffer doivent être configurés avec une structure de type `VkDescriptorBufferInfo`. Elle +indique le buffer contenant les données, et où les données y sont stockées. + +```c++ +for (size_t i = 0; i < swapChainImages.size(); i++) { + VkDescriptorBufferInfo bufferInfo{}; + bufferInfo.buffer = uniformBuffers[i]; + bufferInfo.offset = 0; + bufferInfo.range = sizeof(UniformBufferObject); +} +``` + +Nous allons utiliser tout le buffer, il est donc aussi possible d'indiquer `VK_WHOLE_SIZE`. La configuration des +descripteurs est maintenant mise à jour avec la fonction `vkUpdateDescriptorSets`. Elle prend un tableau de +`VkWriteDescriptorSet` en paramètre. + +```c++ +VkWriteDescriptorSet descriptorWrite{}; +descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; +descriptorWrite.dstSet = descriptorSets[i]; +descriptorWrite.dstBinding = 0; +descriptorWrite.dstArrayElement = 0; +``` + +Les deux premiers champs spécifient le set à mettre à jour et l'indice du binding auquel il correspond. Nous avons donné +à notre unique descripteur l'indice `0`. Souvenez-vous que les descripteurs peuvent être des tableaux ; nous devons donc +aussi indiquer le premier élément du tableau que nous voulons modifier. Nous n'en n'avons qu'un. + +```c++ +descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; +descriptorWrite.descriptorCount = 1; +``` + +Nous devons encore indiquer le type du descripteur. Il est possible de mettre à jour plusieurs descripteurs d'un même +type en même temps. La fonction commence à `dstArrayElement` et s'étend sur `descriptorCount` descripteurs. + +```c++ +descriptorWrite.pBufferInfo = &bufferInfo; +descriptorWrite.pImageInfo = nullptr; // Optionnel +descriptorWrite.pTexelBufferView = nullptr; // Optionnel +``` + +Le dernier champ que nous allons utiliser est `pBufferInfo`. Il permet de fournir `descriptorCount` structures qui +configureront les descripteurs. Les autres champs correspondent aux structures qui peuvent configurer des descripteurs +d'autres types. Ainsi il y aura `pImageInfo` pour les descripteurs liés aux images, et `pTexelBufferInfo` pour les +descripteurs liés aux buffer views. + +```c++ +vkUpdateDescriptorSets(device, 1, &descriptorWrite, 0, nullptr); +``` + +Les mises à jour sont appliquées quand nous appelons `vkUpdateDescriptorSets`. La fonction accepte deux tableaux, un de +`VkWriteDesciptorSets` et un de `VkCopyDescriptorSet`. Le second permet de copier des descripteurs. + +## Utiliser des sets de descripteurs + +Nous devons maintenant étendre `createCommandBuffers` pour qu'elle lie les sets de descripteurs aux descripteurs des +shaders avec la commande `vkCmdBindDescriptorSets`. Il faut invoquer cette commande dans l'enregistrement des command +buffers avant l'appel à `vkCmdDrawIndexed`. + +```c++ +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); +``` + +Au contraire des buffers de vertices et d'indices, les sets de descripteurs ne sont pas spécifiques aux pipelines +graphiques. Nous devons donc spécifier que nous travaillons sur une pipeline graphique et non pas une pipeline de +calcul. Le troisième paramètre correspond à l'organisation des descripteurs. Viennent ensuite l'indice du premier +descripteur, la quantité à évaluer et bien sûr le set d'où ils proviennent. Nous y reviendrons. Les deux derniers +paramètres sont des décalages utilisés pour les descripteurs dynamiques. Nous y reviendrons aussi dans un futur +chapitre. + +Si vous lanciez le programme vous verrez que rien ne s'affiche. Le problème est que l'inversion de la coordonnée Y dans +la matrice induit l'évaluation des vertices dans le sens inverse des aiguilles d'une montre (*counter-clockwise* en anglais), +alors que nous voudrions le contraire. En effet, les systèmes actuels utilisent ce sens de rotation pour détermnier la face de devant. +La face de derrière est ensuite simplement ignorée. C'est pourquoi notre géométrie n'est pas rendue. C'est le *backface culling*. +Changez le champ `frontface` de la structure `VkPipelineRasterizationStateCreateInfo` dans la fonction +`createGraphicsPipeline` de la manière suivante : + +```c++ +rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; +rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; +``` + +Maintenant vous devriez voir ceci en lançant votre programme : + +![](/images/spinning_quad.png) + +Le rectangle est maintenant un carré car la matrice de projection corrige son aspect. La fonction `updateUniformBuffer` +inclut d'office les redimensionnements d'écran, il n'est donc pas nécessaire de recréer les descripteurs dans +`recreateSwapChain`. + +## Alignement + +Jusqu'à présent nous avons évité la question de la compatibilité des types côté C++ avec la définition des types pour +les variables uniformes. Il semble évident d'utiliser des types au même nom des deux côtés : + +```c++ +struct UniformBufferObject { + glm::mat4 model; + glm::mat4 view; + glm::mat4 proj; +}; +layout(binding = 0) uniform UniformBufferObject { + mat4 model; + mat4 view; + mat4 proj; +} ubo; +``` + +Pourtant ce n'est pas aussi simple. Essayez la modification suivante : + +```c++ +struct UniformBufferObject { + glm::vec2 foo; + glm::mat4 model; + glm::mat4 view; + glm::mat4 proj; +}; +layout(binding = 0) uniform UniformBufferObject { + vec2 foo; + mat4 model; + mat4 view; + mat4 proj; +} ubo; +``` + +Recompilez les shaders et relancez le programme. Le carré coloré a disparu! La raison réside dans cette question de +l'alignement. + +Vulkan s'attend à un certain alignement des données en mémoire pour chaque type. Par exemple : + +* Les scalaires doivent être alignés sur leur nombre d'octets N (float de 32 bits donne un alognement de 4 octets) +* Un `vec2` doit être aligné sur 2N (8 octets) +* Les `vec3` et `vec4` doivent être alignés sur 4N (16 octets) +* Une structure imbriquée doit être alignée sur la somme des alignements de ses membres arrondie sur le multiple de +16 octets au-dessus +* Une `mat4` doit avoir le même alignement qu'un `vec4` + +Les alignemenents imposés peuvent être trouvés dans +[la spécification](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap15.html#interfaces-resources-layout) + +Notre shader original et ses trois `mat4` était bien aligné. `model` a un décalage de 0, `view` de 64 et `proj` de 128, +ce qui sont des multiples de 16. + +La nouvelle structure commence avec un membre de 8 octets, ce qui décale tout ce qui suit. Les décalages sont augmentés +de 8 et ne sont alors plus multiples de 16. Nous pouvons fixer ce problème avec le mot-clef `alignas` : + +```c++ +struct UniformBufferObject { + glm::vec2 foo; + alignas(16) glm::mat4 model; + glm::mat4 view; + glm::mat4 proj; +}; +``` + +Si vous recompilez et relancez, le programme devrait fonctionner à nouveau. + +Heureusement pour nous, GLM inclue un moyen qui nous permet de plus penser à ce souci d'alignement : + +```c++ +#define GLM_FORCE_RADIANS +#define GLM_FORCE_DEFAULT_ALIGNED_GENTYPES +#include +``` + +La ligne `#define GLM_FORCE_DEFAULT_ALIGNED_GENTYPES` force GLM a s'assurer de l'alignement des types qu'elle expose. +La limite de cette méthode s'atteint en utilisant des structures imbriquées. Prenons l'exemple suivant : + +```c++ +struct Foo { + glm::vec2 v; +}; +struct UniformBufferObject { + Foo f1; + Foo f2; +}; +``` + +Et côté shader mettons : + +```c++ +struct Foo { + vec2 v; +}; +layout(binding = 0) uniform UniformBufferObject { + Foo f1; + Foo f2; +} ubo; +``` + +Nous nous retrouvons avec un décalage de 8 pour `f2` alors qu'il lui faudrait un décalage de 16. Il faut dans ce cas +de figure utiliser `alignas` : + +```c++ +struct UniformBufferObject { + Foo f1; + alignas(16) Foo f2; +}; +``` + +Pour cette raison il est préférable de toujours être explicite à propos de l'alignement de données que l'on envoie aux +shaders. Vous ne serez pas supris par des problèmes d'alignement imprévus. + +```c++ +struct UniformBufferObject { + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; +}; +``` + +Recompilez le shader avant de continuer la lecture. + +## Plusieurs sets de descripteurs + +Comme on a pu le voir dans les en-têtes de certaines fonctions, il est possible de lier plusieurs sets de descripteurs +en même temps. Vous devez fournir une organisation pour chacun des sets pendant la mise en place de l'organisation de la +pipeline. Les shaders peuvent alors accéder aux descripteurs de la manière suivante : + +```c++ +layout(set = 0, binding = 0) uniform UniformBufferObject { ... } +``` + +Vous pouvez utiliser cette possibilité pour placer dans différents sets les descripteurs dépendant d'objets et les +descripteurs partagés. De cette manière vous éviter de relier constemment une partie des descripteurs, ce qui peut être +plus performant. + +[Code C++](/code/22_descriptor_sets.cpp) / +[Vertex shader](/code/21_shader_ubo.vert) / +[Fragment shader](/code/21_shader_ubo.frag) diff --git a/fr/06_Texture_mapping/00_Images.md b/fr/06_Texture_mapping/00_Images.md new file mode 100644 index 00000000..32e8ec63 --- /dev/null +++ b/fr/06_Texture_mapping/00_Images.md @@ -0,0 +1,687 @@ +## Introduction + +Jusqu'à présent nous avons écrit les couleurs dans les données de chaque sommet, pratique peu efficace. Nous allons +maintenant implémenter l'échantillonnage (sampling) des textures, afin que le rendu soit plus intéressant. Nous +pourrons ensuite passer à l'affichage de modèles 3D dans de futurs chapitres. + +L'ajout d'une texture comprend les étapes suivantes : + +* Créer un objet *image* stocké sur la mémoire de la carte graphique +* La remplir avec les pixels extraits d'un fichier image +* Créer un sampler +* Ajouter un descripteur pour l'échantillonnage de l'image + +Nous avons déjà travaillé avec des images, mais nous n'en avons jamais créé. Celles que nous avons manipulées avaient +été automatiquement crées par la swap chain. Créer une image et la remplir de pixels ressemble à la création d'un vertex +buffer. Nous allons donc commencer par créer une ressource intermédiaire pour y faire transiter les données que nous +voulons retrouver dans l'image. Bien qu'il soit possible d'utiliser une image comme intermédiaire, il est aussi autorisé +de créer un `VkBuffer` comme intermédiaire vers l'image, et cette méthode est +[plus rapide sur certaines plateformes](https://developer.nvidia.com/vulkan-memory-management). Nous allons donc +d'abord créer un buffer et y mettre les données relatives aux pixels. Pour l'image nous devrons nous enquérir des +spécificités de la mémoire, allouer la mémoire nécessaire et y copier les pixels. Cette procédure est très +similaire à la création de buffers. + +La grande différence - il en fallait une tout de même - réside dans l'organisation des données à l'intérieur même des +pixels. Leur organisation affecte la manière dont les données brutes de la mémoire sont interprétées. De plus, stocker +les pixels ligne par ligne n'est pas forcément ce qui se fait de plus efficace, et cela est dû à la manière dont les +cartes graphiques fonctionnent. Nous devrons donc faire en sorte que les images soient organisées de la meilleure +manière possible. Nous avons déjà croisé certaines organisation lors de la création de la passe de rendu : + +* `VK_IMAGE_LAYOUT_PRESENT_SCR_KHR` : optimal pour la présentation +* `VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL` : optimal pour être l'attachement cible du fragment shader donc en tant que +cible de rendu +* `VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL` : optimal pour être la source d'un transfert comme `vkCmdCopyImageToBuffer` +* `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL` : optimal pour être la cible d'un transfert comme `vkCmdCopyBufferToImage` +* `VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL` : optimal pour être échantillonné depuis un shader + +La plus commune des méthode spour réaliser une transition entre différentes organisations est la *barrière pipeline*. +Celles-ci sont principalement utilisées pour synchroniser l'accès à une ressource, mais peuvent aussi permettre la +transition d'un état à un autre. Dans ce chapitre nous allons utiliser cette seconde possibilité. Les barrières peuvent +enfin être utilisées pour changer la queue family qui possède une ressource. + +## Librairie de chargement d'image + +De nombreuses librairies de chargement d'images existent ; vous pouvez même écrire la vôtre pour des formats simples +comme BMP ou PPM. Nous allons utiliser stb_image, de [la collection stb](https://github.com/nothings/stb). Elle +possède l'avantage d'être écrite en un seul fichier. Téléchargez donc `stb_image.h` et placez-la ou vous voulez, par +exemple dans le dossier où sont stockés GLFW et GLM. + +**Visual Studio** + +Ajoutez le dossier comprenant `stb_image.h` dans `Additional Include Directories`. + +![](/images/include_dirs_stb.png) + +**Makefile** + +Ajoutez le dossier comprenant `stb_image.h` aux chemins parcourus par GCC : + +```text +VULKAN_SDK_PATH = /home/user/VulkanSDK/x.x.x.x/x86_64 +STB_INCLUDE_PATH = /home/user/libraries/stb + +... + +CFLAGS = -std=c++17 -I$(VULKAN_SDK_PATH)/include -I$(STB_INCLUDE_PATH) +``` + +## Charger une image + +Incluez la librairie de cette manière : + +```c++ +#define STB_IMAGE_IMPLEMENTATION +#include +``` + +Le header simple ne fournit que les prototypes des fonctions. Nous devons demander les implémentations avec la define +`STB_IMAGE_IMPLEMENTATION` pour ne pas avoir d'erreurs à l'édition des liens. + +```c++ +void initVulkan() { + ... + createCommandPool(); + createTextureImage(); + createVertexBuffer(); + ... +} + +... + +void createTextureImage() { + +} +``` + +Créez la fonction `createTextureImage`, depuis laquelle nous chargerons une image et la placerons dans un objet Vulkan +représentant une image. Nous allons avoir besoin de command buffers, il faut donc appeler cette fonction après +`createCommandPool`. + +Créez un dossier `textures` au même endroit que `shaders` pour y placer les textures. Nous allons y mettre un fichier +appelé `texture.jpg` pour l'utiliser dans notre programme. J'ai choisi d'utiliser +[cette image de license CC0](https://pixbay.com/en/statue-sculpture-fig-historically-1275469) redimensionnée à 512x512, +mais vous pouvez bien sûr en utiliser une autre. La librairie supporte des formats tels que JPEG, PNG, BMP ou GIF. + +![](/images/texture.jpg) + +Le chargement d'une image est très facile avec cette librairie : + +```c++ +void createTextureImage() { + int texWidth, texHeight, texChannels; + stbi_uc* pixels = stbi_load("textures/texture.jpg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + VkDeviceSize imageSize = texWidth * texHeight * 4; + + if (!pixels) { + throw std::runtime_error("échec du chargement d'une image!"); + } +} +``` + +La fonction `stbi_load` prend en argument le chemin de l'image et les différentes canaux à charger. L'argument +`STBI_rgb_alpha` force la fonction à créer un canal alpha même si l'image originale n'en possède pas. Cela simplifie le +travail en homogénéisant les situations. Les trois arguments transmis en addresse servent de résultats pour stocker +des informations sur l'image. Les pixels sont retournés sous forme du pointeur `stbi_uc *pixels`. Ils sont organisés +ligne par ligne et ont chacun 4 octets, ce qui représente `texWidth * texHeight * 4` octets au total pour l'image. + +## Buffer intermédiaire + +Nous allons maintenant créer un buffer en mémoire accessible pour que nous puissions utiliser `vkMapMemory` et y placer +les pixels. Ajoutez les variables suivantes à la fonction pour contenir ce buffer temporaire : + +```c++ +VkBuffer stagingBuffer; +VkDeviceMemory stagingBufferMemory; +``` + +Le buffer doit être en mémoire visible pour que nous puissions le mapper, et il doit être utilisable comme source d'un +transfert vers une image, d'où l'appel suivant : + +```c++ +createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); +``` + +Nous pouvons placer tel quels les pixels que nous avons récupérés dans le buffer : + +```c++ +void* data; +vkMapMemory(device, stagingBufferMemory, 0, imageSize, 0, &data); + memcpy(data, pixels, static_cast(imageSize)); +vkUnmapMemory(device, stagingBufferMemory); +``` + +Il ne faut surtout pas oublier de libérer le tableau de pixels après cette opération : + +```c++ +stbi_image_free(pixels); +``` + +## Texture d'image + +Bien qu'il nous soit possible de paramétrer le shader afin qu'il utilise le buffer comme source de pixels, il est bien +plus efficace d'utiliser un objet image. Ils rendent plus pratique, mais surtout plus rapide, l'accès aux données de +l'image en nous permettant d'utiliser des coordonnées 2D. Les pixels sont appelés texels dans le contexte du shading, et +nous utiliserons ce terme à partir de maintenant. Ajoutez les membres données suivants : + +```c++ +VkImage textureImage; +VkDeviceMemory textureImageMemory; +``` + +Les paramètres pour la création d'une image sont indiqués dans une structure de type `VkImageCreateInfo` : + +```c++ +VkImageCreateInfo imageInfo{}; +imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; +imageInfo.imageType = VK_IMAGE_TYPE_2D; +imageInfo.extent.width = static_cast(texWidth); +imageInfo.extent.height = static_cast(texHeight); +imageInfo.extent.depth = 1; +imageInfo.mipLevels = 1; +imageInfo.arrayLayers = 1; +``` + +Le type d'image contenu dans `imageType` indique à Vulkan le repère dans lesquels les texels sont placés. Il est +possible de créer des repères 1D, 2D et 3D. Les images 1D peuvent être utilisés comme des tableaux ou des gradients. Les +images 2D sont majoritairement utilisés comme textures. Certaines techniques les utilisent pour stocker autre chose +que des couleur, par exemple des vecteurs. Les images 3D peuvent être utilisées pour stocker des voxels par +exemple. Le champ `extent` indique la taille de l'image, en terme de texels par axe. Comme notre texture fonctionne +comme un plan dans un espace en 3D, nous devons indiquer `1` au champ `depth`. Finalement, notre texture n'est pas un +tableau, et nous verrons le mipmapping plus tard. + +```c++ +imageInfo.format = VK_FORMAT_R8G8B8A8_SRGB; +``` + +Vulkan supporte de nombreux formats, mais nous devons utiliser le même format que les données présentes dans le buffer. + +```c++ +imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; +``` + +Le champ `tiling` peut prendre deux valeurs : + +* `VK_IMAGE_TILING_LINEAR` : les texels sont organisés ligne par ligne +* `VK_IMAGE_TILING_OPTIMAL` : les texels sont organisés de la manière la plus optimale pour l'implémentation + +Le mode mis dans `tiling` ne peut pas être changé, au contraire de l'organisation de l'image. Par conséquent, si vous +voulez pouvoir directement accéder aux texels, comme il faut qu'il soient organisés d'une manière logique, il vous faut +indiquer `VK_IMAGE_TILING_LINEAR`. Comme nous utilisons un buffer intermédiaire et non une image intermédiaire, nous +pouvons utiliser le mode le plus efficace. + +```c++ +imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; +``` + +Idem, il n'existe que deux valeurs pour `initialLayout` : + +* `VK_IMAGE_LAYOUT_UNDEFINED` : inutilisable par le GPU, son contenu sera éliminé à la première transition +* `VK_IMAGE_LAYOUT_PREINITIALIZED` : inutilisable par le GPU, mais la première transition conservera les texels + +Il n'existe que quelques situations où il est nécessaire de préserver les texels pendant la première transition. L'une +d'elle consiste à utiliser l'image comme ressource intermédiaire en combinaison avec `VK_IMAGE_TILING_LINEAR`. Il +faudrait dans ce cas la faire transitionner vers un état source de transfert, sans perte de données. Cependant nous +utilisons un buffer comme ressource intermédiaire, et l'image transitionne d'abord vers cible de transfert. À ce +moment-là elle n'a pas de donnée intéressante. + +```c++ +imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; +``` + +Le champ de bits `usage` fonctionne de la même manière que pour la création des buffers. L'image sera destination +d'un transfert, et sera utilisée par les shaders, d'où les deux indications ci-dessus. + +```c++ +imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; +``` + +L'image ne sera utilisée que par une famille de queues : celle des graphismes (qui rappelons-le supporte +implicitement les transferts). Si vous avez choisi d'utiliser une queue spécifique vous devrez mettre +`VK_SHARING_MODE_CONCURENT`. + +```c++ +imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; +imageInfo.flags = 0; // Optionnel +``` + +Le membre `sample` se réfère au multisampling. Il n'a de sens que pour les images utilisées comme attachements d'un +framebuffer, nous devons donc mettre `1`, traduit par `VK_SAMPLE_COUNT_1_BIT`. Finalement, certaines informations se +réfèrent aux *images étendues*. Ces image étendues sont des images dont seule une partie est stockée dans la mémoire. +Voici une exemple d'utilisation : si vous utilisiez une image 3D pour représenter un terrain à l'aide de voxels, vous +pourriez utiliser cette fonctionnalité pour éviter d'utiliser de la mémoire qui au final ne contiendrait que de l'air. +Nous ne verrons pas cette fonctionnalité dans ce tutoriel, donnez à `flags` la valeur `0`. + +```c++ +if (vkCreateImage(device, &imageInfo, nullptr, &textureImage) != VK_SUCCESS) { + throw std::runtime_error("echec de la creation d'une image!"); +} +``` + +L'image est créée par la fonction `vkCreateImage`, qui ne possède pas d'argument particulièrement intéressant. Il est +possible que le format `VK_FORMAT_R8G8B8A8_SRGB` ne soit pas supporté par la carte graphique, mais c'est tellement peu +probable que nous ne verrons pas comment y remédier. En effet utiliser un autre format demanderait de réaliser plusieurs +conversions compliquées. Nous reviendrons sur ces conversions dans le chapitre sur le buffer de profondeur. + +```c++ +VkMemoryRequirements 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_DEVICE_LOCAL_BIT); + +if (vkAllocateMemory(device, &allocInfo, nullptr, &textureImageMemory) != VK_SUCCESS) { + throw std::runtime_error("echec de l'allocation de la mémoire pour l'image!"); +} + +vkBindImageMemory(device, textureImage, textureImageMemory, 0); +``` + +L'allocation de la mémoire nécessaire à une image fonctionne également de la même façon que pour un buffer. Seuls les +noms de deux fonctions changent : `vkGetBufferMemoryRequirements` devient `vkGetImageMemoryRequirements` et +`vkBindBufferMemory` devient `vkBindImageMemory`. + +Cette fonction est déjà assez grande ainsi, et comme nous aurons besoin d'autres images dans de futurs chapitres, il est +judicieux de déplacer la logique de leur création dans une fonction, comme nous l'avons fait pour les buffers. Voici +donc la fonction `createImage` : + +```c++ +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; + imageInfo.extent.width = width; + imageInfo.extent.height = height; + imageInfo.extent.depth = 1; + imageInfo.mipLevels = 1; + 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("echec de la creation d'une 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("echec de l'allocation de la memoire d'une image!"); + } + + vkBindImageMemory(device, image, imageMemory, 0); +} +``` + +La largeur, la hauteur, le mode de tiling, l'usage et les propriétés de la mémoire sont des paramètres car ils varierons +toujours entre les différentes images que nous créerons dans ce tutoriel. + +La fonction `createTextureImage` peut maintenant être réduite à ceci : + +```c++ +void createTextureImage() { + int texWidth, texHeight, texChannels; + stbi_uc* pixels = stbi_load("textures/texture.jpg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + VkDeviceSize imageSize = texWidth * texHeight * 4; + + if (!pixels) { + throw std::runtime_error("échec du chargement de l'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, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, textureImage, textureImageMemory); +} +``` + +## Transitions de l'organisation + +La fonction que nous allons écrire inclut l'enregistrement et l'exécution de command buffers. Il est donc également +judicieux de placer cette logique dans une autre fonction : + +```c++ +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); +} +``` + +Le code de ces fonctions est basé sur celui de `copyBuffer`. Vous pouvez maintenant réduire `copyBuffer` à : + +```c++ +void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + VkBufferCopy copyRegion{}; + copyRegion.size = size; + vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); + + endSingleTimeCommands(commandBuffer); +} +``` + +Si nous utilisions de simples buffers nous pourrions nous contenter d'écrire une fonction qui enregistre l'appel à +`vkCmdCopyBufferToImage`. Mais comme cette fonction utilse une image comme cible nous devons changer l'organisation de +l'image avant l'appel. Créez une nouvelle fonction pour gérer de manière générique les transitions : + +```c++ +void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + endSingleTimeCommands(commandBuffer); +} +``` + +L'une des manières de réaliser une transition consiste à utiliser une *barrière pour mémoire d'image*. Une telle barrière +de pipeline est en général utilisée pour synchroniser l'accès à une ressource, mais nous avons déjà évoqué ce sujet. Il +existe au passage un équivalent pour les buffers : une barrière pour mémoire de buffer. + +```c++ +VkImageMemoryBarrier barrier{}; +barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; +barrier.oldLayout = oldLayout; +barrier.newLayout = newLayout; +``` + +Les deux premiers champs indiquent la transition à réaliser. Il est possible d'utiliser `VK_IMAGE_LAYOUT_UNDEFINED` pour +`oldLayout` si le contenu de l'image ne vous intéresse pas. + +```c++ +barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; +barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; +``` + +Ces deux paramètres sont utilisés pour transmettre la possession d'une queue à une autre. Il faut leur indiquer les +indices des familles de queues correspondantes. Comme nous ne les utilisons pas, nous devons les mettre à +`VK_QUEUE_FAMILY_IGNORED`. + +```c++ +barrier.image = image; +barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; +barrier.subresourceRange.baseMipLevel = 0; +barrier.subresourceRange.levelCount = 1; +barrier.subresourceRange.baseArrayLayer = 0; +barrier.subresourceRange.layerCount = 1; +``` + +Les paramètres `image` et `subresourceRange` servent à indiquer l'image, puis la partie de l'image concernées par les +changements. Comme notre image n'est pas un tableau, et que nous n'avons pas mis en place de mipmapping, les +paramètres sont tous mis au minimum. + +```c++ +barrier.srcAccessMask = 0; // TODO +barrier.dstAccessMask = 0; // TODO +``` + +Comme les barrières sont avant tout des objets de synchronisation, nous devons indiquer les opérations utilisant la +ressource avant et après l'exécution de cette barrière. Pour pouvoir remplir les champs ci-dessus nous devons +déterminer ces opérations, ce que nous ferons plus tard. + +```c++ +vkCmdPipelineBarrier( + commandBuffer, + 0 /* TODO */, 0 /* TODO */, + 0, + 0, nullptr, + 0, nullptr, + 1, &barrier +); +``` + +Tous les types de barrière sont mis en place à l'aide de la même fonction. Le paramètre qui suit le command buffer +indique une étape de la pipeline. Durant celle-ci seront réalisées les opération devant précéder la barrière. Le +paramètre d'après indique également une étape de la pipeline. Cette fois les opérations exécutées durant cette étape +attendront la barrière. Les étapes que vous pouvez fournir comme avant- et après-barrière dépendent de l'utilisation +des ressources qui y sont utilisées. Les valeurs autorisées sont listées +[dans ce tableau](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap7.html#synchronization-access-types-supported). +Par exemple, si vous voulez lire des données présentes dans un UBO après une barrière qui s'applique au buffer, vous +devrez indiquer `VK_ACCESS_UNIFORM_READ_BIT` comme usage, et si le premier shader à utiliser l'uniform est le fragment +shader il vous faudra indiquer `VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT` comme étape. Dans ce cas de figure, spécifier +une autre étape qu'une étape shader n'aurait aucun sens, et les validation layers vous le feraient remarquer. + +Le paramètre sur la troisième ligne peut être soit `0` soit `VK_DEPENDENCY_BY_REGION_BIT`. Dans ce second cas la +barrière devient une condition spécifique d'une région de la ressource. Cela signifie entre autres que l'implémentation +peut lire une région aussitôt que le transfert y est terminé, sans considération pour les autres régions. Cela permet +d'augmenter encore les performances en permettant d'utiliser les optimisations des architectures actuelles. + +Les trois dernières paires de paramètres sont des tableaux de barrières pour chacun des trois types existants : barrière +mémorielle, barrière de buffer et barrière d'image. + +## Copier un buffer dans une image + +Avant de compléter `vkCreateTextureImage` nous allons écrire une dernière fonction appelée `copyBufferToImage` : + +```c++ +void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) { + VkCommandBuffer commandBuffer = beginSingleTimeCommands(); + + endSingleTimeCommands(commandBuffer); +} +``` + +Comme avec les recopies de buffers, nous devons indiquer les parties du buffer à copier et les parties de l'image où +écrire. Ces données doivent être placées dans une structure de type `VkBufferImageCopy`. + +```c++ +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 +}; +``` + +La plupart de ces champs sont évidents. `bufferOffset` indique l'octet à partir duquel les données des pixels commencent +dans le buffer. L'organisation des pixels doit être indiquée dans les champs `bufferRowLenght` et `bufferImageHeight`. +Il pourrait en effet avoir un espace entre les lignes de l'image. Comme notre image est en un seul bloc, nous devons +mettre ces paramètres à `0`. Enfin, les membres `imageSubResource`, `imageOffset` et `imageExtent` indiquent les parties +de l'image qui receveront les données. + +Les copies buffer vers image sont envoyées à la queue avec la fonction `vkCmdCopyBufferToImage`. + +```c++ +vkCmdCopyBufferToImage( + commandBuffer, + buffer, + image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + ®ion +); +``` + +Le quatrième paramètre indique l'organisation de l'image au moment de la copie. Normalement l'image doit être dans +l'organisation optimale pour la réception de données. Nous avons paramétré la copie pour qu'un seul command buffer +soit à l'origine de la copie successive de tous les pixels. Nous aurions aussi pu créer un tableau de +`VkBufferImageCopy` pour que le command buffer soit à l'origine de plusieurs copies simultanées. + +## Préparer la texture d'image + +Nous avons maintenant tous les outils nécessaires pour compléter la mise en place de la texture d'image. Nous pouvons +retourner à la fonction `createTextureImage`. La dernière chose que nous y avions fait consistait à créer l'image +texture. Notre prochaine étape est donc d'y placer les pixels en les copiant depuis le buffer intermédiaire. Il y a deux +étapes pour cela : + +* Transitionner l'organisation de l'image vers `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL` +* Exécuter le buffer de copie + +C'est simple à réaliser avec les fonctions que nous venons de créer : + +```c++ +transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); +copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); +``` + +Nous avons créé l'image avec une organisation `VK_LAYOUT_UNDEFINED`, car le contenu initial ne nous intéresse pas. + +Pour ensuite pouvoir échantillonner la texture depuis le fragment shader nous devons réaliser une dernière transition, +qui la préparera à être accédée depuis un shader : + +```c++ +transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); +``` + +## Derniers champs de la barrière de transition + +Si vous lanciez le programme vous verrez que les validation layers vous indiquent que les champs d'accès et d'étapes +shader sont invalides. C'est normal, nous ne les avons pas remplis. + +Nous sommes pour le moment interessés par deux transitions : + +* Non défini → cible d'un transfert : écritures par transfert qui n'ont pas besoin d'être synchronisées +* Cible d'un transfert → lecture par un shader : la lecture par le shader doit attendre la fin du transfert + +Ces règles sont indiquées en utilisant les valeurs suivantes pour l'accès et les étapes shader : + +```c++ +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("transition d'orgisation non supportée!"); +} + +vkCmdPipelineBarrier( + commandBuffer, + sourceStage, destinationStage, + 0, + 0, nullptr, + 0, nullptr, + 1, &barrier +); +``` + +Comme vous avez pu le voir dans le tableau mentionné plus haut, l'écriture dans l'image doit se réaliser à l'étape +pipeline de transfert. Mais cette opération d'écriture ne dépend d'aucune autre opération. Nous pouvons donc fournir +une condition d'accès nulle et `VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT` comme opération pré-barrière. Cette valeur correspond +au début de la pipeline, mais ne représente pas vraiment une étape. Elle désigne plutôt le moment où la pipeline se +prépare, et donc sert communément aux transferts. Voyez +[la documentation](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap7.html#VkPipelineStageFlagBits) +pour de plus amples informations sur les pseudo-étapes. + +L'image sera écrite puis lue dans la même passe, c'est pourquoi nous devons indiquer que le fragment shader aura accès à +la mémoire de l'image. + +Quand nous aurons besoin de plus de transitions, nous compléterons la fonction de transition pour qu'elle les prenne en +compte. L'application devrait maintenant tourner sans problème, bien qu'il n'y aie aucune différence visible. + +Un point intéressant est que l'émission du command buffer génère implicitement une synchronisation de type +`VK_ACCESS_HOST_WRITE_BIT`. Comme la fonction `transitionImageLayout` exécute un command buffer ne comprenant qu'une +seule commande, il est possbile d'utiliser cette synchronisation. Cela signifie que vous pourriez alors mettre +`srcAccessMask` à `0` dans le cas d'une transition vers `VK_ACCESS_HOST_WRITE_BIT`. C'est à vous de voir si vous +voulez être explicites à ce sujet. Personnellement je n'aime pas du tout faire dépendre mon application sur des +opérations cachées, que je trouve dangereusement proche d'OpenGL. + +Autre chose intéressante à savoir, il existe une organisation qui supporte toutes les opérations. Elle s'appelle +`VK_IMAGE_LAYOUT_GENERAL`. Le problème est qu'elle est évidemment moins optimisée. Elle est cependant utile dans +certains cas, comme quand une image doit être utilisée comme cible et comme source, ou pour pouvoir lire l'image juste +après qu'elle aie quittée l'organisation préinitialisée. + +Enfin, il important de noter que les fonctions que nous avons mises en place exécutent les commandes de manière +synchronisées et attendent que la queue soit en pause. Pour de véritables applications il est bien sûr recommandé de +combiner toutes ces opérations dans un seul command buffer pour qu'elles soient exécutées de manière asynchrones. Les +commandes de transitions et de copie pourraient grandement bénéficier d'une telle pratique. Essayez par exemple de créer +une fonction `setupCommandBuffer`, puis d'enregistrer les commandes nécessaires depuis les fonctions actuelles. +Appelez ensuite une autre fonction nommée par exemple `flushSetupCommands` qui exécutera le command buffer. Avant +d'implémenter ceci attendez que nous ayons fait fonctionner l'échantillonage. + +## Nettoyage + +Complétez la fonction `createImageTexture` en libérant le buffer intermédiaire et en libérant la mémoire : + +```c++ + transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + vkDestroyBuffer(device, stagingBuffer, nullptr); + vkFreeMemory(device, stagingBufferMemory, nullptr); +} +``` + +L'image texture est utilisée jusqu'à la fin du programme, nous devons donc la libérer dans `cleanup` : + +```c++ +void cleanup() { + cleanupSwapChain(); + + vkDestroyImage(device, textureImage, nullptr); + vkFreeMemory(device, textureImageMemory, nullptr); + + ... +} +``` + +L'image contient maintenant la texture, mais nous n'avons toujours pas mis en place de quoi y accéder depuis la +pipeline. Nous y travaillerons dans le prochain chapitre. + +[C++ code](/code/23_texture_image.cpp) / +[Vertex shader](/code/21_shader_ubo.vert) / +[Fragment shader](/code/21_shader_ubo.frag) diff --git a/fr/06_Texture_mapping/01_Vue_sur_image_et_sampler.md b/fr/06_Texture_mapping/01_Vue_sur_image_et_sampler.md new file mode 100644 index 00000000..cd67767b --- /dev/null +++ b/fr/06_Texture_mapping/01_Vue_sur_image_et_sampler.md @@ -0,0 +1,328 @@ +Dans ce chapitre nous allons créer deux nouvelles ressources dont nous aurons besoin pour pouvoir échantillonner une +image depuis la pipeline graphique. Nous avons déjà vu la première en travaillant avec la swap chain, mais la seconde +est nouvelle, et est liée à la manière dont le shader accédera aux texels de l'image. + +## Vue sur une image texture + +Nous avons vu précédemment que les images ne peuvent être accédées qu'à travers une vue. Nous aurons donc besoin de +créer une vue sur notre nouvelle image texture. + +Ajoutez un membre donnée pour stocker la référence à la vue de type `VkImageView`. Ajoutez ensuite la fonction +`createTextureImageView` qui créera cette vue. + +```c++ +VkImageView textureImageView; + +... + +void initVulkan() { + ... + createTextureImage(); + createTextureImageView(); + createVertexBuffer(); + ... +} + +... + +void createTextureImageView() { + +} +``` + +Le code de cette fonction peut être basé sur `createImageViews`. Les deux seuls changements sont dans `format` et +`image` : + +```c++ +VkImageViewCreateInfo viewInfo{}; +viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; +viewInfo.image = textureImage; +viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; +viewInfo.format = VK_FORMAT_R8G8B8A8_SRGB; +viewInfo.components = VK_COMPONENT_SWIZZLE_IDENTITY; +viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; +viewInfo.subresourceRange.baseMipLevel = 0; +viewInfo.subresourceRange.levelCount = 1; +viewInfo.subresourceRange.baseArrayLayer = 0; +viewInfo.subresourceRange.layerCount = 1; +``` + +Appellons `vkCreateImageView` pour finaliser la création de la vue : + +```c++ +if (vkCreateImageView(device, &viewInfo, nullptr, &textureImageView) != VK_SUCCESS) { + throw std::runtime_error("échec de la création d'une vue sur l'image texture!"); +} +``` + +Comme la logique est similaire à celle de `createImageViews`, nous ferions bien de la déplacer dans une fonction. Créez +donc `createImageView` : + +```c++ +VkImageView createImageView(VkImage image, VkFormat format) { + 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 = VK_IMAGE_ASPECT_COLOR_BIT; + viewInfo.subresourceRange.baseMipLevel = 0; + viewInfo.subresourceRange.levelCount = 1; + viewInfo.subresourceRange.baseArrayLayer = 0; + viewInfo.subresourceRange.layerCount = 1; + + VkImageView imageView; + if (vkCreateImageView(device, &viewInfo, nullptr, &imageView) != VK_SUCCESS) { + throw std::runtime_error("échec de la creation de la vue sur une image!"); + } + + return imageView; +} +``` + +Et ainsi `createTextureImageView` peut être réduite à : + +```c++ +void createTextureImageView() { + textureImageView = createImageView(textureImage, VK_FORMAT_R8G8B8A8_SRGB); +} +``` + +Et de même `createImageView` se résume à : + +```c++ +void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + + for (uint32_t i = 0; i < swapChainImages.size(); i++) { + swapChainImageViews[i] = createImageView(swapChainImages[i], swapChainImageFormat); + } +} +``` + +Préparons dès maintenant la libération de la vue sur l'image à la fin du programme, juste avant la destruction de +l'image elle-même. + +```c++ +void cleanup() { + cleanupSwapChain(); + + vkDestroyImageView(device, textureImageView, nullptr); + + vkDestroyImage(device, textureImage, nullptr); + vkFreeMemory(device, textureImageMemory, nullptr); +``` + +## Samplers + +Il est possible pour les shaders de directement lire les texels de l'image. Ce n'est cependant pas la technique +communément utilisée. Les textures sont généralement accédées à travers un sampler (ou échantillonneur) qui filtrera +et/ou transformera les données afin de calculer la couleur la plus désirable pour le pixel. + +Ces filtres sont utiles pour résoudre des problèmes tels que l'oversampling. Imaginez une texture que l'on veut mettre +sur de la géométrie possédant plus de fragments que la texture n'a de texels. Si le sampler se contentait de prendre +le pixel le plus proche, une pixellisation apparaît : + +![](/images/texture_filtering.png) + +En combinant les 4 texels les plus proches il est possible d'obtenir un rendu lisse comme présenté sur l'image de +droite. Bien sûr il est possible que votre application cherche plutôt à obtenir le premier résultat (Minecraft), mais +la seconde option est en général préférée. Un sampler applique alors automatiquement ce type d'opérations. + +L'undersampling est le problème inverse. Cela crée des artefacts particulièrement visibles dans le cas de textures +répétées vues à un angle aigu : + +![](/images/anisotropic_filtering.png) + +Comme vous pouvez le voir sur l'image de droite, la texture devient d'autant plus floue que l'angle de vision se réduit. +La solution à ce problème peut aussi être réalisée par le sampler et s'appelle +[anisotropic filtering](https://en.wikipedia.org/wiki/Anisotropic_filtering). Elle est par contre plus gourmande en +ressources. + +Au delà de ces filtres le sampler peut aussi s'occuper de transformations. Il évalue ce qui doit se passer quand le +fragment shader essaie d'accéder à une partie de l'image qui dépasse sa propre taille. Il se base sur le *addressing +mode* fourni lors de sa configuration. L'image suivante présente les différentes possiblités : + +![](/images/texture_addressing.png) + +Nous allons maintenant créer la fonction `createTextureSampler` pour mettre en place un sampler simple. Nous +l'utiliserons pour lire les couleurs de la texture. + +```c++ +void initVulkan() { + ... + createTextureImage(); + createTextureImageView(); + createTextureSampler(); + ... +} + +... + +void createTextureSampler() { + +} +``` + +Les samplers se configurent avec une structure de type `VkSamplerCreateInfo`. Elle permet d'indiquer les filtres et les +transformations à appliquer. + +```c++ +VkSamplerCreateInfo samplerInfo{}; +samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; +samplerInfo.magFilter = VK_FILTER_LINEAR; +samplerInfo.minFilter = VK_FILTER_LINEAR; +``` + +Les membres `magFilter` et `minFilter` indiquent comment interpoler les texels respectivement magnifiés et minifiés, ce +qui correspond respectivement aux problèmes évoqués plus haut. Nous avons choisi `VK_FILTER_LINEAR`, qui indiquent +l'utilisation des méthodes pour régler les problèmes vus plus haut. + +```c++ +samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; +samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; +samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; +``` + +Le addressing mode peut être configuré pour chaque axe. Les axes disponibles sont indiqués ci-dessus ; notez +l'utilisation de U, V et W au lieu de X, Y et Z. C'est une convention dans le contexte des textures. Voilà les +différents modes possibles : + +* `VK_SAMPLER_ADDRESS_MODE_REPEAT` : répète le texture +* `VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT` : répète en inversant les coordonnées pour réaliser un effet miroir +* `VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE` : prend la couleur du pixel de bordure le plus proche +* `VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE` : prend la couleur de l'opposé du plus proche côté de l'image +* `VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER` : utilise une couleur fixée + +Le mode que nous utilisons n'est pas très important car nous ne dépasserons pas les coordonnées dans ce tutoriel. +Cependant le mode de répétition est le plus commun car il est infiniment plus efficace que d'envoyer plusieurs fois le +même carré à la pipeline, pour dessiner un pavage au sol par exemple. + +```c++ +samplerInfo.anisotropyEnable = VK_TRUE; +samplerInfo.maxAnisotropy = 16.0f; +``` + +Ces deux membres paramètrent l'utilisation de l'anistropic filtering. Il n'y a pas vraiment de raison de ne pas +l'utiliser, sauf si vous manquez de performances. Le champ `maxAnistropy` est le nombre maximal de texels utilisés pour +calculer la couleur finale. Une plus petite valeur permet d'augmenter les performances, mais résulte évidemment en une +qualité réduite. Il n'existe à ce jour aucune carte graphique pouvant utiliser plus de 16 texels car la qualité ne +change quasiment plus. + +```c++ +samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; +``` + +Le paramètre `borderColor` indique la couleur utilisée pour le sampling qui dépasse les coordonnées, si tel est le mode +choisi. Il est possible d'indiquer du noir, du blanc ou du transparent, mais vous ne pouvez pas indiquer une couleur +quelconque. + +```c++ +samplerInfo.unnormalizedCoordinates = VK_FALSE; +``` + +Le champ `unnomalizedCoordinates` indique le système de coordonnées que vous voulez utiliser pour accéder aux texels de +l'image. Avec `VK_TRUE`, vous pouvez utiliser des coordonnées dans `[0, texWidth)` et `[0, texHeight)`. Sinon, les +valeurs sont accédées avec des coordonnées dans `[0, 1)`. Dans la plupart des cas les coordonnées sont utilisées +normalisées car cela permet d'utiliser un même shader pour des textures de résolution différentes. + +```c++ +samplerInfo.compareEnable = VK_FALSE; +samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS; +``` + +Si une fonction de comparaison est activée, les texels seront comparés à une valeur. Le résultat de la comparaison est +ensuite utilisé pour une opération de filtrage. Cette fonctionnalité est principalement utilisée pour réaliser +[un percentage-closer filtering](https://developer.nvidia.com/gpugems/GPUGems/gpugems_chll.html) sur les shadow maps. +Nous verrons cela dans un futur chapitre. + +```c++ +samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; +samplerInfo.mipLodBias = 0.0f; +samplerInfo.minLod = 0.0f; +samplerInfo.maxLod = 0.0f; +``` + +Tous ces champs sont liés au mipmapping. Nous y reviendrons dans un [prochain chapitre](/Generating_Mipmaps), mais pour +faire simple, c'est encore un autre type de filtre. + +Nous avons maintenant paramétré toutes les fonctionnalités du sampler. Ajoutez un membre donnée pour stocker la +référence à ce sampler, puis créez-le avec `vkCreateSampler` : + +```c++ +VkImageView textureImageView; +VkSampler textureSampler; + +... + +void createTextureSampler() { + ... + + if (vkCreateSampler(device, &samplerInfo, nullptr, &textureSampler) != VK_SUCCESS) { + throw std::runtime_error("échec de la creation d'un sampler!"); + } +} +``` + +Remarquez que le sampler n'est pas lié à une quelconque `VkImage`. Il ne constitue qu'un objet distinct qui représente +une interface avec les images. Il peut être appliqué à n'importe quelle image 1D, 2D ou 3D. Cela diffère d'anciens APIs, +qui combinaient la texture et son filtrage. + +Préparons la destruction du sampler à la fin du programme : + +```c++ +void cleanup() { + cleanupSwapChain(); + + vkDestroySampler(device, textureSampler, nullptr); + vkDestroyImageView(device, textureImageView, nullptr); + + ... +} +``` + +## Capacité du device à supporter l'anistropie + +Si vous lancez le programme, vous verrez que les validation layers vous envoient un message comme celui-ci : + +![](/images/validation_layer_anisotropy.png) + +En effet, l'anistropic filtering est une fonctionnalité du device qui doit être activée. Nous devons donc mettre à jour +la fonction `createLogicalDevice` : + +```c++ +VkPhysicalDeviceFeatures deviceFeatures{}; +deviceFeatures.samplerAnisotropy = VK_TRUE; +``` + +Et bien qu'il soit très peu probable qu'une carte graphique moderne ne supporte pas cette fonctionnalité, nous devrions +aussi adapter `isDeviceSuitable` pour en être sûr. + +```c++ +bool isDeviceSuitable(VkPhysicalDevice device) { + ... + + VkPhysicalDeviceFeatures supportedFeatures; + vkGetPhysicalDeviceFeatures(device, &supportedFeatures); + + return indices.isComplete() && extensionsSupported && swapChainAdequate && supportedFeatures.samplerAnisotropy; +} +``` + +La structure `VkPhysicalDeviceFeatures` permet d'indiquer les capacités supportées quand elle est utilisée avec la +fonction `VkPhysicalDeviceFeatures`, plutôt que de fournir ce dont nous avons besoin. + +Au lieu de simplement obliger le client à posséder une carte graphique supportant l'anistropic filtering, nous pourrions +conditionnellement activer ou pas l'anistropic filtering : + +```c++ +samplerInfo.anisotropyEnable = VK_FALSE; +samplerInfo.maxAnisotropy = 1.0f; +``` + +Dans le prochain chapitre nous exposerons l'image et le sampler au fragment shader pour qu'il puisse utiliser la +texture sur le carré. + +[C++ code](/code/24_sampler.cpp) / +[Vertex shader](/code/21_shader_ubo.vert) / +[Fragment shader](/code/21_shader_ubo.frag) diff --git "a/fr/06_Texture_mapping/02_Sampler_d'image_combin\303\251.md" "b/fr/06_Texture_mapping/02_Sampler_d'image_combin\303\251.md" new file mode 100644 index 00000000..3f7043d7 --- /dev/null +++ "b/fr/06_Texture_mapping/02_Sampler_d'image_combin\303\251.md" @@ -0,0 +1,260 @@ +## Introduction + +Nous avons déjà évoqué les descripteurs dans la partie sur les buffers d'uniformes. Dans ce chapitre nous en verrons un +nouveau type : les *samplers d'image combinés* (*combined image sampler*). Ceux-ci permettent aux shaders d'accéder au +contenu d'images, à travers un sampler. + +Nous allons d'abord modifier l'organisation des descripteurs, la pool de descripteurs et le set de descripteurs pour +qu'ils incluent le sampler d'image combiné. Ensuite nous ajouterons des coordonnées de texture à la structure +`Vertex` et modifierons le vertex shader et le fragment shader pour qu'il utilisent les couleurs de la texture. + +## Modifier les descripteurs + +Trouvez la fonction `createDescriptorSetLayout` et créez une instance de `VkDescriptorSetLayoutBinding`. Cette +structure correspond aux descripteurs d'image combinés. Nous n'avons quasiment que l'indice du binding à y mettre : + +```c++ +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(); +``` + +Assurez-vous également de bien indiquer le fragment shader dans le champ `stageFlags`. Ce sera à cette étape que la +couleur sera extraite de la texture. Il est également possible d'utiliser le sampler pour échantilloner une texture dans +le vertex shader. Cela permet par exemple de déformer dynamiquement une grille de vertices pour réaliser une +[heightmap](https://en.wikipedia.org/wiki/Heightmap) à partir d'une texture de vecteurs. + +Si vous lancez l'application, vous verrez que la pool de descripteurs ne peut pas allouer de set avec l'organisation que +nous avons préparée, car elle ne comprend aucun descripteur de sampler d'image combiné. Il nous faut donc modifier la +fonction `createDescriptorPool` pour qu'elle inclue une structure `VkDesciptorPoolSize` qui corresponde à ce type de +descripteur : + +```c++ +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()); +``` + +La dernière étape consiste à lier l'image et le sampler aux descripteurs du set de descripteurs. Allez à la fonction +`createDescriptorSets`. + +```c++ +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; + + ... +} +``` + +Les ressources nécessaires à la structure paramétrant un descripteur d'image combiné doivent être fournies dans +une structure de type `VkDescriptorImageInfo`. Cela est similaire à la création d'un descripteur pour buffer. Les objets +que nous avons créés dans les chapitres précédents s'assemblent enfin! + +```c++ +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); +``` + +Les descripteurs doivent être mis à jour avec des informations sur l'image, comme pour les buffers. Cette fois nous +allons utiliser le tableau `pImageInfo` plutôt que `pBufferInfo`. Les descripteurs sont maintenant prêts à l'emploi. + +## Coordonnées de texture + +Il manque encore un élément au mapping de textures. Ce sont les coordonnées spécifiques aux sommets. Ce sont elles qui +déterminent les coordonnées de la texture à lier à la géométrie. + +```c++ +struct Vertex { + glm::vec2 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_R32G32_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; + } +}; +``` + +Modifiez la structure `Vertex` pour qu'elle comprenne un `vec2`, qui servira à contenir les coordonnées de texture. +Ajoutez également un `VkVertexInputAttributeDescription` afin que ces coordonnées puissent être accédées en entrée du +vertex shader. Il est nécessaire de les passer du vertex shader vers le fragment shader afin que l'interpolation les +transforment en un gradient. + +```c++ +const std::vector vertices = { + {{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}, {1.0f, 0.0f}}, + {{0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f}}, + {{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}, {0.0f, 1.0f}}, + {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}, {1.0f, 1.0f}} +}; +``` + +Dans ce tutoriel nous nous contenterons de mettre une texture sur le carré en utilisant des coordonnées normalisées. +Nous mettrons le `0, 0` en haut à gauche et le `1, 1` en bas à droite. Essayez de mettre des valeurs sous `0` ou au-delà +de `1` pour voir l'addressing mode en action. Vous pourrez également changer le mode dans la création du sampler pour +voir comment ils se comportent. + +## Shaders + +La dernière étape consiste à modifier les shaders pour qu'ils utilisent la texture et non les couleurs. Commençons par +le vertex shader : + +```glsl +layout(location = 0) in vec2 inPosition; +layout(location = 1) in vec3 inColor; +layout(location = 2) in vec2 inTexCoord; + +layout(location = 0) out vec3 fragColor; +layout(location = 1) out vec2 fragTexCoord; + +void main() { + gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 0.0, 1.0); + fragColor = inColor; + fragTexCoord = inTexCoord; +} +``` + +Comme pour les couleurs spécifiques aux vertices, les valeurs `fragTexCoord` seront interpolées dans le carré par +le rasterizer pour créer un gradient lisse. Le résultat de l'interpolation peut être visualisé en utilisant les +coordonnées comme couleurs : + +```glsl +#version 450 + +layout(location = 0) in vec3 fragColor; +layout(location = 1) in vec2 fragTexCoord; + +layout(location = 0) out vec4 outColor; + +void main() { + outColor = vec4(fragTexCoord, 0.0, 1.0); +} +``` + +Vous devriez avoir un résultat similaire à l'image suivante. N'oubliez pas de recompiler les shader! + +![](/images/texcoord_visualization.png) + +Le vert représente l'horizontale et le rouge la verticale. Les coins noirs et jaunes confirment la normalisation des +valeurs de `0, 0` à `1, 1`. Utiliser les couleurs pour visualiser les valeurs et déboguer est similaire à utiliser +`printf`. C'est peu pratique mais il n'y a pas vraiment d'autre option. + +Un descripteur de sampler d'image combiné est représenté dans les shaders par un objet de type `sampler` placé dans +une variable uniforme. Créez donc une variable `texSampler` : + +```glsl +layout(binding = 1) uniform sampler2D texSampler; +``` + +Il existe des équivalents 1D et 3D pour de telles textures. + +```glsl +void main() { + outColor = texture(texSampler, fragTexCoord); +} +``` + +Les textures sont échantillonées à l'aide de la fonction `texture`. Elle prend en argument un objet `sampler` et des +coordonnées. Le sampler exécute les transformations et le filtrage en arrière-plan. Vous devriez voir la texture sur le +carré maintenant! + +![](/images/texture_on_square.png) + +Expérimentez avec l'addressing mode en fournissant des valeurs dépassant `1`, et vous verrez la répétition de texture à +l'oeuvre : + +```glsl +void main() { + outColor = texture(texSampler, fragTexCoord * 2.0); +} +``` + +![](/images/texture_on_square_repeated.png) + +Vous pouvez aussi combiner les couleurs avec celles écrites à la main : + +```glsl +void main() { + outColor = vec4(fragColor * texture(texSampler, fragTexCoord).rgb, 1.0); +} +``` + +J'ai séparé l'alpha du reste pour ne pas altérer la transparence. + +![](/images/texture_on_square_colorized.png) + +Nous pouvons désormais utiliser des textures dans notre programme! Cette technique est extrêmement puissante et permet +beaucoup plus que juste afficher des couleurs. Vous pouvez même utiliser les images de la swap chain comme textures et y +appliquer des effets post-processing. + +[Code C++](/code/25_texture_mapping.cpp) / +[Vertex shader](/code/25_shader_textures.vert) / +[Fragment shader](/code/25_shader_textures.frag) diff --git a/fr/07_Buffer_de_profondeur.md b/fr/07_Buffer_de_profondeur.md new file mode 100644 index 00000000..38923608 --- /dev/null +++ b/fr/07_Buffer_de_profondeur.md @@ -0,0 +1,550 @@ +## Introduction + +Jusqu'à présent nous avons projeté notre géométrie en 3D, mais elle n'est toujours définie qu'en 2D. Nous allons ajouter +l'axe Z dans ce chapitre pour permettre l'utilisation de modèles 3D. Nous placerons un carré au-dessus ce celui que nous +avons déjà, et nous verrons ce qui se passe si la géométrie n'est pas organisée par profondeur. + +## Géométrie en 3D + +Mettez à jour la structure `Vertex` pour que les coordonnées soient des vecteurs à 3 dimensions. Il faut également +changer le champ `format` dans la structure `VkVertexInputAttributeDescription` correspondant aux coordonnées : + +```c++ +struct Vertex { + glm::vec3 pos; + glm::vec3 color; + glm::vec2 texCoord; + + ... + + 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); + + ... + } +}; +``` + +Mettez également à jour l'entrée du vertex shader qui correspond aux coordonnées. Recompilez le shader. + +```glsl +layout(location = 0) in vec3 inPosition; + +... + +void main() { + gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 1.0); + fragColor = inColor; + fragTexCoord = inTexCoord; +} +``` + +Enfin, il nous faut ajouter la profondeur là où nous créons les instances de `Vertex`. + +```c++ +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}} +}; +``` + +Si vous lancez l'application vous verrez exactement le même résultat. Il est maintenant temps d'ajouter de la géométrie +pour rendre la scène plus intéressante, et pour montrer le problème évoqué plus haut. Dupliquez les vertices afin qu'un +second carré soit rendu au-dessus de celui que nous avons maintenant : + +![](/images/extra_square.svg) + +Nous allons utiliser `-0.5f` comme coordonnée Z. + +```c++ +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}} +}; + +const std::vector indices = { + 0, 1, 2, 2, 3, 0, + 4, 5, 6, 6, 7, 4 +}; +``` + +Si vous lancez le programme maintenant vous verrez que le carré d'en-dessous est rendu au-dessus de l'autre : + +![](/images/depth_issues.png) + +Ce problème est simplement dû au fait que le carré d'en-dessous est placé après dans le tableau des vertices. Il y a +deux manières de régler ce problème : + +* Trier tous les appels en fonction de la profondeur +* Utiliser un buffer de profondeur + +La première approche est communément utilisée pour l'affichage d'objets transparents, car la transparence non ordonnée +est un problème difficile à résoudre. Cependant, pour la géométrie sans transparence, le buffer de profondeur est un +très bonne solution. Il consiste en un attachement supplémentaire au framebuffer, qui stocke les profondeurs. La +profondeur de chaque fragment produit par le rasterizer est comparée à la valeur déjà présente dans le buffer. Si le +fragment est plus distant que celui déjà traité, il est simplement éliminé. Il est possible de manipuler cette valeur de +la même manière que la couleur. + +```c++ +#define GLM_FORCE_RADIANS +#define GLM_FORCE_DEPTH_ZERO_TO_ONE +#include +#include +``` + +La matrice de perspective générée par GLM utilise par défaut la profondeur OpenGL comprise en -1 et 1. Nous pouvons +configurer GLM avec `GLM_FORCE_DEPTH_ZERO_TO_ONE` pour qu'elle utilise des valeurs correspondant à Vulkan. + +## Image de pronfondeur et views sur cette image + +L'attachement de profondeur est une image. La différence est que celle-ci n'est pas créée par la swap chain. Nous +n'avons besoin que d'un seul attachement de profondeur, car les opérations sont séquentielles. L'attachement aura +encore besoin des trois mêmes ressources : une image, de la mémoire et une image view. + +```c++ +VkImage depthImage; +VkDeviceMemory depthImageMemory; +VkImageView depthImageView; +``` + +Créez une nouvelle fonction `createDepthResources` pour mettre en place ces ressources : + +```c++ +void initVulkan() { + ... + createCommandPool(); + createDepthResources(); + createTextureImage(); + ... +} + +... + +void createDepthResources() { + +} +``` + +La création d'une image de profondeur est assez simple. Elle doit avoir la même résolution que l'attachement de couleur, +définie par l'étendue de la swap chain. Elle doit aussi être configurée comme image de profondeur, avoir un tiling +optimal et une mémoire placée sur la carte graphique. Une question persiste : quelle est l'organisation optimale pour +une image de profondeur? Le format contient un composant de profondeur, indiqué par `_Dxx_` dans les valeurs de type +`VK_FORMAT`. + +Au contraire de l'image de texture, nous n'avons pas besoin de déterminer le format requis car nous n'accéderons pas à +cette texture nous-mêmes. Nous n'avons besoin que d'une précision suffisante, en général un minimum de 24 bits. Il y a +plusieurs formats qui satisfont cette nécéssité : + +* `VK_FORMAT_D32_SFLOAT` : float signé de 32 bits pour la profondeur +* `VK_FORMAT_D32_SFLOAT_S8_UINT` : float signé de 32 bits pour la profondeur et int non signé de 8 bits pour le stencil +* `VK_FORMAT_D24_UNORM_S8_UINT` : float signé de 24 bits pour la profondeur et int non signé de 8 bits pour le stencil + +Le composant de stencil est utilisé pour le [test de stencil](https://en.wikipedia.org/wiki/Stencil_buffer). C'est un +test additionnel qui peut être combiné avec le test de profondeur. Nous y reviendrons dans un futur chapitre. + +Nous pourrions nous contenter d'utiliser `VK_FORMAT_D32_SFLOAT` car son support est pratiquement assuré, mais il est +préférable d'utiliser une fonction pour déterminer le meilleur format localement supporté. Créez pour cela la fonction +`findSupportedFormat`. Elle vérifiera que les formats en argument sont supportés et choisira le meilleur en se basant +sur leur ordre dans le vecteurs des formats acceptables fourni en argument : + +```c++ +VkFormat findSupportedFormat(const std::vector& candidates, VkImageTiling tiling, VkFormatFeatureFlags features) { + +} +``` + +Leur support dépend du mode de tiling et de l'usage, nous devons donc les transmettre en argument. Le support des +formats peut ensuite être demandé à l'aide de la fonction `vkGetPhysicalDeviceFormatProperties` : + +```c++ +for (VkFormat format : candidates) { + VkFormatProperties props; + vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &props); +} +``` + +La structure `VkFormatProperties` contient trois champs : + +* `linearTilingFeatures` : utilisations supportées avec le tiling linéaire +* `optimalTilingFeatures` : utilisations supportées avec le tiling optimal +* `bufferFeatures` : utilisations supportées avec les buffers + +Seuls les deux premiers cas nous intéressent ici, et celui que nous vérifierons dépendra du mode de tiling fourni en +paramètre. + +```c++ +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; +} +``` + +Si aucun des candidats ne supporte l'utilisation désirée, nous pouvons lever une exception. + +```c++ +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("aucun des formats demandés n'est supporté!"); +} +``` + +Nous allons utiliser cette fonction depuis une autre fonction `findDepthFormat`. Elle sélectionnera un format +avec un composant de profondeur qui supporte d'être un attachement de profondeur : + +```c++ +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 + ); +} +``` + +Utilisez bien `VK_FORMAT_FEATURE_` au lieu de `VK_IMAGE_USAGE_`. Tous les candidats contiennent la profondeur, mais +certains ont le stencil en plus. Ainsi il est important de voir que dans ce cas, la profondeur n'est qu'une *capacité* +et non un *usage* exclusif. Autre point, nous devons prendre cela en compte pour les transitions d'organisation. Ajoutez +une fonction pour determiner si le format contient un composant de stencil ou non : + +```c++ +bool hasStencilComponent(VkFormat format) { + return format == VK_FORMAT_D32_SFLOAT_S8_UINT || format == VK_FORMAT_D24_UNORM_S8_UINT; +} +``` + +Appelez cette fonction depuis `createDepthResources` pour déterminer le format de profondeur : + +```c++ +VkFormat depthFormat = findDepthFormat(); +``` + +Nous avons maintenant toutes les informations nécessaires pour invoquer `createImage` et `createImageView`. + +```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); +depthImageView = createImageView(depthImage, depthFormat); +``` + +Cependant cette fonction part du principe que la `subresource` est toujours `VK_IMAGE_ASPECT_COLOR_BIT`, il nous faut +donc en faire un paramètre. + +```c++ +VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags) { + ... + viewInfo.subresourceRange.aspectMask = aspectFlags; + ... +} +``` + +Changez également les appels à cette fonction pour prendre en compte ce changement : + +```c++ +swapChainImageViews[i] = createImageView(swapChainImages[i], swapChainImageFormat, VK_IMAGE_ASPECT_COLOR_BIT); +... +depthImageView = createImageView(depthImage, depthFormat, VK_IMAGE_ASPECT_DEPTH_BIT); +... +textureImageView = createImageView(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_ASPECT_COLOR_BIT); +``` + +Voilà tout pour la création de l'image de profondeur. Nous n'avons pas besoin d'y envoyer de données ou quoi que ce soit +de ce genre, car nous allons l'initialiser au début de la render pass tout comme l'attachement de couleur. + +### Explicitement transitionner l'image de profondeur + +Nous n'avons pas besoin de faire explicitement la transition du layout de l'image vers un attachement de profondeur parce +qu'on s'en occupe directement dans la render pass. En revanche, pour l'exhaustivité je vais quand même vous décrire le processus +dans cette section. Vous pouvez sauter cette étape si vous le souhaitez. + +Faites un appel à `transitionImageLayout` à la fin de `createDepthResources` comme ceci: + +```c++ +transitionImageLayout(depthImage, depthFormat, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); +``` + +L'organisation indéfinie peut être utilisée comme organisation intiale, dans la mesure où aucun contenu d'origine n'a +d'importance. Nous devons faire évaluer la logique de `transitionImageLayout` pour qu'elle puisse utiliser la +bonne subresource. + +```c++ +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; +} +``` + +Même si nous n'utilisons pas le composant de stencil, nous devons nous en occuper dans les transitions de l'image de +profondeur. + +Ajoutez enfin le bon accès et les bonnes étapes pipeline : + +```c++ +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("transition d'organisation non supportée!"); +} +``` + +Le buffer de profondeur sera lu avant d'écrire un fragment, et écrit après qu'un fragment valide soit traité. La lecture +se passe en `VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT` et l'écriture en `VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT`. +Vous devriez choisir la première des étapes correspondant à l'opération correspondante, afin que tout soit prêt pour +l'utilisation de l'attachement de profondeur. + +## Render pass + +Nous allons modifier `createRenderPass` pour inclure l'attachement de profondeur. Spécifiez d'abord un +`VkAttachementDescription` : + +```c++ +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; +``` + +Le `format` doit être celui de l'image de profondeur. Pour cette fois nous ne garderons pas les données de profondeur, +car nous n'en avons plus besoin après le rendu. Encore une fois le hardware pourra réaliser des optimisations. Et +de même nous n'avons pas besoin des valeurs du rendu précédent pour le début du rendu de la frame, nous pouvons donc +mettre `VK_IMAGE_LAYOUT_UNDEFINED` comme valeur pour `initialLayout`. + +```c++ +VkAttachmentReference depthAttachmentRef{}; +depthAttachmentRef.attachment = 1; +depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; +``` + +Ajoutez une référence à l'attachement dans notre seule et unique subpasse : + +```c++ +VkSubpassDescription subpass{}; +subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; +subpass.colorAttachmentCount = 1; +subpass.pColorAttachments = &colorAttachmentRef; +subpass.pDepthStencilAttachment = &depthAttachmentRef; +``` + +Les subpasses ne peuvent utiliser qu'un seul attachement de profondeur (et de stencil). Réaliser le test de profondeur +sur plusieurs buffers n'a de toute façon pas beaucoup de sens. + +```c++ +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; +``` + +Changez enfin la structure `VkRenderPassCreateInfo` pour qu'elle se réfère aux deux attachements. + +## Framebuffer + +L'étape suivante va consister à modifier la création du framebuffer pour lier notre image de profondeur à l'attachement +de profondeur. Trouvez `createFramebuffers` et indiquez la view sur l'image de profondeur comme second attachement : + +```c++ +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; +``` + +L'attachement de couleur doit différer pour chaque image de la swap chain, mais l'attachement de profondeur peut être le +même pour toutes, car il n'est utilisé que par la subpasse, et la synchronisation que nous avons mise en place ne permet +pas l'exécution de plusieurs subpasses en même temps. + +Nous devons également déplacer l'appel à `createFramebuffers` pour que la fonction ne soit appelée qu'après la création +de l'image de profondeur : + +```c++ +void initVulkan() { + ... + createDepthResources(); + createFramebuffers(); + ... +} +``` + +## Supprimer les valeurs + +Comme nous avons plusieurs attachements avec `VK_ATTACHMENT_LOAD_OP_CLEAR`, nous devons spécifier plusieurs valeurs de +suppression. Allez à `createCommandBuffers` et créez un tableau de `VkClearValue` : + +```c++ +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(); +``` + +Avec Vulkan, `0.0` correspond au plan near et `1.0` au plan far. La valeur initiale doit donc être `1.0`, afin que tout +fragment puisse s'y afficher. Notez que l'ordre des `clearValues` correspond à l'ordre des attachements auquelles les +couleurs correspondent. + +## État de profondeur et de stencil + +L'attachement de profondeur est prêt à être utilisé, mais le test de profondeur n'a pas encore été activé. Il est +configuré à l'aide d'une structure de type `VkPipelineDepthStencilStateCreateInfo`. + +```c++ +VkPipelineDepthStencilStateCreateInfo depthStencil{}; +depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; +depthStencil.depthTestEnable = VK_TRUE; +depthStencil.depthWriteEnable = VK_TRUE; +``` + +Le champ `depthTestEnable` permet d'activer la comparaison de la profondeur des fragments. Le champ `depthWriteEnable` +indique si la nouvelle profondeur des fragments qui passent le test doivent être écrite dans le tampon de profondeur. + +```c++ +depthStencil.depthCompareOp = VK_COMPARE_OP_LESS; +``` + +Le champ `depthCompareOp` permet de fournir le test de comparaison utilisé pour conserver ou éliminer les fragments. +Nous gardons le `<` car il correspond le mieux à la convention employée par Vulkan. + +```c++ +depthStencil.depthBoundsTestEnable = VK_FALSE; +depthStencil.minDepthBounds = 0.0f; // Optionnel +depthStencil.maxDepthBounds = 1.0f; // Optionnel +``` + +Les champs `depthBoundsTestEnable`, `minDepthBounds` et `maxDepthBounds` sont utilisés pour des tests optionnels +d'encadrement de profondeur. Ils permettent de ne garder que des fragments dont la profondeur est comprise entre deux +valeurs fournies ici. Nous n'utiliserons pas cette fonctionnalité. + +```c++ +depthStencil.stencilTestEnable = VK_FALSE; +depthStencil.front = {}; // Optionnel +depthStencil.back = {}; // Optionnel +``` + +Les trois derniers champs configurent les opérations du buffer de stencil, que nous n'utiliserons pas non plus dans ce +tutoriel. Si vous voulez l'utiliser, vous devrez vous assurer que le format sélectionné pour la profondeur contient +aussi un composant pour le stencil. + +```c++ +pipelineInfo.pDepthStencilState = &depthStencil; +``` + +Mettez à jour la création d'une instance de `VkGraphicsPipelineCreateInfo` pour référencer l'état de profondeur et de +stencil que nous venons de créer. Un tel état doit être spécifié si la passe contient au moins l'une de ces +fonctionnalités. + +Si vous lancez le programme, vous verrez que la géométrie est maintenant correctement rendue : + +![](/images/depth_correct.png) + +## Gestion des redimensionnements de la fenêtre + +La résolution du buffer de profondeur doit changer avec la fenêtre quand elle redimensionnée, pour pouvoir correspondre +à la taille de l'attachement. Étendez `recreateSwapChain` pour régénérer les ressources : + +```c++ +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(); + createUniformBuffers(); + createDescriptorPool(); + createDescriptorSets(); + createCommandBuffers(); +} +``` + +La libération des ressources doit avoir lieu dans la fonction de libération de la swap chain. + +```c++ +void cleanupSwapChain() { + vkDestroyImageView(device, depthImageView, nullptr); + vkDestroyImage(device, depthImage, nullptr); + vkFreeMemory(device, depthImageMemory, nullptr); + + ... +} +``` + +Votre application est maintenant capable de rendre correctement de la géométrie 3D! Nous allons utiliser cette +fonctionnalité pour afficher un modèle dans le prohain chapitre. + +[Code C++](/code/26_depth_buffering.cpp) / +[Vertex shader](/code/26_shader_depth.vert) / +[Fragment shader](/code/26_shader_depth.frag) diff --git "a/fr/08_Charger_des_mod\303\250les.md" "b/fr/08_Charger_des_mod\303\250les.md" new file mode 100644 index 00000000..b0e9f5f6 --- /dev/null +++ "b/fr/08_Charger_des_mod\303\250les.md" @@ -0,0 +1,287 @@ +## Introduction + +Votre programme peut maintenant réaliser des rendus 3D, mais la géométrie que nous utilisons n'est pas très +intéressante. Nous allons maintenant étendre notre programme pour charger les sommets depuis des fichiers. Votre carte +graphique aura enfin un peu de travail sérieux à faire. + +Beaucoup de tutoriels sur les APIs graphiques font implémenter par le lecteur un système pour charger les modèle OBJ. Le +problème est que ce type de fichier est limité. Nous *allons* charger des modèles en OBJ, mais nous nous concentrerons +plus sur l'intégration des sommets dans le programme, plutôt que sur les aspects spécifiques de ce format de fichier. + +## Une librairie + +Nous utiliserons la librairie [tinyobjloader](https://github.com/syoyo/tinyobjloader) pour charger les vertices et les +faces depuis un fichier OBJ. Elle est facile à utiliser et à intégrer, car elle est contenue dans un seul fichier. +Téléchargez-la depuis le lien GitHub, elle est contenue dans le fichier `tiny_obj_loader.h`. Téléchargez bien le fichier +de la branche `master` car la version "release" n'est plus assez à jour. + +**Visual Studio** + +Ajoutez dans `Additional Include Directories` le dossier dans lequel est contenu `tiny_obj_loader.h`. + +![](/images/include_dirs_tinyobjloader.png) + +**Makefile** + +Ajoutez le dossier contenant `tiny_obj_loader.h` aux dossiers d'inclusions de GCC : + +```text +VULKAN_SDK_PATH = /home/user/VulkanSDK/x.x.x.x/x86_64 +STB_INCLUDE_PATH = /home/user/libraries/stb +TINYOBJ_INCLUDE_PATH = /home/user/libraries/tinyobjloader + +... + +CFLAGS = -std=c++17 -I$(VULKAN_SDK_PATH)/include -I$(STB_INCLUDE_PATH) -I$(TINYOBJ_INCLUDE_PATH) +``` + +## Exemple de modèle + +Nous n'allons pas utiliser de lumières pour l'instant. Il est donc préférable de charger un modèle qui comprend les +ombres pour que nous ayons un rendu plus intéressant. Vous pouvez trouver de tels modèles sur +[Sketchfab](https://sketchfab.com/). + +Pour ce tutoriel j'ai choisi d'utiliser le [Viking room](https://sketchfab.com/3d-models/viking-room-a49f1b8e4f5c4ecf9e1fe7d81915ad38) créé par [nigelgoh](https://sketchfab.com/nigelgoh) ([CC BY 4.0](https://web.archive.org/web/20200428202538/https://sketchfab.com/3d-models/viking-room-a49f1b8e4f5c4ecf9e1fe7d81915ad38)). +J'en ai changé la taille et l'orientation pour l'utiliser comme remplacement de notre géométrie actuelle : + +* [viking_room.obj](/resources/viking_room.obj) +* [viking_room.png](/resources/viking_room.png) + +Il possède un demi-million de triangles, ce qui fera un bon test pour notre application. Vous pouvez utiliser un +autre modèle si vous le désirez, mais assurez-vous qu'il ne comprend qu'un seul matériau et que ses dimensions sont +d'approximativement 1.5 x 1.5 x 1.5. Si il est plus grand vous devrez changer la matrice view. Mettez le modèle dans un +dossier appelé `models`, et placez l'image dans le dossier `textures`. + +Ajoutez deux variables de configuration pour la localisation du modèle et de la texture : + +```c++ +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; + +const std::string MODEL_PATH = "models/viking_room.obj"; +const std::string TEXTURE_PATH = "textures/viking_room.png"; +``` + +Changez la fonction `createTextureImage` pour qu'elle utilise cette seconde constante pour charger la texture. + +```c++ +stbi_uc* pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); +``` + +## Charger les vertices et les indices + +Nous allons maintenant charger les vertices et les indices depuis le fichier OBJ. Supprimez donc les tableaux +`vertices` et `indices`, et remplacez-les par des vecteurs dynamiques : + +```c++ +std::vector vertices; +std::vector indices; +VkBuffer vertexBuffer; +VkDeviceMemory vertexBufferMemory; +``` + +Il faut aussi que le type des indices soit maintenant un `uint32_t` car nous allons avoir plus que 65535 sommets. +Changez également le paramètre de type dans l'appel à `vkCmdBindIndexBuffer`. + +```c++ +vkCmdBindIndexBuffer(commandBuffers[i], indexBuffer, 0, VK_INDEX_TYPE_UINT32); +``` + +La librairie que nous utilisons s'inclue de la même manière que les librairies STB. Il faut définir la macro +`TINYOBJLOADER_IMLEMENTATION` pour que le fichier comprenne les définitions des fonctions. + +```c++ +#define TINYOBJLOADER_IMPLEMENTATION +#include +``` + +Nous allons ensuite écrire la fonction `loadModel` pour remplir le tableau de vertices et d'indices depuis le fichier +OBJ. Nous devons l'appeler avant que les buffers de vertices et d'indices soient créés. + +```c++ +void initVulkan() { + ... + loadModel(); + createVertexBuffer(); + createIndexBuffer(); + ... +} + +... + +void loadModel() { + +} +``` + +Un modèle se charge dans la librairie avec la fonction `tinyobj::LoadObj` : + +```c++ +void loadModel() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn, err; + + if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, MODEL_PATH.c_str())) { + throw std::runtime_error(warn + err); + } +} +``` + +Dans un fichier OBJ on trouve des positions, des normales, des coordonnées de textures et des faces. Ces dernières +sont une collection de vertices, avec chaque vertex lié à une position, une normale et/ou un coordonnée de texture à +l'aide d'un indice. Il est ainsi possible de réutiliser les attributs de manière indépendante. + +Le conteneur `attrib` contient les positions, les normales et les coordonnées de texture dans les vecteurs +`attrib.vertices`, `attrib.normals` et `attrib.texcoords`. Le conteneur `shapes` contient tous les objets et leurs +faces. Ces dernières se réfèrent donc aux données stockées dans `attrib`. Les modèles peuvent aussi définir un matériau +et une texture par face, mais nous ignorerons ces attributs pour le moment. + +La chaîne de caractères `err` contient les erreurs et les messages générés pendant le chargement du fichier. Le +chargement des fichiers ne rate réellement que quand `LoadObj` retourne `false`. Les faces peuvent être constitués d'un +nombre quelconque de vertices, alors que notre application ne peut dessiner que des triangles. Heureusement, la fonction +possède la capacité - activée par défaut - de triangulariser les faces. + +Nous allons combiner toutes les faces du fichier en un seul modèle. Commençons par itérer sur ces faces. + +```c++ +for (const auto& shape : shapes) { + +} +``` + +Grâce à la triangularisation nous sommes sûrs que les faces n'ont que trois vertices. Nous pouvons donc simplement les +copier vers le vecteur des vertices finales : + +```c++ +for (const auto& shape : shapes) { + for (const auto& index : shape.mesh.indices) { + Vertex vertex{}; + + vertices.push_back(vertex); + indices.push_back(indices.size()); + } +} +``` + +Pour faire simple nous allons partir du principe que les sommets sont uniques. La variable `index` est du type +`tinyobj::index_t`, et contient `vertex_index`, `normal_index` et `texcoord_index`. Nous devons traiter ces données +pour les relier aux données contenues dans les tableaux `attrib` : + +```c++ +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], + attrib.texcoords[2 * index.texcoord_index + 1] +}; + +vertex.color = {1.0f, 1.0f, 1.0f}; +``` + +Le tableau `attrib.vertices` est constitués de floats et non de vecteurs à trois composants comme `glm::vec3`. Il faut +donc multiplier les indices par 3. De même on trouve deux coordonnées de texture par entrée. Les décalages `0`, `1` et +`2` permettent ensuite d'accéder aux composant X, Y et Z, ou aux U et V dans le cas des textures. + +Lancez le programme avec les optimisation activées (`Release` avec Visual Studio ou avec l'argument `-03` pour GCC). +Vous pourriez le faire sans mais le chargement du modèle sera très long. Vous devriez voir ceci : + +![](/images/inverted_texture_coordinates.png) + +La géométrie est correcte! Par contre les textures sont quelque peu... étranges. En effet le format OBJ part d'en bas à +gauche pour les coordonnées de texture, alors que Vulkan part d'en haut à gauche. Il suffit de changer cela pendant le +chargement du modèle : + +```c++ +vertex.texCoord = { + attrib.texcoords[2 * index.texcoord_index + 0], + 1.0f - attrib.texcoords[2 * index.texcoord_index + 1] +}; +``` + +Vous pouvez lancer à nouveau le programme. Le rendu devrait être correct : + +![](/images/drawing_model.png) + +## Déduplication des vertices + +Pour le moment nous n'utilisons pas l'index buffer, et le vecteur `vertices` contient beaucoup de vertices dupliquées. +Nous ne devrions les inclure qu'une seule fois dans ce conteneur et utiliser leurs indices pour s'y référer. Une +manière simple de procéder consiste à utiliser une `unoredered_map` pour suivre les vertices multiples et leurs indices. + +```c++ +#include + +... + +std::unordered_map uniqueVertices{}; + +for (const auto& shape : shapes) { + for (const auto& index : shape.mesh.indices) { + Vertex vertex{}; + + ... + + if (uniqueVertices.count(vertex) == 0) { + uniqueVertices[vertex] = static_cast(vertices.size()); + vertices.push_back(vertex); + } + + indices.push_back(uniqueVertices[vertex]); + } +} +``` + +Chaque fois que l'on extrait un vertex du fichier, nous devons vérifier si nous avons déjà manipulé un vertex possédant +les mêmes attributs. Si il est nouveau, nous le stockerons dans `vertices` et placerons son indice dans +`uniqueVertices` et dans `indices`. Si nous avons déjà un tel vertex nous regarderons son indice depuis `uniqueVertices` +et copierons cette valeur dans `indices`. + +Pour l'instant le programme ne peut pas compiler, car nous devons implémenter une fonction de hachage et l'opérateur +d'égalité pour utiliser la structure `Vertex` comme clé dans une table de hachage. L'opérateur est simple à surcharger : + +```c++ +bool operator==(const Vertex& other) const { + return pos == other.pos && color == other.color && texCoord == other.texCoord; +} +``` + +Nous devons définir une spécialisation du patron de classe `std::hash` pour la fonction de hachage. Le hachage est +un sujet compliqué, mais [cppreference.com recommande](http://en.cppreference.com/w/cpp/utility/hash) l'approche +suivante pour combiner correctement les champs d'une structure : + +```c++ +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); + } + }; +} +``` + +Ce code doit être placé hors de la définition de `Vertex`. Les fonctions de hashage des type GLM sont activés avec +la définition et l'inclusion suivantes : + +```c++ +#define GLM_ENABLE_EXPERIMENTAL +#include +``` + +Le dossier `glm/gtx/` contient les extensions expérimentales de GLM. L'API peut changer dans le futur, mais la +librairie a toujours été très stable. + +Vous devriez pouvoir compiler et lancer le programme maintenant. Si vous regardez la taille de `vertices` vous verrez +qu'elle est passée d'un million et demi vertices à seulement 265645! Les vertices sont utilisés pour six triangles en +moyenne, ce qui représente une optimisation conséquente. + +[Code C++](/code/27_model_loading.cpp) / +[Vertex shader](/code/26_shader_depth.vert) / +[Fragment shader](/code/26_shader_depth.frag) diff --git "a/fr/09_G\303\251n\303\251rer_des_mipmaps.md" "b/fr/09_G\303\251n\303\251rer_des_mipmaps.md" new file mode 100644 index 00000000..33ffaa25 --- /dev/null +++ "b/fr/09_G\303\251n\303\251rer_des_mipmaps.md" @@ -0,0 +1,411 @@ +## Introduction + +Notre programme peut maintenant charger et afficher des modèles 3D. Dans ce chapitre nous allons ajouter une nouvelle +fonctionnalité : celle de générer et d'utiliser des mipmaps. Elles sont utilisées dans tous les applications 3D. Vulkan +laisse au programmeur un control quasiment total sur leur génération. + +Les mipmaps sont des versions de qualité réduite précalculées d'une texture. Chacune de ces versions est deux fois +moins haute et large que l'originale. Les objets plus distants de la caméra peuvent utiliser ces versions pour le +sampling de la texture. Le rendu est alors plus rapide et plus lisse. Voici un exemple de mipmaps : + +![](/images/mipmaps_example.jpg) + +## Création des images + +Avec Vulkan, chaque niveau de mipmap est stocké dans les différents *niveaux de mipmap* de l'image originale. Le niveau +0 correspond à l'image originale. Les images suivantes sont souvent appelées *mip chain*. + +Le nombre de niveaux de mipmap doit être fourni lors de la création de l'image. Jusqu'à présent nous avons indiqué la +valeur `1`. Nous devons ainsi calculer le nombre de mipmaps à générer à partir de la taille de l'image. Créez un membre +donnée pour contenir cette valeur : + +```c++ +... +uint32_t mipLevels; +VkImage textureImage; +... +``` + +La valeur pour `mipLevels` peut être déterminée une fois que nous avons chargé la texture dans `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; + +``` + +La troisième ligne ci-dessus calcule le nombre de niveaux de mipmaps. La fonction `max` chosit la plus grande des +dimensions, bien que dans la pratique les textures seront toujours carrées. Ensuite, `log2` donne le nombre de fois que +les dimensions peuvent être divisées par deux. La fonction `floor` gère le cas où la dimension n'est pas un multiple +de deux (ce qui est déconseillé). `1` est finalement rajouté pour que l'image originale soit aussi comptée. + +Pour utiliser cette valeur nous devons changer les fonctions `createImage`, `createImageView` et +`transitionImageLayout`. Nous devrons y indiquer le nombre de mipmaps. Ajoutez donc cette donnée en paramètre à toutes +ces fonctions : + +```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; + ... +``` + +Il nous faut aussi mettre à jour les appels. + +```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_SRGB, 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_SRGB, 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_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, mipLevels); +``` + +## Génération des mipmaps + +Notre texture a plusieurs niveaux de mipmaps, mais le buffer intermédiaire ne peut pas gérer cela. Les niveaux +autres que 0 sont indéfinis. Pour les remplir nous devons générer les mipmaps à partir du seul niveau que nous avons. +Nous allons faire cela du côté de la carte graphique. Nous allons pour cela utiliser la commande `vkCmdBlitImage`. +Elle effectue une copie, une mise à l'échelle et un filtrage. Nous allons l'appeler une fois par niveau. + +Cette commande est considérée comme une opération de transfert. Nous devons donc indiquer que la mémoire de l'image sera +utilisée à la fois comme source et comme destination de la commande. Ajoutez `VK_IMAGE_USAGE_TRANSFER_SRC_BIT` à la +création de l'image. + +```c++ +... +createImage(texWidth, texHeight, mipLevels, VK_FORMAT_R8G8B8A8_SRGB, 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); +... +``` + +Comme pour les autres opérations sur les images, la commande `vkCmdBlitImage` dépend de l'organisation de l'image sur +laquelle elle opère. Nous pourrions transitionner l'image vers `VK_IMAGE_LAYOUT_GENERAL`, mais les opérations +prendraient beaucoup de temps. En fait il est possible de transitionner les niveaux de mipmaps indépendemment les uns +des autres. Nous pouvons donc mettre l'image initiale à `VK_IMAGE_LAYOUT_TRANSFER_SCR_OPTIMAL` et la chaîne de mipmaps +à `VK_IMAGE_LAYOUT_DST_OPTIMAL`. Nous pourrons réaliser les transitions à la fin de chaque opération. + +La fonction `transitionImageLayout` ne peut réaliser une transition d'organisation que sur l'image entière. Nous allons +donc devoir écrire quelque commandes liées aux barrières de pipeline. Supprimez la transition vers +`VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL` dans `createTextureImage` : + +```c++ +... +transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, mipLevels); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); +//transitionné vers VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL lors de la generation des mipmaps +... +``` + +Tous les niveaux de l'image seront ainsi en `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL`. Chaque niveau sera ensuite +transitionné vers `VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL` après l'exécution de la commande. + +Nous allons maintenant écrire la fonction qui génèrera les 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); +} +``` + +Nous allons réaliser plusieurs transitions, et pour cela nous réutiliserons cette structure `VkImageMemoryBarrier`. Les +champs remplis ci-dessus seront valides pour tous les niveaux, et nous allons changer les champs manquant au fur et à +mesure de la génération des mipmaps. + +```c++ +int32_t mipWidth = texWidth; +int32_t mipHeight = texHeight; + +for (uint32_t i = 1; i < mipLevels; i++) { + +} +``` + +Cette boucle va enregistrer toutes les commandes `VkCmdBlitImage`. Remarquez que la boucle commence à 1, et pas à 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); +``` + +Tout d'abord nous transitionnons le `i-1`ième niveau vers `VK_IMAGE_LAYOUT_TRANSFER_SCR_OPTIMAL`. Cette transition +attendra que le niveau de mipmap soit prêt, que ce soit par copie depuis le buffer pour l'image originale, ou bien par +`vkCmdBlitImage`. La commande de génération de la mipmap suivante attendra donc la fin de la précédente. + +```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; +``` + +Nous devons maintenant indiquer les régions concernées par la commande. Le niveau de mipmap source est `i-1` et le +niveau destination est `i`. Les deux éléments du tableau `scrOffsets` déterminent en 3D la région source, et +`dstOffsets` la région cible. Les coordonnées X et Y sont à chaque fois divisées par deux pour réduire la taille des +mipmaps. La coordonnée Z doit être mise à la profondeur de l'image, c'est à dire 1. + +```c++ +vkCmdBlitImage(commandBuffer, + image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, &blit, + VK_FILTER_LINEAR); +``` + +Nous enregistrons maintenant les commandes. Remarquez que `textureImage` est utilisé à la fois comme source et comme +cible, car la commande s'applique à plusieurs niveaux de l'image. Le niveau de mipmap source vient d'être transitionné +vers `VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL`, et le niveau cible est resté en destination depuis sa création. + +Attention au cas où vous utilisez une queue de transfert dédiée (comme suggéré dans [Vertex buffers](!fr/Vertex_buffers/Buffer_intermédiaire)) : la fonction `vkCmdBlitImage` doit être envoyée dans une queue graphique. + +Le dernier paramètre permet de fournir un `VkFilter`. Nous voulons le même filtre que pour le sampler, nous pouvons donc +mettre `VK_FILTER_LINEAR`. + +```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); +``` + +Ensuite, la boucle transtionne le `i-1`ième niveau de mipmap vers l'organisation optimale pour la lecture par shader. +La transition attendra la fin de la commande, de même que les opérations de sampling. + +```c++ + ... + if (mipWidth > 1) mipWidth /= 2; + if (mipHeight > 1) mipHeight /= 2; +} +``` + +Les tailles de la mipmap sont ensuite divisées par deux. Nous vérifions quand même que ces dimensions sont bien +supérieures à 1, ce qui peut arriver dans le cas d'une image qui n'est pas carrée. + +```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); +} +``` + +Avant de terminer avec le command buffer, nous devons ajouter une dernière barrière. Elle transitionne le dernier +niveau de mipmap vers `VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL`. Ce cas n'avait pas été géré par la boucle, car elle +n'a jamais servie de source à une copie. + +Appelez finalement cette fonction depuis `createTextureImage` : + +```c++ +transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, mipLevels); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); +//transions vers VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL pendant la génération des mipmaps +... +generateMipmaps(textureImage, texWidth, texHeight, mipLevels); +``` + +Les mipmaps de notre image sont maintenant complètement remplies. + +## Support pour le filtrage linéaire + +La fonction `vkCmdBlitImage` est extrêmement pratique. Malheureusement il n'est pas garanti qu'elle soit disponible. Elle +nécessite que le format de l'image texture supporte ce type de filtrage, ce que nous pouvons vérifier avec la fonction +`vkGetPhysicalDeviceFormatProperties`. Nous allons vérifier sa disponibilité dans `generateMipmaps`. + +Ajoutez d'abord un paramètre qui indique le format de l'image : + +```c++ +void createTextureImage() { + ... + + generateMipmaps(textureImage, VK_FORMAT_R8G8B8A8_SRGB, texWidth, texHeight, mipLevels); +} + +void generateMipmaps(VkImage image, VkFormat imageFormat, int32_t texWidth, int32_t texHeight, uint32_t mipLevels) { + + ... +} +``` + +Utilisez `vkGetPhysicalDeviceFormatProperties` dans `generateMipmaps` pour récupérer les propriétés liés au format : + +```c++ +void generateMipmaps(VkImage image, VkFormat imageFormat, int32_t texWidth, int32_t texHeight, uint32_t mipLevels) { + + // Vérifions si l'image supporte le filtrage linéaire + VkFormatProperties formatProperties; + vkGetPhysicalDeviceFormatProperties(physicalDevice, imageFormat, &formatProperties); + + ... +``` + +La structure `VkFormatProperties` possède les trois champs `linearTilingFeatures`, `optimalTilingFeature` et +`bufferFeaetures`. Ils décrivent chacun l'utilisation possible d'images de ce format dans certains contextes. Nous avons +créé l'image avec le format optimal, les informations qui nous concernent sont donc dans `optimalTilingFeatures`. Le +support pour le filtrage linéaire est ensuite indiqué par `VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT`. + +```c++ +if (!(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT)) { + throw std::runtime_error("le format de l'image texture ne supporte pas le filtrage lineaire!"); +} +``` + +Il y a deux alternatives si le format ne permet pas l'utilisation de `vkCmdBlitImage`. Vous pouvez créer une fonction +pour essayer de trouver un format supportant la commande, ou vous pouvez utiliser une librairie pour générer les +mipmaps comme [stb_image_resize](https://github.com/nothings/stb/blob/master/stb_image_resize.h). Chaque niveau de +mipmap peut ensuite être chargé de la même manière que vous avez chargé l'image. + +Souvenez-vous qu'il est rare de générer les mipmaps pendant l'exécution. Elles sont généralement prégénérées et stockées +dans le fichier avec l'image de base. Le chargement de mipmaps prégénérées est laissé comme exercice au lecteur. + +## Sampler + +Un objet `VkImage` contient les données de l'image et un objet `VkSampler` contrôle la lecture des données pendant le +rendu. Vulkan nous permet de spécifier les valeurs `minLod`, `maxLod`, `mipLodBias` et `mipmapMode`, où "Lod" signifie +*level of detail* (*niveau de détail*). Pendant l'échantillonnage d'une texture, le sampler sélectionne le niveau de +mipmap à utiliser suivant ce pseudo-code : + +```c++ +lod = getLodLevelFromScreenSize(); //plus petit quand l'objet est proche, peut être negatif +lod = clamp(lod + mipLodBias, minLod, maxLod); + +level = clamp(floor(lod), 0, texture.mipLevels - 1); //limité par le nombre de niveaux de mipmaps dans le texture + +if (mipmapMode == VK_SAMPLER_MIPMAP_MODE_NEAREST) { + color = sample(level); +} else { + color = blend(sample(level), sample(level + 1)); +} +``` + +Si `samplerInfo.mipmapMode` est `VK_SAMPLER_MIPMAP_MODE_NEAREST`, la variable `lod` correspond au niveau de mipmap à +échantillonner. Sinon, si il vaut `VK_SAMPLER_MIPMAP_MODE_LINEAR`, deux niveaux de mipmaps sont samplés, puis interpolés +linéairement. + +L'opération d'échantillonnage est aussi affectée par `lod` : + +```c++ +if (lod <= 0) { + color = readTexture(uv, magFilter); +} else { + color = readTexture(uv, minFilter); +} +``` + +Si l'objet est proche de la caméra, `magFilter` est utilisé comme filtre. Si l'objet est plus distant, `minFilter` sera +utilisé. Normalement `lod` est positif, est devient nul au niveau de la caméra. `mipLodBias` permet de forcer Vulkan à +utiliser un `lod` plus petit et donc un noveau de mipmap plus élevé. + +Pour voir les résultats de ce chapitre, nous devons choisir les valeurs pour `textureSampler`. Nous avons déjà fourni +`minFilter` et `magFilter`. Il nous reste les valeurs `minLod`, `maxLod`, `mipLodBias` et `mipmapMode`. + +```c++ +void createTextureSampler() { + ... + samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + samplerInfo.minLod = 0.0f; + samplerInfo.maxLod = static_cast(mipLevels); + samplerInfo.mipLodBias = 0.0f; // Optionnel + ... +} +``` + +Pour utiliser la totalité des niveaux de mipmaps, nous mettons `minLod` à `0.0f` et `maxLod` au nombre de niveaux de +mipmaps. Nous n'avons aucune raison d'altérer `lod` avec `mipLodBias`, alors nous pouvons le mettre à `0.0f`. + +Lancez votre programme et vous devriez voir ceci : + +![](/images/mipmaps.png) + +Notre scène est si simple qu'il n'y a pas de différence majeure. En comparant précisement on peut voir quelques +différences. + +![](/images/mipmaps_comparison.png) + +La différence la plus évidente est l'écriture sur le paneau, plus lisse avec les mipmaps. + +Vous pouvez modifier les paramètres du sampler pour voir l'impact sur le rendu. Par exemple vous pouvez empêcher le +sampler d'utiliser le plus haut nivau de mipmap en ne lui indiquant pas le niveau le plus bas : + +```c++ +samplerInfo.minLod = static_cast(mipLevels / 2); +``` + +Ce paramètre produira ce rendu : + +![](/images/highmipmaps.png) + +[Code C++](/code/28_mipmapping.cpp) / +[Vertex shader](/code/26_shader_depth.vert) / +[Fragment shader](/code/26_shader_depth.frag) diff --git a/fr/10_Multisampling.md b/fr/10_Multisampling.md new file mode 100644 index 00000000..85926e07 --- /dev/null +++ b/fr/10_Multisampling.md @@ -0,0 +1,323 @@ +## Introduction + +Notre programme peut maintenant générer plusieurs niveaux de détails pour les textures qu'il utilise. Ces images sont +plus lisses quand vues de loin. Cependant on peut voir des motifs en dent de scie si on regarde les textures de plus +près. Ceci est particulièrement visible sur le rendu de carrés : + +![](/images/texcoord_visualization.png) + +Cet effet indésirable s'appelle "aliasing". Il est dû au manque de pixels pour afficher tous les détails de la +géométrie. Il sera toujours visible, par contre nous pouvons utiliser des techniques pour le réduire considérablement. +Nous allons ici implémenter le [multisample anti-aliasing](https://en.wikipedia.org/wiki/Multisample_anti-aliasing), +terme condensé en MSAA. + +Dans un rendu standard, la couleur d'un pixel est déterminée à partir d'un unique sample, en général le centre du pixel. +Si une ligne passe partiellement par un pixel sans en toucher le centre, sa contribution à la couleur sera nulle. Nous +voudrions plutôt qu'il y contribue partiellement. + +![](/images/aliasing.png) + +Le MSAA consiste à utiliser plusieurs points dans un pixel pour déterminer la couleur d'un pixel. Comme on peut s'y +attendre, plus de points offrent un meilleur résultat, mais consomment plus de ressources. + +![](/images/antialiasing.png) + +Nous allons utiliser le maximum de points possible. Si votre application nécessite plus de performances, il vous suffira +de réduire ce nombre. + +## Récupération du nombre maximal de samples + +Commençons par déterminer le nombre maximal de samples que la carte graphique supporte. Les GPUs modernes supportent au +moins 8 points, mais il peut tout de même différer entre modèles. Nous allons stocker ce nombre dans un membre donnée : + +```c++ +... +VkSampleCountFlagBits msaaSamples = VK_SAMPLE_COUNT_1_BIT; +... +``` + +Par défaut nous n'utilisons qu'un point, ce qui correspond à ne pas utiliser de multisampling. Le nombre maximal est +inscrit dans la structure de type `VkPhysicalDeviceProperties` associée au GPU. Comme nous utilisons un buffer de +profondeur, nous devons prendre en compte le nombre de samples pour la couleur et pour la profondeur. Le plus haut taux +de samples supporté par les deux (&) sera celui que nous utiliserons. Créez une fonction dans laquelle les informations +seront récupérées : + +```c++ +VkSampleCountFlagBits getMaxUsableSampleCount() { + VkPhysicalDeviceProperties physicalDeviceProperties; + vkGetPhysicalDeviceProperties(physicalDevice, &physicalDeviceProperties); + + VkSampleCountFlags counts = 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; +} +``` + +Nous allons maintenant utiliser cette fonction pour donner une valeur à `msaaSamples` pendant la sélection du GPU. Nous +devons modifier la fonction `pickPhysicalDevice` : + +```c++ +void pickPhysicalDevice() { + ... + for (const auto& device : devices) { + if (isDeviceSuitable(device)) { + physicalDevice = device; + msaaSamples = getMaxUsableSampleCount(); + break; + } + } + ... +} +``` + +## Mettre en place une cible de rendu + +Le MSAA consiste à écrire chaque pixel dans un buffer indépendant de l'affichage, dont le contenu est ensuite rendu en +le résolvant à un framebuffer standard. Cette étape est nécessaire car le premier buffer est une image particulière : +elle doit supporter plus d'un échantillon par pixel. Il ne peut pas être utilisé comme framebuffer dans la swap chain. +Nous allons donc devoir changer notre rendu. Nous n'aurons besoin que d'une cible de rendu, car seule une opération +de rendu n'est autorisée à s'exécuter à un instant donné. Créez les membres données suivants : + +```c++ +... +VkImage colorImage; +VkDeviceMemory colorImageMemory; +VkImageView colorImageView; +... +``` + +Cette image doit supporter le nombre de samples déterminé auparavant, nous devons donc le lui fournir durant sa +création. Ajoutez un paramètre `numSamples` à la fonction `createImage` : + +```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; + ... +``` + +Mettez à jour tous les appels avec `VK_SAMPLE_COUNT_1_BIT`. Nous changerons cette valeur pour la nouvelle image. + +```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, depthImage, depthImageMemory); +... +createImage(texWidth, texHeight, mipLevels, VK_SAMPLE_COUNT_1_BIT, VK_FORMAT_R8G8B8A8_SRGB, 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); +``` + +Nous allons maintenant créer un buffer de couleur à plusieurs samples. Créez la fonction `createColorResources`, et +passez `msaaSamples` à `createImage` depuis cette fonction. Nous n'utilisons également qu'un niveau de mipmap, ce qui +est nécessaire pour conformer à la spécification de Vulkan. Mais de toute façon cette image n'a pas besoin de mipmaps. + +```c++ +void createColorResources() { + VkFormat colorFormat = swapChainImageFormat; + + 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, colorImage, colorImageMemory); + colorImageView = createImageView(colorImage, colorFormat, VK_IMAGE_ASPECT_COLOR_BIT, 1); +} +``` + +Pour une question de cohérence mettons cette fonction juste avant `createDepthResource`. + +```c++ +void initVulkan() { + ... + createColorResources(); + createDepthResources(); + ... +} +``` + +Nous avons maintenant un buffer de couleurs qui utilise le multisampling. Occupons-nous maintenant de la profondeur. +Modifiez `createDepthResources` et changez le nombre de samples utilisé : + +```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, depthImage, depthImageMemory); + ... +} +``` + +Comme nous avons créé quelques ressources, nous devons les libérer : + +```c++ +void cleanupSwapChain() { + vkDestroyImageView(device, colorImageView, nullptr); + vkDestroyImage(device, colorImage, nullptr); + vkFreeMemory(device, colorImageMemory, nullptr); + ... +} +``` + +Mettez également à jour `recreateSwapChain` pour prendre en charge les recréations de l'image couleur. + +```c++ +void recreateSwapChain() { + ... + createGraphicsPipeline(); + createColorResources(); + createDepthResources(); + ... +} +``` + +Nous avons fini le paramétrage initial du MSAA. Nous devons maintenant utiliser ces ressources dans la pipeline, le +framebuffer et la render pass! + +## Ajouter de nouveaux attachements + +Gérons d'abord la render pass. Modifiez `createRenderPass` et changez-y la création des attachements de couleur et de +profondeur. + +```c++ +void createRenderPass() { + ... + colorAttachment.samples = msaaSamples; + colorAttachment.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + ... + depthAttachment.samples = msaaSamples; + ... +``` + +Nous avons changé l'organisation finale à `VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL`, car les images qui utilisent le +multisampling ne peuvent être présentées directement. Nous devons la convertir en une image plus classique. Nous +n'aurons pas à convertir le buffer de profondeur, dans la mesure où il ne sera jamais présenté. Nous avons donc besoin +d'un nouvel attachement pour la couleur, dans lequel les pixels seront résolus. + +```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; + ... +``` + +La render pass doit maintenant être configurée pour résoudre l'attachement multisamplé en un attachement simple. +Créez une nouvelle référence au futur attachement qui contiendra le buffer de pixels résolus : + +```c++ + ... + VkAttachmentReference colorAttachmentResolveRef{}; + colorAttachmentResolveRef.attachment = 2; + colorAttachmentResolveRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + ... +``` + +Ajoutez la référence à l'attachement dans le membre `pResolveAttachments` de la structure de création de la subpasse. +La subpasse n'a besoin que de cela pour déterminer l'opération de résolution du multisampling : + +``` + ... + subpass.pResolveAttachments = &colorAttachmentResolveRef; + ... +``` + +Fournissez ensuite l'attachement de couleur à la structure de création de la render pass. + +```c++ + ... + std::array attachments = {colorAttachment, depthAttachment, colorAttachmentResolve}; + ... +``` + +Modifiez ensuite `createFramebuffer` afin de d'ajouter une image view de couleur à la liste : + +```c++ +void createFrameBuffers() { + ... + std::array attachments = { + colorImageView, + depthImageView, + swapChainImageViews[i] + }; + ... +} +``` + +Il ne reste plus qu'à informer la pipeline du nombre de samples à utiliser pour les opérations de rendu. + +```c++ +void createGraphicsPipeline() { + ... + multisampling.rasterizationSamples = msaaSamples; + ... +} +``` + +Lancez votre programme et vous devriez voir ceci : + +![](/images/multisampling.png) + +Comme pour le mipmapping, la différence n'est pas forcément visible immédiatement. En y regardant de plus près, vous +pouvez normalement voir que, par exemple, les bords sont beaucoup plus lisses qu'avant. + +![](/images/multisampling_comparison.png) + +La différence est encore plus visible en zoomant sur un bord : + +![](/images/multisampling_comparison2.png) + +## Amélioration de la qualité + +Notre implémentation du MSAA est limitée, et ces limitations impactent la qualité. Il existe un autre problème +d'aliasing dû aux shaders qui n'est pas résolu par le MSAA. En effet cette technique ne permet que de lisser les bords +de la géométrie, mais pas les lignes contenus dans les textures. Ces bords internes sont particulièrement visibles dans +le cas de couleurs qui contrastent beaucoup. Pour résoudre ce problème nous pouvons activer le +[sample shading](https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap27.html#primsrast-sampleshading), qui +améliore encore la qualité de l'image au prix de performances encore réduites. + +```c++ + +void createLogicalDevice() { + ... + deviceFeatures.sampleRateShading = VK_TRUE; // Activation du sample shading pour le device + ... +} + +void createGraphicsPipeline() { + ... + multisampling.sampleShadingEnable = VK_TRUE; // Activation du sample shading dans la pipeline + multisampling.minSampleShading = .2f; // Fraction minimale pour le sample shading; plus proche de 1 lisse d'autant plus + ... +} +``` + +Dans notre tutoriel nous désactiverons le sample shading, mais dans certain cas son activation permet une nette +amélioration de la qualité du rendu : + +![](/images/sample_shading.png) + +## Conclusion + +Il nous a fallu beaucoup de travail pour en arriver là, mais vous avez maintenant une bonne connaissances des bases de +Vulkan. Ces connaissances vous permettent maintenant d'explorer d'autres fonctionnalités, comme : + +* Push constants +* Instanced rendering +* Uniforms dynamiques +* Descripteurs d'images et de samplers séparés +* Pipeline caching +* Génération des command buffers depuis plusieurs threads +* Multiples subpasses +* Compute shaders + +Le programme actuel peut être grandement étendu, par exemple en ajoutant l'éclairage Blinn-Phong, des effets en +post-processing et du shadow mapping. Vous devriez pouvoir apprendre ces techniques depuis des tutoriels conçus pour +d'autres APIs, car la plupart des concepts sont applicables à Vulkan. + +[Code C++](/code/29_multisampling.cpp) / +[Vertex shader](/code/26_shader_depth.vert) / +[Fragment shader](/code/26_shader_depth.frag) diff --git a/fr/90_FAQ.md b/fr/90_FAQ.md new file mode 100644 index 00000000..4aca4904 --- /dev/null +++ b/fr/90_FAQ.md @@ -0,0 +1,20 @@ +Cette page liste quelques problèmes que vous pourriez rencontrer lors du développement d'une application Vulkan. + +* **J'obtiens un erreur de violation d'accès dans les validations layers** : assurez-vous que MSI Afterburner / +RivaTuner Statistics Server ne tournent pas, car ils possèdent des problèmes de compatibilité avec Vulkan. + +* **Je ne vois aucun message provenant des validation layers / les validation layers ne sont pas disponibles** : +assurez-vous d'abord que les validation layers peuvent écrire leurs message en laissant le terminal ouvert après +l'exécution. Avec Visual Studio, lancez le programme avec Ctrl-F5. Sous Linux, lancez le programme depuis un terminal. +S'il n'y a toujours pas de message, revoyez l'installation du SDK en suivant les instructions de [cette page](https://vulkan.lunarg.com/doc/view/1.2.135.0/windows/getting_started.html) (section "Verify the Installation"). +Assurez-vous également que le SDK est au moins de la version 1.1.106.0 pour le support de `VK_LAYER_KHRONOS_validation`. + +* **vkCreateSwapchainKHR induit une erreur dans SteamOverlayVulkanLayer64.dll** : Il semble qu'il y ait un problème de +compatibilité avec la version beta du client Steam. Il y a quelques moyens de régler le conflit : + * Désinstaller Steam + * Mettre la variable d'environnement `DISABLE_VK_LAYER_VALVE_steam_overlay_1` à `1` + * Supprimer la layer de Steam dans le répertoire sous `HKEY_LOCAL_MACHINE\SOFTWARE\Khronos\Vulkan\ImplicitLayers` + +Exemple pour la variable : + +![](/images/steam_layers_env.png) \ No newline at end of file diff --git "a/fr/95_Politique_de_confidentialit\303\251.md" "b/fr/95_Politique_de_confidentialit\303\251.md" new file mode 100644 index 00000000..1317183b --- /dev/null +++ "b/fr/95_Politique_de_confidentialit\303\251.md" @@ -0,0 +1,34 @@ +## Généralités + +Cette politique de confidentialité concerne les informations collectées quand vous utilisez vulkan-tutorial.com ou +l'un de ses sous-domaines. Il décrit la manière dont Alexander Overvoorde, propriétaire du site, collecte, utilise et +partage les informations vous concernant. + +## Renseignements + +Ce site web collecte des informations sur ses visiteurs à l'aide d'une instance Matomo +([https://matomo.org/](https://matomo.org/)) localement hébergée. Il analyse les pages que vous visitez, le type +d'appareil et le navigateur que vous utilisez, combien de temps vous restez sur une page et comment vous êtes arrivés +sur le site. Ces informations sont anonymisées en ne stockant que les deux premiers octets de votre addresse IP (par +exemple `123.123.xxx.xxx`). Ces données sont stockés pour une durée indéterminée. + +Les données sont utilisées dans déterminer trois données : la manière dont le site est utilisé, le nombre de visiteurs +en général et les sites qui mènent à ce site web. Cela permet de mieux engager un contact avec la communauté, et de +déterminer les zones du site à améliorer. Par exemple cela permet de savoir s'il faut investir plus de temps dans +l'interface mobile. + +Ces données ne sont pas partagées à des tiers. + +## Publicité + +Ce site utilise des publicités fournies par des serveurs tiers. Elles peuvent utiliser des cookies pour suivre +l'activité du site ou l'engagement avec les publicités. + +## Commentaires + +Chaque chapitre comporte une section commentaires. Elle utilise le service tier Disqus. Ce service collecte des données +individuelles pour faciliter la lecture et l'émission de commentaires. Il agglomère ces informations pour améliorer son +offre de services. + +La politique de confidentialité complète de ce service tier se trouve à l'addresse suivante : +[https://help.disqus.com/terms-and-policies/disqus-privacy-policy](https://help.disqus.com/terms-and-policies/disqus-privacy-policy). \ No newline at end of file diff --git a/images/aliasing.png b/images/aliasing.png new file mode 100644 index 00000000..cdb3c269 Binary files /dev/null and b/images/aliasing.png differ diff --git a/images/antialiasing.png b/images/antialiasing.png new file mode 100644 index 00000000..644f569b Binary files /dev/null and b/images/antialiasing.png differ diff --git a/images/cube_demo_mac.png b/images/cube_demo_mac.png new file mode 100644 index 00000000..9a075934 Binary files /dev/null and b/images/cube_demo_mac.png differ diff --git a/images/depth_correct.png b/images/depth_correct.png index 9f6f9487..d9dce89a 100644 Binary files a/images/depth_correct.png and b/images/depth_correct.png differ diff --git a/images/depth_issues.png b/images/depth_issues.png index b1af423c..ad486c95 100644 Binary files a/images/depth_issues.png and b/images/depth_issues.png differ diff --git a/images/drawing_model.png b/images/drawing_model.png index 070dd76c..c327281f 100644 Binary files a/images/drawing_model.png and b/images/drawing_model.png differ diff --git a/images/glfw_directory.png b/images/glfw_directory.png index f0c9fdb6..afd450b3 100644 Binary files a/images/glfw_directory.png and b/images/glfw_directory.png differ diff --git a/images/highmipmaps.png b/images/highmipmaps.png new file mode 100644 index 00000000..bb6c6dd0 Binary files /dev/null and b/images/highmipmaps.png differ diff --git a/images/include_dirs_stb.png b/images/include_dirs_stb.png index 13657e6d..9035e765 100644 Binary files a/images/include_dirs_stb.png and b/images/include_dirs_stb.png differ diff --git a/images/include_dirs_tinyobjloader.png b/images/include_dirs_tinyobjloader.png index e4a1cc3e..4e2fbe36 100644 Binary files a/images/include_dirs_tinyobjloader.png and b/images/include_dirs_tinyobjloader.png differ diff --git a/images/indexed_rectangle.png b/images/indexed_rectangle.png index ef9c801e..2c7498db 100644 Binary files a/images/indexed_rectangle.png and b/images/indexed_rectangle.png differ diff --git a/images/inverted_texture_coordinates.png b/images/inverted_texture_coordinates.png index cb752b42..4d37371e 100644 Binary files a/images/inverted_texture_coordinates.png and b/images/inverted_texture_coordinates.png differ diff --git a/images/library_directory.png b/images/library_directory.png index fd316a14..692349e0 100644 Binary files a/images/library_directory.png and b/images/library_directory.png differ diff --git a/images/mipmaps.png b/images/mipmaps.png new file mode 100644 index 00000000..c48bea75 Binary files /dev/null and b/images/mipmaps.png differ diff --git a/images/mipmaps_comparison.png b/images/mipmaps_comparison.png new file mode 100644 index 00000000..405edfd1 Binary files /dev/null and b/images/mipmaps_comparison.png differ diff --git a/images/mipmaps_example.jpg b/images/mipmaps_example.jpg new file mode 100644 index 00000000..c44c9ee7 Binary files /dev/null and b/images/mipmaps_example.jpg differ diff --git a/images/multisampling.png b/images/multisampling.png new file mode 100644 index 00000000..3066ea50 Binary files /dev/null and b/images/multisampling.png differ diff --git a/images/multisampling_comparison.png b/images/multisampling_comparison.png new file mode 100644 index 00000000..221f4dc9 Binary files /dev/null and b/images/multisampling_comparison.png differ diff --git a/images/multisampling_comparison2.png b/images/multisampling_comparison2.png new file mode 100644 index 00000000..048ffc18 Binary files /dev/null and b/images/multisampling_comparison2.png differ diff --git a/images/clip_coordinates.svg b/images/normalized_device_coordinates.svg similarity index 98% rename from images/clip_coordinates.svg rename to images/normalized_device_coordinates.svg index 35eb7292..970c9f4b 100644 --- a/images/clip_coordinates.svg +++ b/images/normalized_device_coordinates.svg @@ -152,8 +152,8 @@ sodipodi:linespacing="125%">Clip coordinates + x="435.34827" + y="112.66027">Normalized device coordinates