Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions en/03_Drawing_a_triangle/00_Setup/01_Instance.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ raii context:
----
private:
vk::raii::Context context;
std::unique_ptr<vk::raii::Instance> instance;
vk::raii::Instance instance = nullptr;
----

Now, to create an instance, we'll first have to fill in a struct with some
Expand Down Expand Up @@ -77,14 +77,26 @@ that which we can pass to the struct:

[,c++]
----
// Get the required instance extensions from GLFW.
uint32_t glfwExtensionCount = 0;
const char** glfwExtensions;

glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);

// Check if the required GLFW extensions are supported by the Vulkan implementation.
auto extensionProperties = context.enumerateInstanceExtensionProperties();
for (uint32_t i = 0; i < glfwExtensionCount; ++i)
{
if (std::ranges::none_of(extensionProperties,
[glfwExtension = glfwExtensions[i]](auto const& extensionProperty)
{ return strcmp(extensionProperty.extensionName, glfwExtension) == 0; }))
{
throw std::runtime_error("Required GLFW extension not supported: " + std::string(glfwExtensions[i]));
}
}

vk::InstanceCreateInfo createInfo{
.pApplicationInfo = &appInfo,
.ppEnabledLayerNames = glfwExtensions};
.enabledExtensionCount = glfwExtensionCount,
.ppEnabledExtensionNames = glfwExtensions};
----

The other missing piece is the Layers to enable. Here is where we'll talk
Expand Down
118 changes: 81 additions & 37 deletions en/03_Drawing_a_triangle/00_Setup/02_Validation_layers.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -104,26 +104,35 @@ constexpr bool enableValidationLayers = true;
#endif
----

We'll add a new function `checkValidationLayerSupport` that checks if all
the requested layers are available. As the instance layers are returned as a
std::array, they can be filtered via a single line and return the value
using lambda functions.

[,c++]
----
bool checkValidationLayerSupport() {
return (std::ranges::any_of(context.enumerateInstanceLayerProperties(),
[]( vk::LayerProperties const & lp ) { return ( strcmp( "VK_LAYER_KHRONOS_validation", lp.layerName ) == 0 ); } ) );
}
----

We can now use this function in `createInstance`:
We'll check if all the requested layers are available. We need to iterate
through the requested layers and validate that all the required layers are
supported by the Vulkan implementation. This check is performed directly in the
`createInstance` function:

[,c++]
----
void createInstance() {
if (enableValidationLayers && !checkValidationLayerSupport()) {
throw std::runtime_error("validation layers requested, but not available!");
constexpr vk::ApplicationInfo appInfo{ .pApplicationName = "Hello Triangle",
.applicationVersion = VK_MAKE_VERSION( 1, 0, 0 ),
.pEngineName = "No Engine",
.engineVersion = VK_MAKE_VERSION( 1, 0, 0 ),
.apiVersion = vk::ApiVersion14 };

// Get the required layers
std::vector<char const*> requiredLayers;
if (enableValidationLayers) {
requiredLayers.assign(validationLayers.begin(), validationLayers.end());
}

// Check if the required layers are supported by the Vulkan implementation.
auto layerProperties = context.enumerateInstanceLayerProperties();
if (std::ranges::any_of(requiredLayers, [&layerProperties](auto const& requiredLayer) {
return std::ranges::none_of(layerProperties,
[requiredLayer](auto const& layerProperty)
{ return strcmp(layerProperty.layerName, requiredLayer) == 0; });
}))
{
throw std::runtime_error("One or more required layers are not supported!");
}

...
Expand All @@ -138,11 +147,12 @@ validation layer names if they are enabled:

[,c++]
----
std::vector<char const *> enabledLayers;
if (enableValidationLayers) {
enabledLayers.assign(validationLayers.begin(), validationLayers.end());
}
vk::InstanceCreateInfo createInfo({}, &appInfo, enabledLayers, {});
vk::InstanceCreateInfo createInfo{
.pApplicationInfo = &appInfo,
.enabledLayerCount = static_cast<uint32_t>(requiredLayers.size()),
.ppEnabledLayerNames = requiredLayers.data(),
.enabledExtensionCount = 0,
.ppEnabledExtensionNames = nullptr };
----

If the check was successful then `vkCreateInstance` should not ever return a
Expand Down Expand Up @@ -171,12 +181,6 @@ std::vector<const char*> getRequiredExtensions() {
uint32_t glfwExtensionCount = 0;
auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);

std::vector<vk::ExtensionProperties> props = context.enumerateInstanceExtensionProperties();
if (const auto propsIterator = std::ranges::find_if(props, []( vk::ExtensionProperties const & ep ) { return strcmp( ep.extensionName, vk::EXTDebugUtilsExtensionName ) == 0; } ); propsIterator == props.end() )
{
std::cout << "Something went very wrong, cannot find VK_EXT_debug_utils extension" << std::endl;
exit( 1 );
}
std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount);
if (enableValidationLayers) {
extensions.push_back(vk::EXTDebugUtilsExtensionName );
Expand Down Expand Up @@ -268,7 +272,7 @@ you want. Add a class member for this handle right under `instance`:

[,c++]
----
std::unique_ptr<vk::raii::DebugUtilsMessengerEXT> debugMessenger;
vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr;
----

Now add a function `setupDebugMessenger` to be called from `initVulkan` right
Expand All @@ -293,9 +297,12 @@ We'll need to fill in a structure with details about the messenger and its callb
----
vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError );
vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation );

vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT({}, severityFlags, messageTypeFlags, &debugCallback);
debugMessenger = std::make_unique<vk::raii::DebugUtilsMessengerEXT>( *instance, debugUtilsMessengerCreateInfoEXT );
vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{
.messageSeverity = severityFlags,
.messageType = messageTypeFlags,
.pfnUserCallback = &debugCallback
};
debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT);
----

The `messageSeverity` field allows you to specify all the types of
Expand Down Expand Up @@ -324,14 +331,51 @@ We can now re-use this in the `createInstance` function:
[,c++]
----
void createInstance() {
constexpr auto appInfo = vk::ApplicationInfo("Hello Triangle", 1, "No Engine", 1, vk::ApiVersion11);
auto extensions = getRequiredExtensions();
std::vector<char const *> enabledLayers;
constexpr vk::ApplicationInfo appInfo{ .pApplicationName = "Hello Triangle",
.applicationVersion = VK_MAKE_VERSION( 1, 0, 0 ),
.pEngineName = "No Engine",
.engineVersion = VK_MAKE_VERSION( 1, 0, 0 ),
.apiVersion = vk::ApiVersion14 };

// Get the required layers
std::vector<char const*> requiredLayers;
if (enableValidationLayers) {
enabledLayers.assign(validationLayers.begin(), validationLayers.end());
requiredLayers.assign(validationLayers.begin(), validationLayers.end());
}

// Check if the required layers are supported by the Vulkan implementation.
auto layerProperties = context.enumerateInstanceLayerProperties();
if (std::ranges::any_of(requiredLayers, [&layerProperties](auto const& requiredLayer) {
return std::ranges::none_of(layerProperties,
[requiredLayer](auto const& layerProperty)
{ return strcmp(layerProperty.layerName, requiredLayer) == 0; });
}))
{
throw std::runtime_error("One or more required layers are not supported!");
}
vk::InstanceCreateInfo createInfo({}, &appInfo, enabledLayers.size(), enabledLayers.data(), extensions.size(), extensions.data());
instance = std::make_unique<vk::raii::Instance>(context, createInfo);

// Get the required extensions.
auto requiredExtensions = getRequiredExtensions();

// Check if the required extensions are supported by the Vulkan implementation.
auto extensionProperties = context.enumerateInstanceExtensionProperties();
for (auto const & requiredExtension : requiredExtensions)
{
if (std::ranges::none_of(extensionProperties,
[requiredExtension](auto const& extensionProperty)
{ return strcmp(extensionProperty.extensionName, requiredExtension) == 0; }))
{
throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension));
}
}

vk::InstanceCreateInfo createInfo{
.pApplicationInfo = &appInfo,
.enabledLayerCount = static_cast<uint32_t>(requiredLayers.size()),
.ppEnabledLayerNames = requiredLayers.data(),
.enabledExtensionCount = static_cast<uint32_t>(requiredExtensions.size()),
.ppEnabledExtensionNames = requiredExtensions.data() };
instance = vk::raii::Instance(context, createInfo);
}
----

Expand Down
79 changes: 41 additions & 38 deletions en/03_Drawing_a_triangle/00_Setup/04_Logical_device_and_queues.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -77,50 +77,40 @@ start doing more interesting things with Vulkan.
vk::PhysicalDeviceFeatures deviceFeatures;
----

== Specifying any extra features or updates we'd like our device to support
== Enabling additional device features

Vulkan is built to be backwards compatible. Thus, if you do nothing else,
you will get a Vulkan instance just as it was originally released in Vulkan 1
.0. This is necessary as code written back then would need to still work so
any additional features must be new code which would need to be turned on.
So, let's tell Vulkan that we use some of the more modern features which make
it easier to work with.
Vulkan is designed to be backwards compatible, which means that by default, you only get access to the basic features that were available in Vulkan 1.0. To use newer features, you need to explicitly request them during device creation.

First, let's query for the features of the physical device:
In Vulkan, features are organized into different structures based on when they were introduced or what functionality they relate to. For example:
- Basic features are in `vk::PhysicalDeviceFeatures`
- Vulkan 1.3 features are in `vk::PhysicalDeviceVulkan13Features`
- Extension-specific features are in their own structures (like `vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT`)

[,c++]
----
// query for Vulkan 1.3 features
auto features = physicalDevice.getFeatures2();
----
To enable multiple sets of features, Vulkan uses a concept called "structure chaining." Each feature structure has a `pNext` field that can point to another structure, creating a chain of feature requests.

Now, let's tell Vulkan that there's a few features we wish to use, by turning
on dynamicRendering and the extendedDynamicState.
The C++ Vulkan API provides a helper template called `vk::StructureChain` that makes this process easier. Let's see how to use it:

[,c++]
----
vk::PhysicalDeviceVulkan13Features vulkan13Features;
vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT extendedDynamicStateFeatures;
vulkan13Features.dynamicRendering = vk::True;
extendedDynamicStateFeatures.extendedDynamicState = vk::True;
// Create a chain of feature structures
vk::StructureChain<vk::PhysicalDeviceFeatures2, vk::PhysicalDeviceVulkan13Features, vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT> featureChain = {
{}, // vk::PhysicalDeviceFeatures2 (empty for now)
{.dynamicRendering = true }, // Enable dynamic rendering from Vulkan 1.3
{.extendedDynamicState = true } // Enable extended dynamic state from the extension
};
----

Note that these are just variables that we created, and aren't part of the
device create info logic structure. In Vulkan, all structures have a .pNext
member variable which allows for chaining. So, we need to tell each object
about the next object in the chain like this:
Here's what's happening in this code:

[,c++]
----
vulkan13Features.pNext = &extendedDynamicStateFeatures;
features.pNext = &vulkan13Features;
----
1. We create a `vk::StructureChain` with three different feature structures.
2. For each structure in the chain, we provide an initializer:
- The first structure (`vk::PhysicalDeviceFeatures2`) is left empty with `{}`
- In the second structure, we enable the `dynamicRendering` feature from Vulkan 1.3
- In the third structure, we enable the `extendedDynamicState` feature from an extension

Note here that features is the same object that we got when we queried the
physical device for the features. Each pNext member variable points to the
next feature structure in the chain. So all that's left is to ensure that
when we create the logical device with a VkDeviceCreateInfo structure we set
.pNext in that structure to the pointer to the features.
The `vk::StructureChain` template automatically connects these structures together by setting up the `pNext` pointers between them. This saves us from having to manually link each structure to the next one.

When we create the logical device later, we'll pass a pointer to the first structure in this chain, which will allow Vulkan to see all the features we want to enable.

== Specifying device extensions

Expand All @@ -140,16 +130,29 @@ The `VK_KHR_swapchain` extension is required for presenting rendered images to t

== Creating the logical device

With the previous structures in place, we can start filling in the main
`VkDeviceCreateInfo` structure.
With all the necessary information prepared, we can now create the logical device. We need to fill in the `vk::DeviceCreateInfo` structure and connect our feature chain to it:

[,c++]
----
vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &features, .queueCreateInfoCount = 1, .pQueueCreateInfos = &deviceQueueCreateInfo };
deviceCreateInfo.enabledExtensionCount = deviceExtensions.size();
deviceCreateInfo.ppEnabledExtensionNames = deviceExtensions.data();
vk::DeviceCreateInfo deviceCreateInfo{
.pNext = &featureChain.get<vk::PhysicalDeviceFeatures2>(),
.queueCreateInfoCount = 1,
.pQueueCreateInfos = &deviceQueueCreateInfo,
.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size()),
.ppEnabledExtensionNames = deviceExtensions.data()
};
----

Reviewing how we connect our feature chain to the device creation process:

1. The `featureChain.get<vk::PhysicalDeviceFeatures2>()` method retrieves a reference to the first structure in our chain (the `vk::PhysicalDeviceFeatures2` structure).

2. We assign this reference to the `pNext` field of the `deviceCreateInfo` structure.

3. Since all the structures in our feature chain are already connected (thanks to `vk::StructureChain`), Vulkan will be able to see all the features we want to enable by following the chain of `pNext` pointers.

This approach allows us to request multiple sets of features in a clean and organized way. Vulkan will process each structure in the chain and enable the requested features during device creation.

The remainder of the information bears a resemblance to the
`VkInstanceCreateInfo` struct and requires you to specify extensions and
validation layers. The difference is that these are device-specific this time.
Expand Down
Loading