Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 9cf2a2c

Browse files
committed
Remove VDeleter from setup chapters
1 parent 8d225e9 commit 9cf2a2c

10 files changed

Lines changed: 164 additions & 516 deletions

03_Drawing_a_triangle/00_Setup/00_Base_code.md

Lines changed: 43 additions & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public:
1616
void run() {
1717
initVulkan();
1818
mainLoop();
19+
cleanup();
1920
}
2021

2122
private:
@@ -26,6 +27,10 @@ private:
2627
void mainLoop() {
2728

2829
}
30+
31+
void cleanup() {
32+
33+
}
2934
};
3035

3136
int main() {
@@ -52,6 +57,8 @@ as private class members and add functions to initiate each of them, which will
5257
be called from the `initVulkan` function. Once everything has been prepared, we
5358
enter the main loop to start rendering frames. We'll fill in the `mainLoop`
5459
function 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

5663
If 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

6269
Roughly every chapter that follows after this one will add one new function that
6370
will 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

239123
private:
@@ -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-
321210
When you run the program now you should see a window titled `Vulkan` show up
322211
until the application is terminated by closing the window. Now that we have the
323212
skeleton for the Vulkan application, let's [create the first Vulkan object](!Drawing_a_triangle/Setup/Instance)!

03_Drawing_a_triangle/00_Setup/01_Instance.md

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,13 @@ void initVulkan() {
1414
}
1515
```
1616

17-
Additionally add a class member to hold the handle to the instance, like we saw
18-
in the resource management section of the previous chapter.
17+
Additionally add a class member to hold the handle to the instance:
1918

2019
```c++
2120
private:
22-
VDeleter<VkInstance> instance {vkDestroyInstance};
21+
VkInstance instance;
2322
```
2423

25-
The `vkDestroyInstance` function, as you might imagine, will clean up the
26-
instance that we'll create in a moment. The second parameter is optional and
27-
allows you to specify callbacks for a custom allocator. You'll see that most of
28-
the creation and destroy functions have such a callback parameter and we'll
29-
always pass a `nullptr` as argument, as seen in the `VDeleter` definition.
30-
3124
Now, to create an instance we'll first have to fill in a struct with some
3225
information about our application. This data is technically optional, but it may
3326
provide some useful information to the driver to optimize for our specific
@@ -90,7 +83,7 @@ We've now specified everything Vulkan needs to create an instance and we can
9083
finally issue the `vkCreateInstance` call:
9184

9285
```c++
93-
VkResult result = vkCreateInstance(&createInfo, nullptr, instance.replace());
86+
VkResult result = vkCreateInstance(&createInfo, nullptr, &instance);
9487
```
9588

9689
As you'll see, the general pattern that object creation function parameters in
@@ -101,12 +94,12 @@ Vulkan follow is:
10194
* Pointer to the variable that stores the handle to the new object
10295

10396
If everything went well then the handle to the instance was stored in the
104-
wrapped `VkInstance` class member. Nearly all Vulkan functions return a value of
105-
type `VkResult` that is either `VK_SUCCESS` or an error code. To check if the
97+
`VkInstance` class member. Nearly all Vulkan functions return a value of type
98+
`VkResult` that is either `VK_SUCCESS` or an error code. To check if the
10699
instance was created successfully, simply add a check for the success value:
107100

108101
```c++
109-
if (vkCreateInstance(&createInfo, nullptr, instance.replace()) != VK_SUCCESS) {
102+
if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
110103
throw std::runtime_error("failed to create instance!");
111104
}
112105
```
@@ -167,6 +160,27 @@ that checks if all of the extensions returned by
167160
`glfwGetRequiredInstanceExtensions` are included in the supported extensions
168161
list.
169162

163+
## Cleaning up
164+
165+
The `VkInstance` should be only destroyed right before the program exits. It can
166+
be destroyed in `cleanup` with the `vkDestroyInstance` function:
167+
168+
```c++
169+
void cleanup() {
170+
vkDestroyInstance(instance, nullptr);
171+
172+
glfwDestroyWindow(window);
173+
174+
glfwTerminate();
175+
}
176+
```
177+
178+
The parameters for the `vkDestroyInstance` function are straightforward. As
179+
mentioned in the previous chapter, the allocation and deallocation functions
180+
in Vulkan have an optional allocator callback that we'll ignore by passing
181+
`nullptr` to it. All of the other Vulkan resources that we'll create in the
182+
following chapters should be cleaned up before the instance is destroyed.
183+
170184
Before continuing with the more complex steps after instance creation, it's time
171185
to evaluate our debugging options by checking out [validation layers](!Drawing_a_triangle/Setup/Validation_layers).
172186

0 commit comments

Comments
 (0)