@@ -16,6 +16,7 @@ public:
1616 void run() {
1717 initVulkan();
1818 mainLoop();
19+ cleanup();
1920 }
2021
2122private:
@@ -26,6 +27,10 @@ private:
2627 void mainLoop () {
2728
2829 }
30+
31+ void cleanup() {
32+
33+ }
2934};
3035
3136int main () {
@@ -52,6 +57,8 @@ as private class members and add functions to initiate each of them, which will
5257be called from the ` initVulkan ` function. Once everything has been prepared, we
5358enter the main loop to start rendering frames. We'll fill in the ` mainLoop `
5459function to include a loop that iterates until the window is closed in a moment.
60+ Once the window is closed and ` mainLoop ` returns, we'll make sure to deallocate
61+ the resources we've used in the ` cleanup ` function.
5562
5663If any kind of fatal error occurs during execution then we'll throw a
5764` std::runtime_error ` exception with a descriptive message, which will propagate
@@ -61,157 +68,33 @@ extension is not supported.
6168
6269Roughly every chapter that follows after this one will add one new function that
6370will be called from ` initVulkan ` and one or more new Vulkan objects to the
64- private class members.
71+ private class members that need to be freed at the end in ` cleanup ` .
6572
6673## Resource management
6774
68- You may have noticed that there's no cleanup function anywhere to be seen and
69- that is intentional. Every Vulkan object needs to be destroyed with a function
70- call when it's no longer needed, just like each chunk of memory allocated with
71- ` malloc ` requires a call to ` free ` . Doing that manually is a lot of work and is
72- very error-prone, but we can completely avoid that by taking advantage of the
73- C++ [ RAII] ( https://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization )
74- principle. To do that, we're going to create a class that wraps Vulkan objects
75- and automatically cleans them up when it goes out of scope, for example because
76- the application was closed.
77-
78- First consider the interface we want from this ` VDeleter ` wrapper class.
79- Let's say we want to store a ` VkInstance ` object that should be destroyed with
80- ` vkDestroyInstance ` at some point. Then we would add the following class member:
81-
82- ``` c++
83- VDeleter<VkInstance> instance{vkDestroyInstance};
84- ```
85-
86- The template argument specifies the type of Vulkan object we want to wrap and
87- the constructor argument specifies the function to use to clean up the object
88- when it goes out of scope.
89-
90- To assign an object to the wrapper, we would simply want to pass its pointer to
91- the creation function as if it was a normal `VkInstance` variable:
92-
93- ```c++
94- vkCreateInstance(&instanceCreateInfo, nullptr, &instance);
95- ```
96-
97- Unfortunately, taking the address of the handle in the wrapper doesn't
98- necessarily mean that we want to overwrite its existing value. A common pattern
99- is to simply use ` &instance ` as short-hand for an array of instances with 1
100- item. If we intend to write a new handle, then the wrapper should clean up any
101- previous object to not leak memory. Therefore it would be better to have the ` & `
102- operator return a constant pointer and have an explicit function to state that
103- we wish to replace the handle. The ` replace ` function calls clean up for any
104- existing handle and then gives you a non-const pointer to overwrite the handle:
105-
106- ``` c++
107- vkCreateInstance (&instanceCreateInfo, nullptr, instance.replace());
108- ```
109-
110- Just like that we can now use the `instance` variable wherever a `VkInstance`
111- would normally be accepted. We no longer have to worry about cleaning up
112- anymore, because that will automatically happen once the `instance` variable
113- becomes unreachable! That's pretty easy, right?
114-
115- The implementation of such a wrapper class is fairly straightforward. It just
116- requires a bit of lambda magic to shorten the syntax for specifying the cleanup
117- functions.
118-
119- ```c++
120- template <typename T>
121- class VDeleter {
122- public:
123- VDeleter() : VDeleter([](T, VkAllocationCallbacks*) {}) {}
124-
125- VDeleter(std::function<void(T, VkAllocationCallbacks*)> deletef) {
126- this->deleter = [=](T obj) { deletef(obj, nullptr); };
127- }
128-
129- VDeleter(const VDeleter<VkInstance>& instance, std::function<void(VkInstance, T, VkAllocationCallbacks*)> deletef) {
130- this->deleter = [&instance, deletef](T obj) { deletef(instance, obj, nullptr); };
131- }
132-
133- VDeleter(const VDeleter<VkDevice>& device, std::function<void(VkDevice, T, VkAllocationCallbacks*)> deletef) {
134- this->deleter = [&device, deletef](T obj) { deletef(device, obj, nullptr); };
135- }
136-
137- ~VDeleter() {
138- cleanup();
139- }
140-
141- const T* operator &() const {
142- return &object;
143- }
144-
145- T* replace() {
146- cleanup();
147- return &object;
148- }
149-
150- operator T() const {
151- return object;
152- }
153-
154- void operator=(T rhs) {
155- if (rhs != object) {
156- cleanup();
157- object = rhs;
158- }
159- }
160-
161- template<typename V>
162- bool operator==(V rhs) {
163- return object == T(rhs);
164- }
165-
166- private:
167- T object{VK_NULL_HANDLE};
168- std::function<void(T)> deleter;
169-
170- void cleanup() {
171- if (object != VK_NULL_HANDLE) {
172- deleter(object);
173- }
174- object = VK_NULL_HANDLE;
175- }
176- };
177- ```
178-
179- The three non-default constructors allow you to specify all three types of
180- deletion functions used in Vulkan:
181-
182- * ` vkDestroyXXX(object, callbacks) ` : Only the object itself needs to be passed
183- to the cleanup function, so we can simply construct a ` VDeleter ` with just the
184- function as argument.
185- * ` vkDestroyXXX(instance, object, callbacks) ` : A ` VkInstance ` also
186- needs to be passed to the cleanup function, so we use the ` VDeleter ` constructor
187- that takes the ` VkInstance ` reference and cleanup function as parameters.
188- * ` vkDestroyXXX(device, object, callbacks) ` : Similar to the previous case, but a
189- ` VkDevice ` must be passed instead of a ` VkInstance ` .
190-
191- The ` callbacks ` parameter is optional and we always pass ` nullptr ` to it, as you
192- can see in the ` VDeleter ` definition.
193-
194- All of the constructors initialize the object handle with the equivalent of
195- ` nullptr ` in Vulkan: ` VK_NULL_HANDLE ` . Any extra arguments that are needed for
196- the deleter functions must also be passed, usually the parent object. It
197- overloads the address-of, assignment, comparison and casting operators to make
198- the wrapper as transparent as possible. When the wrapped object goes out of
199- scope, the destructor is invoked, which in turn calls the cleanup function we
200- specified.
201-
202- The address-of operator returns a constant pointer to make sure that the object
203- within the wrapper is not unexpectedly changed. If you want to replace the
204- handle within the wrapper through a pointer, then you should use the ` replace() `
205- function instead. It will invoke the cleanup function for the existing handle so
206- that you can safely overwrite it afterwards.
207-
208- There is also a default constructor with a dummy deleter function that can be
209- used to initialize it later, which will be useful for lists of deleters.
210-
211- I've added the class code between the headers and the ` HelloTriangleApplication `
212- class definition. You can also choose to put it in a separate header file. We'll
213- use it for the first time in the next chapter where we'll create the very first
214- Vulkan object!
75+ Just like each chunk of memory allocated with ` malloc ` requires a call to
76+ ` free ` , every Vulkan object that we create needs to be explicitly destroyed when
77+ we no longer need it. In modern C++ code it is possible to do automatic resource
78+ management through the utilities in the ` <memory> ` header, but I've chosen to be
79+ explicit about allocation and deallocation of Vulkan objects in this tutorial.
80+ After all, Vulkan's niche is to be explicit about every operation to avoid
81+ mistakes, so it's good to be explicit about the lifetime of objects to learn how
82+ the API works.
83+
84+ After following this tutorial, you could implement automatic resource management
85+ by overloading ` std::shared_ptr ` for example. Using [ RAII] ( https://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization )
86+ to your advantage is the recommended approach for larger Vulkan programs, but
87+ for learning purposes it's always good to know what's going on behind the
88+ scenes.
89+
90+ Vulkan objects are either created directly with functions like ` vkCreateXXX ` , or
91+ allocated through another object with functions like ` vkAllocateXXX ` . After
92+ making sure that an object is no longer used anywhere, you need to destroy it
93+ with the counterparts ` vkDestroyXXX ` and ` vkFreeXXX ` . The parameters for these
94+ functions generally vary for different types of objects, but there is one
95+ parameter that they all share: ` pAllocator ` . This is an optional parameter that
96+ allows you to specify callbacks for a custom memory allocator. We will ignore
97+ this parameter in the tutorial and always pass ` nullptr ` as argument.
21598
21699## Integrating GLFW
217100
@@ -234,6 +117,7 @@ void run() {
234117 initWindow ();
235118 initVulkan();
236119 mainLoop();
120+ cleanup();
237121}
238122
239123private:
@@ -305,19 +189,24 @@ void mainLoop() {
305189 while (!glfwWindowShouldClose(window)) {
306190 glfwPollEvents ();
307191 }
192+ }
193+ ```
308194
195+ This code should be fairly self-explanatory. It loops and checks for events like
196+ pressing the X button until the window has been closed by the user. This is also
197+ the loop where we'll later call a function to render a single frame.
198+
199+ Once the window is closed, we need to clean up resources by destroying it and
200+ terminating GLFW itself. This will be our first ` cleanup ` code:
201+
202+ ``` c++
203+ void cleanup () {
309204 glfwDestroyWindow (window);
310205
311206 glfwTerminate ();
312207}
313208```
314209
315- This code should be fairly self-explanatory. It loops and checks for events like
316- pressing the X button until the window has been closed by the user. This is also
317- the loop where we'll later call a function to render a single frame. Once the
318- window is closed, we need to clean up resources by destroying it and GLFW]
319- itself.
320-
321210When you run the program now you should see a window titled ` Vulkan ` show up
322211until the application is terminated by closing the window. Now that we have the
323212skeleton for the Vulkan application, let's [ create the first Vulkan object] ( !Drawing_a_triangle/Setup/Instance ) !
0 commit comments