Skip to content

Commit dd53e8f

Browse files
committed
Refactor: modernize Vulkan tutorial with RAII and dynamic rendering
Simplified examples by updating to RAII patterns for Vulkan-Hpp bindings. Added dynamic rendering to replace render pass and framebuffer usage, aligning with Vulkan 1.3+. Improved code readability with structured initialization and fixed typos in documentation.
1 parent 69487d7 commit dd53e8f

31 files changed

+824
-587
lines changed

en/03_Drawing_a_triangle/00_Setup/00_Base_code.adoc

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
== General structure
66

77
In the previous chapter, you've created a Vulkan project with all the proper
8-
configuration and tested it with the sample code. In this chapter, we're starting
8+
configurations and tested it with the sample code. In this chapter, we're starting
99
from scratch with the following code:
1010

1111
[,c++]
@@ -143,14 +143,34 @@ can be directly replaced by this:
143143
vk::raii::instance = std::make_unique<vk::raii::Instance>(context, createInfo);
144144
----
145145

146+
As this can be a little hard to read when we look at the structures. We have
147+
opted to turn on VULKAN_HPP_NO_STRUCT_CONSTRUCTORS. This option means that
148+
the above code will look like this throughout the tutorial:
149+
150+
[,c++]
151+
----
152+
constexpr vk::ApplicationInfo appInfo{ .pApplicationName = "Hello Triangle",
153+
.applicationVersion = VK_MAKE_VERSION( 1, 0, 0 ),
154+
.pEngineName = "No Engine",
155+
.engineVersion = VK_MAKE_VERSION( 1, 0, 0 ),
156+
.apiVersion = vk::ApiVersion14 };
157+
vk::InstanceCreateInfo createInfo{
158+
.pApplicationInfo = &appInfo
159+
};
160+
instance = vk::raii::Instance(context, createInfo);
161+
----
162+
163+
This provides a better meaning towards what each option relates to in the
164+
structures that we're depending upon.
165+
146166
== Integrating GLFW
147167

148168
Vulkan works perfectly fine without creating a window if you want to use it for
149169
off-screen rendering, but it's a lot more exciting to actually show something!
150170
First, let's add GLFW: NB: we will continue to use the GLFW_INCLUDE_VULKAN as
151171
GLFW is designed to get a Vulkan Surface, but it uses the C surface directly.
152172
Other than that task, we can use GLFW_INCLUDE_NONE or not make that
153-
specification and everything else works perfectly fine.
173+
specification, and everything else works perfectly fine.
154174

155175
[,c++]
156176
----

en/03_Drawing_a_triangle/00_Setup/01_Instance.adoc

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,11 @@ certain special behavior). This struct is called `VkApplicationInfo`:
3838
[,c++]
3939
----
4040
void createInstance() {
41-
constexpr auto appInfo = vk::ApplicationInfo("Hello Triangle", 1, "No Engine", 1, vk::ApiVersion14);
41+
constexpr vk::ApplicationInfo appInfo{ .pApplicationName = "Hello Triangle",
42+
.applicationVersion = VK_MAKE_VERSION( 1, 0, 0 ),
43+
.pEngineName = "No Engine",
44+
.engineVersion = VK_MAKE_VERSION( 1, 0, 0 ),
45+
.apiVersion = vk::ApiVersion14 };
4246
}
4347
----
4448

@@ -58,7 +62,9 @@ device, which will become clear in the next few chapters.
5862

5963
[,c++]
6064
----
61-
vk::InstanceCreateInfo createInfo({}, &appInfo, {}, {});
65+
vk::InstanceCreateInfo createInfo{
66+
.pApplicationInfo = &appInfo
67+
};
6268
----
6369

6470
The first parameter is the flags for the structure, the second is the
@@ -76,12 +82,14 @@ const char** glfwExtensions;
7682
7783
glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
7884
79-
vk::InstanceCreateInfo createInfo({}, &appInfo, {}, glfwExtensions);
85+
vk::InstanceCreateInfo createInfo{
86+
.pApplicationInfo = &appInfo,
87+
.ppEnabledLayerNames = glfwExtensions};
8088
----
8189

8290
The other missing piece is the Layers to enable. Here is where we'll talk
8391
about how to enable validation layers, which is one of the most useful and
84-
important layers to enable for any project. We'll talk about these more
92+
important layers to enable for any project. We'll talk about this more
8593
in-depth in the next chapter, so leave this empty for now.
8694

8795
We've now specified everything Vulkan needs to create an instance, and we can
@@ -160,8 +168,16 @@ Typically, the code could be like this:
160168

161169
[,c++]
162170
----
163-
constexpr auto appInfo = vk::ApplicationInfo("Hello Triangle", 1, "No Engine", 1, vk::ApiVersion11);
164-
vk::InstanceCreateInfo createInfo(vk::InstanceCreateFlagBits::eEnumeratePortabilityKHR, &appInfo, {}, { vk::KHRPortabilityEnumerationExtensionName });
171+
constexpr vk::ApplicationInfo appInfo{ .pApplicationName = "Hello Triangle",
172+
.applicationVersion = VK_MAKE_VERSION( 1, 0, 0 ),
173+
.pEngineName = "No Engine",
174+
.engineVersion = VK_MAKE_VERSION( 1, 0, 0 ),
175+
.apiVersion = vk::ApiVersion14 };
176+
vk::InstanceCreateInfo createInfo{
177+
.flas = vk::InstanceCreateFlagBits::eEnumeratePortabilityKHR,
178+
.pApplicationInfo = &appInfo,
179+
.ppEnabledExtensionNames = { vk::KHRPortabilityEnumerationExtensionName }
180+
};
165181
instance = std::make_unique<vk::raii::Instance>(context, createInfo);
166182
----
167183

@@ -175,7 +191,7 @@ what if we want to check for optional functionality?
175191

176192
To retrieve a list of supported extensions before creating an instance, there's
177193
the `vkEnumerateInstanceExtensionProperties` function. We can call it on the
178-
context object; it returns a vector of the extensions available which
194+
context object; it returns a vector of the extensions available, which
179195
allows us to filter extensions by a specific validation layer, which we'll
180196
ignore for now.
181197

en/03_Drawing_a_triangle/00_Setup/02_Validation_layers.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ layers are optional components that hook into Vulkan function calls to apply
2020
additional operations. Common operations in validation layers are:
2121

2222
* Checking the values of parameters against the specification to detect misuse
23-
* Tracking creation and destruction of objects to find resource leaks
23+
* Tracking the creation and destruction of objects to find resource leaks
2424
* Checking thread safety by tracking the threads that calls originate from
2525
* Logging every call and its parameters to the standard output
2626
* Tracing Vulkan calls for profiling and replaying

en/03_Drawing_a_triangle/00_Setup/03_Physical_devices_and_queue_families.adoc

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -157,13 +157,47 @@ You don't need to implement all that for this tutorial, but it's to give you an
157157
idea of how you could design your device selection process. Of course, you can
158158
also display the names of the choices and allow the user to select.
159159

160-
Because we're just starting out, Vulkan support is the only thing we need, and
161-
therefore we'll settle for just any GPU:
160+
Because we're just starting out, Vulkan 1.3 support is the only thing we need,
161+
and therefore we'll search for that and the extensions that we actually are
162+
going to be demonstrating:
162163

163164
[,c++]
164165
----
166+
std::vector<const char*> deviceExtensions = {
167+
vk::KHRSwapchainExtensionName,
168+
vk::KHRSpirv14ExtensionName,
169+
vk::KHRSynchronization2ExtensionName,
170+
vk::KHRCreateRenderpass2ExtensionName
171+
};
172+
165173
void pickPhysicalDevice() {
166-
physicalDevice = std::make_unique<vk::raii::PhysicalDevice>(vk::raii::PhysicalDevices( *instance ).front());
174+
std::vector<vk::raii::PhysicalDevice> devices = instance.enumeratePhysicalDevices();
175+
const auto devIter = std::ranges::find_if(devices,
176+
[&](auto const & device) {
177+
auto queueFamilies = device.getQueueFamilyProperties();
178+
bool isSuitable = device.getProperties().apiVersion >= VK_API_VERSION_1_3;
179+
const auto qfpIter = std::ranges::find_if(queueFamilies,
180+
[]( vk::QueueFamilyProperties const & qfp )
181+
{
182+
return (qfp.queueFlags & vk::QueueFlagBits::eGraphics) != static_cast<vk::QueueFlags>(0);
183+
} );
184+
isSuitable = isSuitable && ( qfpIter != queueFamilies.end() );
185+
auto extensions = device.enumerateDeviceExtensionProperties( );
186+
bool found = true;
187+
for (auto const & extension : deviceExtensions) {
188+
auto extensionIter = std::ranges::find_if(extensions, [extension](auto const & ext) {return strcmp(ext.extensionName, extension) == 0;});
189+
found = found && extensionIter != extensions.end();
190+
}
191+
isSuitable = isSuitable && found;
192+
printf("\n");
193+
if (isSuitable) {
194+
physicalDevice = device;
195+
}
196+
return isSuitable;
197+
});
198+
if (devIter == devices.end()) {
199+
throw std::runtime_error("failed to find a suitable GPU!");
200+
}
167201
}
168202
----
169203

en/03_Drawing_a_triangle/00_Setup/04_Logical_device_and_queues.adoc

Lines changed: 72 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,9 @@ Right now we're only interested in a queue with graphics capabilities.
4343

4444
[,c++]
4545
----
46-
std::vector<vk::QueueFamilyProperties> queueFamilyProperties = physicalDevice->getQueueFamilyProperties();
46+
std::vector<vk::QueueFamilyProperties> queueFamilyProperties = physicalDevice.getQueueFamilyProperties();
4747
48-
vk::DeviceQueueCreateInfo deviceQueueCreateInfo( {}, graphicsIndex, {} );
48+
vk::DeviceQueueCreateInfo deviceQueueCreateInfo { .queueFamilyIndex = graphicsIndex };
4949
----
5050

5151
The currently available drivers will only allow you to create a small number of
@@ -59,8 +59,8 @@ This is required even if there is only a single queue:
5959

6060
[,c++]
6161
----
62-
float queuePriority = 1.0f;
63-
vk::DeviceQueueCreateInfo deviceQueueCreateInfo( {}, graphicsIndex, &queuePriority );
62+
float queuePriority = 0.0f;
63+
vk::DeviceQueueCreateInfo deviceQueueCreateInfo { .queueFamilyIndex = graphicsIndex, .queueCount = 1, .pQueuePriorities = &queuePriority };
6464
----
6565

6666
== Specifying used device features
@@ -77,14 +77,77 @@ start doing more interesting things with Vulkan.
7777
vk::PhysicalDeviceFeatures deviceFeatures;
7878
----
7979

80+
== Specifying any extra features or updates we'd like our device to support
81+
82+
Vulkan is built to be backwards compatible. Thus, if you do nothing else,
83+
you will get a Vulkan instance just as it was originally released in Vulkan 1
84+
.0. This is necessary as code written back then would need to still work so
85+
any additional features must be new code which would need to be turned on.
86+
So, let's tell Vulkan that we use some of the more modern features which make
87+
it easier to work with.
88+
89+
First, let's query for the features of the physical device:
90+
91+
[,c++]
92+
----
93+
// query for Vulkan 1.3 features
94+
auto features = physicalDevice.getFeatures2();
95+
----
96+
97+
Now, let's tell Vulkan that there's a few features we wish to use, by turning
98+
on dynamicRendering and the extendedDynamicState.
99+
100+
[,c++]
101+
----
102+
vk::PhysicalDeviceVulkan13Features vulkan13Features;
103+
vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT extendedDynamicStateFeatures;
104+
vulkan13Features.dynamicRendering = vk::True;
105+
extendedDynamicStateFeatures.extendedDynamicState = vk::True;
106+
----
107+
108+
Note that these are just variables that we created, and aren't part of the
109+
device create info logic structure. In Vulkan, all structures have a .pNext
110+
member variable which allows for chaining. So, we need to tell each object
111+
about the next object in the chain like this:
112+
113+
[,c++]
114+
----
115+
vulkan13Features.pNext = &extendedDynamicStateFeatures;
116+
features.pNext = &vulkan13Features;
117+
----
118+
119+
Note here that features is the same object that we got when we queried the
120+
physical device for the features. Each pNext member variable points to the
121+
next feature structure in the chain. So all that's left is to ensure that
122+
when we create the logical device with a VkDeviceCreateInfo structure we set
123+
.pNext in that structure to the pointer to the features.
124+
125+
== Specifying device extensions
126+
127+
For our application to work properly, we need to enable certain device extensions. These extensions provide additional functionality that we'll need later in the tutorial.
128+
129+
[,c++]
130+
----
131+
std::vector<const char*> deviceExtensions = {
132+
vk::KHRSwapchainExtensionName,
133+
vk::KHRSpirv14ExtensionName,
134+
vk::KHRSynchronization2ExtensionName,
135+
vk::KHRCreateRenderpass2ExtensionName
136+
};
137+
----
138+
139+
The `VK_KHR_swapchain` extension is required for presenting rendered images to the window. The other extensions provide additional functionality that we'll use in later parts of the tutorial.
140+
80141
== Creating the logical device
81142

82-
With the previous two structures in place, we can start filling in the main
143+
With the previous structures in place, we can start filling in the main
83144
`VkDeviceCreateInfo` structure.
84145

85146
[,c++]
86147
----
87-
vk::DeviceCreateInfo createInfo( {}, deviceQueueCreateInfo, {}, deviceExtensions, &deviceFeatures );
148+
vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &features, .queueCreateInfoCount = 1, .pQueueCreateInfos = &deviceQueueCreateInfo };
149+
deviceCreateInfo.enabledExtensionCount = deviceExtensions.size();
150+
deviceCreateInfo.ppEnabledExtensionNames = deviceExtensions.data();
88151
----
89152

90153
The remainder of the information bears a resemblance to the
@@ -103,11 +166,11 @@ device-specific validation layers, but this is [no longer the case]
103166
That means that the `enabledLayerCount` and `ppEnabledLayerNames` fields of
104167
`VkDeviceCreateInfo` are ignored by up-to-date implementations.
105168

106-
We won't need any device-specific extensions for now.
169+
As mentioned earlier, we need several device-specific extensions for our application to work properly.
107170

108171
[,c++]
109172
----
110-
device = std::make_unique<vk::raii::Device>( *physicalDevice, deviceCreateInfo );
173+
device = vk::raii::Device( physicalDevice, deviceCreateInfo );
111174
----
112175

113176
The parameters are the physical device to interface with, and the usage
@@ -139,7 +202,7 @@ creating a single queue from this family, we'll simply use index `0`.
139202

140203
[,c++]
141204
----
142-
graphicsQueue = std::make_unique<vk::raii::Queue>( *device, graphicsIndex, 0 );
205+
graphicsQueue = vk::raii::Queue( device, graphicsIndex, 0 );
143206
----
144207

145208
With the logical device and queue handles, we can now actually start using the

0 commit comments

Comments
 (0)