diff --git a/attachments/02_validation_layers.cpp b/attachments/02_validation_layers.cpp index 11fb1e0d..ecc6eb01 100644 --- a/attachments/02_validation_layers.cpp +++ b/attachments/02_validation_layers.cpp @@ -92,36 +92,38 @@ class HelloTriangleApplication } // Check if the required layers are supported by the Vulkan implementation. - auto layerProperties = context.enumerateInstanceLayerProperties(); - for (auto const &requiredLayer : requiredLayers) + auto layerProperties = context.enumerateInstanceLayerProperties(); + auto unsupportedLayerIt = std::ranges::find_if(requiredLayers, + [&layerProperties](auto const &requiredLayer) { + return std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; }); + }); + if (unsupportedLayerIt != requiredLayers.end()) { - if (std::ranges::none_of(layerProperties, - [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) - { - throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); - } + throw std::runtime_error("Required layer not supported: " + std::string(*unsupportedLayerIt)); } // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); + auto requiredExtensions = getRequiredInstanceExtensions(); // Check if the required extensions are supported by the Vulkan implementation. auto extensionProperties = context.enumerateInstanceExtensionProperties(); - for (auto const &requiredExtension : requiredExtensions) + auto unsupportedPropertyIt = + std::ranges::find_if(requiredExtensions, + [&extensionProperties](auto const &requiredExtension) { + return std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; }); + }); + if (unsupportedPropertyIt != requiredExtensions.end()) { - 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)); - } + throw std::runtime_error("Required extension not supported: " + std::string(*unsupportedPropertyIt)); } - vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = static_cast(requiredExtensions.size()), - .ppEnabledExtensionNames = requiredExtensions.data()}; + vk::InstanceCreateInfo createInfo{.pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; instance = vk::raii::Instance(context, createInfo); } @@ -130,16 +132,18 @@ class HelloTriangleApplication if (!enableValidationLayers) return; - 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{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback}; + 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{.messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); } - std::vector getRequiredExtensions() + std::vector getRequiredInstanceExtensions() { uint32_t glfwExtensionCount = 0; auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); diff --git a/en/03_Drawing_a_triangle/00_Setup/00_Base_code.adoc b/en/03_Drawing_a_triangle/00_Setup/00_Base_code.adoc index ea022a37..4d05d28c 100644 --- a/en/03_Drawing_a_triangle/00_Setup/00_Base_code.adoc +++ b/en/03_Drawing_a_triangle/00_Setup/00_Base_code.adoc @@ -4,11 +4,11 @@ == Vulkan-hpp and designated initializers -NOTE: We are going to use designated initializers introduced with C++ 20. By default, -Vulkan-hpp uses a different way of initializing and we need to explicitly enable this -by using the `VULKAN_HPP_NO_STRUCT_CONSTRUCTORS` define. +NOTE: We are going to use designated initializers introduced with C++ 20. +By default, Vulkan-hpp uses a different way of initializing and we need to explicitly enable this by using the `VULKAN_HPP_NO_STRUCT_CONSTRUCTORS` define. -This provides a better meaning towards what each option relates to in the structures that we're depending upon. For this tutorial, said define is declared in the xref:../../02_Development_environment.adoc#cmake[CMake build setup]. +This provides a better meaning towards what each option relates to in the structures that we're depending upon. +For this tutorial, said define is declared in the xref:../../02_Development_environment.adoc#cmake[CMake build setup]. If you use a different build setup or want to write code from scratch, you need to manually define this before including the Vulkan-hpp headers like this: [,c++] @@ -21,9 +21,8 @@ If you use a different build setup or want to write code from scratch, you need == General structure -In the previous chapter, you've created a Vulkan project with all the proper - configurations and tested it with the sample code. In this chapter, we're starting -from scratch with the following code: +In the previous chapter, you've created a Vulkan project with all the proper configurations and tested it with the sample code. +In this chapter, we're starting from scratch with the following code: [,c++] ---- @@ -60,12 +59,15 @@ private: } }; -int main() { - HelloTriangleApplication app; - - try { +int main() +{ + try + { + HelloTriangleApplication app; app.run(); - } catch (const std::exception& e) { + } + catch (const std::exception& e) + { std::cerr << e.what() << std::endl; return EXIT_FAILURE; } @@ -108,11 +110,10 @@ we no longer need it. In c{pp} it is possible to perform automatic resource management using https://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization[RAII] or smart pointers provided in the `` header. This tutorial is an attempt to make Vulkan easier to work with, and demonstrate modern Vulkan -programming. This tutorial will not only use RAII with smart pointers, it -will endeavor to demonstrate the latest methods and extensions which should -hopefully make Vulkan a joy to use. Just because we enjoy working with -low level graphics APIs, we shouldn't make the bar too high to learn how -to do so. Where appropriate, we will discuss concerns for resource +programming. This tutorial will not only use RAII, it will endeavor to demonstrate the +latest methods and extensions which should hopefully make Vulkan a joy to use. Just +because we enjoy working with low level graphics APIs, we shouldn't make the bar too +high to learn how to do so. Where appropriate, we will discuss concerns for resource management for freeing resources. However, for this tutorial, we'll demonstrate that we can get pretty far with a basic destructor to clean up after our work. @@ -132,7 +133,7 @@ of code that looks like this: [,c++] ---- -vkInstance instance; +VkInstance instance; VkApplicationInfo appInfo{}; appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; appInfo.pApplicationName = "Hello Triangle"; @@ -146,8 +147,8 @@ createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; createInfo.pApplicationInfo = &appInfo; createInfo.enabledExtensionCount = 0; createInfo.ppEnabledExtensionNames = nullptr; - createInfo.enabledLayerCount = 0; +createInfo.ppEnabledLayerNames = nullptr; if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); @@ -160,11 +161,11 @@ can be directly replaced by this: [,c++] ---- -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 }; +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}; vk::InstanceCreateInfo createInfo{ .pApplicationInfo = &appInfo diff --git a/en/03_Drawing_a_triangle/00_Setup/01_Instance.adoc b/en/03_Drawing_a_triangle/00_Setup/01_Instance.adoc index d127d014..884ab43a 100644 --- a/en/03_Drawing_a_triangle/00_Setup/01_Instance.adoc +++ b/en/03_Drawing_a_triangle/00_Setup/01_Instance.adoc @@ -33,16 +33,17 @@ 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 our specific application, (e.g., because it uses a well-known graphics engine with -certain special behavior). This struct is called `VkApplicationInfo`: +certain special behavior). This struct is called `vk::ApplicationInfo`: [,c++] ---- -void createInstance() { - 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 }; +void createInstance() +{ + 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}; } ---- @@ -67,13 +68,13 @@ vk::InstanceCreateInfo createInfo{ }; ---- -The first parameter is the flags for the structure, the second is the -appInfo that we just created. The next is an array of layers being -requested, and the final is an array of the desired global extensions. As -mentioned in the overview chapter, Vulkan is a platform-agnostic API, which -means that you need an extension to interface with 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: +This structure has a member named flags, which we will handle later in this chapter. +The member pApplicationInfo points to the appInfo that we just created. +The next is an array of layers being requested, and the final is an array of +the desired global extensions. As mentioned in the overview chapter, Vulkan +is a platform-agnostic API, which means that you need an extension to interface +with 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++] ---- @@ -105,7 +106,7 @@ important layers to enable for any project. We'll talk about this more in-depth in the next chapter, so leave this empty for now. We've now specified everything Vulkan needs to create an instance, and we can -finally issue the `vk:CreateInstance` call: +finally create the vk::raii::Instance: [,c++] ---- @@ -122,14 +123,15 @@ Vulkan follow is: * Returns the pointer to the raii constructed object. If everything went well, then the handle to the instance was returned. We can -check that everything worked by use of c{pp} exceptions, or a more advanced -way is to turn off exceptions by defining VULKAN_HPP_NO_EXCEPTIONS. Then +check that everything worked by use of c{pp} exceptions. If you can't use c{pp} +exceptions, you can turn them off by defining VULKAN_HPP_NO_EXCEPTIONS. Then the calls will return a std::tuple with a VKResult and the returned object. Here's an example of checking for errors in Vulkan calls: [,c++] ---- - try { + try + { vk::raii::Context context; vk::raii::Instance instance(context, vk::InstanceCreateInfo{}); vk::raii::PhysicalDevice physicalDevice = instance.enumeratePhysicalDevices().front(); @@ -137,10 +139,14 @@ Here's an example of checking for errors in Vulkan calls: // Use Vulkan objects vk::raii::Buffer buffer(device, vk::BufferCreateInfo{}); - } catch (const vk::SystemError& err) { + } + catch (const vk::SystemError& err) + { std::cerr << "Vulkan error: " << err.what() << std::endl; return 1; - } catch (const std::exception& err) { + } + catch (const std::exception& err) + { std::cerr << "Error: " << err.what() << std::endl; return 1; } @@ -150,43 +156,48 @@ Or use the tuple: [,c++] ---- - auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphores[frameIndex], nullptr); - - if (result == vk::Result::eErrorOutOfDateKHR) - { - recreateSwapChain(); - return; - } - if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) - { - throw std::runtime_error("failed to acquire swap chain image!"); - } + vk::raii::Context context; + ... + auto instanceRV = context.createInstance(...); + if (!instanceRV.has_value()) + { + std::cerr << "Error: Instance creation failed with " << vk::to_string(instanceVR.result) << std::endl; + std::exit(EXIT_FAILURE); + } + vk::raii::Instance instance = std::move(instanceRV.value); + ... + auto physicalDevicesRV = instance.enumeratePhysicalDevices(); + if (!physicalDevicesRV.hasValue()) + { + std::cerr << "Error: Enumerating PhysicalDevices failed with " << vk::to_string(physicalDevicesRV) << std::endl; + std::exit(EXIT_FAILURE); + } + vk::raii::PhysicalDevice physicalDevice = std::move(physicalDevicesRV.value.front()); + ... ---- -Those examples are from later parts of our tutorial, this is just an example +This example is from later parts of our tutorial, it is just an example of how to check for errors in all of your calls. -== Encountered VK_ERROR_INCOMPATIBLE_DRIVER: -If using macOS with the latest MoltenVK sdk, you may get `VK_ERROR_INCOMPATIBLE_DRIVER` -returned from `vkCreateInstance`. According to the +== Encountered vk::Result::eErrorIncompatibleDriver: +If using macOS with the latest MoltenVK sdk, you may get `vk::Result::eErrorIncompatibleDriver`, +thrown from `vk::raii::createInstance` or the vk::raii::Instance constructor. According to the https://vulkan.lunarg.com/doc/sdk/1.3.216.0/mac/getting_started.html[Getting Start Notes]. -Beginning with the 1.3.216 Vulkan SDK, the `VK_KHR_PORTABILITY_subset` -extension is mandatory. +Beginning with the 1.3.216 Vulkan SDK, the `VK_KHR_PORTABILITY_subset` extension is mandatory. To get over this error, first add the `vk::InstanceCreateFlagBits::eEnumeratePortabilityKHR` bit -to `VkInstanceCreateInfo` struct flags, then add -`vk::KHRPortabilityEnumerationExtensionName` to instance enabled -extension list. +to `vk::InstanceCreateInfo` struct flags, then add `vk::KHRPortabilityEnumerationExtensionName` +to instance enabled extension list. Typically, the code could be like this: [,c++] ---- -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 }; +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}; vk::InstanceCreateInfo createInfo{ .flags = vk::InstanceCreateFlagBits::eEnumeratePortabilityKHR, .pApplicationInfo = &appInfo, @@ -204,17 +215,16 @@ That makes sense for essential extensions like the window system interface, but what if we want to check for optional functionality? To retrieve a list of supported extensions before creating an instance, there's -the `vkEnumerateInstanceExtensionProperties` function. We can call it on the -context object; it returns a vector of the extensions available, which -allows us to filter extensions by a specific validation layer, which we'll -ignore for now. +the `vk::raii::Context::enumerateInstanceExtensionProperties` function. It returns +a vector of the available extensions, which allows us to filter extensions by a +specific validation layer, which we'll ignore for now. [,c++] ---- auto extensions = context.enumerateInstanceExtensionProperties(); ---- -Each `VkExtensionProperties` struct contains the name and version of an +Each `vk::ExtensionProperties` struct contains the name and version of an extension. We can list them with a simple for loop (`\t` is a tab for indentation): @@ -228,10 +238,7 @@ for (const auto& extension : extensions) { ---- You can add this code to the `createInstance` function if you'd like to provide -some details about the Vulkan support. As a challenge, try to create a function -that checks if all the extensions returned by -`glfwGetRequiredInstanceExtensions` are included in the supported extensions -list. +some details about the Vulkan support. Before continuing with the more complex steps after instance creation, it's time to evaluate our debugging options by checking out xref:./02_Validation_layers.adoc[validation layers]. diff --git a/en/03_Drawing_a_triangle/00_Setup/02_Validation_layers.adoc b/en/03_Drawing_a_triangle/00_Setup/02_Validation_layers.adoc index f7b10ef3..46160be2 100644 --- a/en/03_Drawing_a_triangle/00_Setup/02_Validation_layers.adoc +++ b/en/03_Drawing_a_triangle/00_Setup/02_Validation_layers.adoc @@ -70,11 +70,7 @@ 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 -xref:./04_Logical_device_and_queues.adoc[later on]. +calls. == Using validation layers @@ -111,29 +107,84 @@ supported by the Vulkan implementation. This check is performed directly in the [,c++] ---- -void createInstance() { - 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 }; +void createInstance() +{ + ... // Get the required layers std::vector requiredLayers; - if (enableValidationLayers) { + 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!"); - } + auto unsupportedLayerIt = std::ranges::find_if(requiredLayers, + [&layerProperties](auto const &requiredLayer) { + return std::ranges::none_of(layerProperties, + [requiredLayer](auto const &layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; }); + }); + if (unsupportedLayerIt != requiredLayers.end()) + { + throw std::runtime_error("Required layer not supported: " + std::string(*unsupportedLayerIt)); + } + + ... +} +---- + +== Using extensions + +As already mentioned above, extensions also need to be enabled by their name. But +here, you have to distinguish between instance- and device-extensions. + +We'll first create a getRequiredInstanceExtensions function that will return a +list of the required instance extensions. + +[,c++] +---- +std::vector getRequiredInstanceExtensions() +{ + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + return extensions; +} +---- + +The extensions specified by GLFW are always required, as we're working with +the GLFW dependency for windowing. + +We'll check if all the required extensions are available. We first get a list of all +supported instance extensions by using the +`vk::raii::Context::enumerateInstanceLayerProperties` function and check that all +required layers are listed in that list. This check is also performed directly in the +`createInstance` function: + +[,c++] +---- +void createInstance() +{ + ... + + // Get the required extensions. + auto requiredExtensions = getRequiredInstanceExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + auto unsupportedPropertyIt = + std::ranges::find_if(requiredExtensions, + [&extensionProperties](auto const &requiredExtension) { + return std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; }); + }); + if (unsupportedPropertyIt != requiredExtensions.end()) + { + throw std::runtime_error("Required extension not supported: " + std::string(*unsupportedPropertyIt)); + } ... } @@ -142,21 +193,27 @@ void createInstance() { 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: +Finally, modify the `vk::InstanceCreateInfo` struct instantiation to include the +validation layer names and the extension names: [,c++] ---- -vk::InstanceCreateInfo createInfo{ - .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), - .enabledExtensionCount = 0, - .ppEnabledExtensionNames = nullptr }; +void createInstance() +{ + ... + + vk::InstanceCreateInfo createInfo{.pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + instance = vk::raii::Instance(context, createInfo); +} ---- -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. +If the check was successful then the `vk::raii::Instance` constructor should not +ever throw a `vk::Result::eErrorLayerNotPresent` error, but you should run the +program to make sure. == Message callback @@ -171,44 +228,31 @@ 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 +We'll first extent the `getRequiredInstanceExtensions` function based on whether +validation layers are enabled or not: [,c++] ---- -std::vector getRequiredExtensions() { +std::vector getRequiredInstanceExtensions() +{ uint32_t glfwExtensionCount = 0; auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if (enableValidationLayers) { - extensions.push_back(vk::EXTDebugUtilsExtensionName ); + if (enableValidationLayers) + { + extensions.push_back(vk::EXTDebugUtilsExtensionName); } return extensions; } ---- -The extensions specified by GLFW are always required, as we're working with -the GLFW dependency for windowing, but the debug messenger extension is -conditionally added. Note that We've used the -`VK_EXT_DEBUG_UTILS_EXTENSION_NAME` macro here which is equal to the literal +The debug messenger extension is conditionally added. Note that we've used the +`vk::EXTDebugUtilsExtensionName` 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(); -vk::InstanceCreateInfo createInfo({}, &appInfo, requiredLayers, extensions); ----- - -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 @@ -216,23 +260,24 @@ right signature for Vulkan to call it. [,c++] ---- -static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { - std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; - - return vk::False; +static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, + vk::DebugUtilsMessageTypeFlagsEXT type, + const vk::DebugUtilsMessengerCallbackDataEXT * pCallbackData, + void * pUserData) +{ + std::cerr << "validation layer: type " << to_string(type) << " msg: " << 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 +* `vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose`: Diagnostic message +* `vk::DebugUtilsMessageSeverityFlagBitsEXT::eInfo` : Informational message like the creation of a resource +* `vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning`: Message about behavior that is not necessarily an error, but very likely a bug in your application +* `vk::DebugUtilsMessageSeverityFlagBitsEXT::eError` : 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 @@ -247,14 +292,14 @@ if (messageSeverity >= vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { 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 +* `vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral` : Some event has happened that is unrelated to the specification or performance +* `vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation` : Something has happened that violates the specification or indicates a possible mistake +* `vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance`: 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: +The `pCallbackData` parameter refers to a `vk::DebugUtilsMessengerCallbackDataEXT` 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 +* `pMessage` : The debug message as a null-terminated string +* `pObjects` : Array of Vulkan object handles related to the message * `objectCount`: Number of objects in the array Finally, the `pUserData` parameter contains a pointer specified during the @@ -262,12 +307,12 @@ 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 +then the call is aborted with the `vk::Result::eErrorValidationFailedEXT` error. This is normally only used to test the validation layers themselves, so you should -always return `VK_FALSE`. +always return `vk::False`. All that remains now is telling Vulkan about the callback function. Such a -callback is part of a *debug messenger,* and you can have as many of them as +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++] @@ -280,12 +325,14 @@ after `createInstance`: [,c++] ---- -void initVulkan() { +void initVulkan() +{ createInstance(); setupDebugMessenger(); } -void setupDebugMessenger() { +void setupDebugMessenger() +{ if (!enableValidationLayers) return; } @@ -295,19 +342,24 @@ We'll need to fill in a structure with details about the messenger and its callb [,c++] ---- -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{ - .messageSeverity = severityFlags, - .messageType = messageTypeFlags, - .pfnUserCallback = &debugCallback - }; -debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); +void setupDebugMessenger() +{ + ... + 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{.messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback}; + debugMessenger = instance.createDebugUtilsMessengerEXT( debugUtilsMessengerCreateInfoEXT ); +} ---- The `messageSeverity` field allows you to specify all the types of severities you would like your callback to be called for. We've specified -all types except for `VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT` here to +all types except for `vk::DebugUtilsMessageSeverityFlagBitsEXT::eInfo` here to receive notifications about possible problems while leaving out verbose general debug info. @@ -326,11 +378,12 @@ and debug callbacks, but this is a good setup to get started with for this tutorial. See the https://docs.vulkan.org/spec/latest/chapters/debugging.html#VK_EXT_debug_utils[extension specification] for more info about the possibilities. -We can now re-use this in the `createInstance` function: +The complete `createInstance` function now looks like: [,c++] ---- -void createInstance() { +void createInstance() +{ constexpr vk::ApplicationInfo appInfo{ .pApplicationName = "Hello Triangle", .applicationVersion = VK_MAKE_VERSION( 1, 0, 0 ), .pEngineName = "No Engine", @@ -355,19 +408,20 @@ void createInstance() { } // Get the required extensions. - auto requiredExtensions = getRequiredExtensions(); + auto requiredExtensions = getRequiredInstanceExtensions(); // 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)); - } - } + auto unsupportedPropertyIt = + std::ranges::find_if(requiredExtensions, + [&extensionProperties](auto const &requiredExtension) { + return std::ranges::none_of(extensionProperties, + [requiredExtension](auto const &extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; }); + }); + if (unsupportedPropertyIt != requiredExtensions.end()) + { + throw std::runtime_error("Required extension not supported: " + std::string(*unsupportedPropertyIt)); + } vk::InstanceCreateInfo createInfo{ .pApplicationInfo = &appInfo, @@ -382,7 +436,7 @@ void createInstance() { == Configuration There are a lot more settings for the behavior of validation layers than just -the flags specified in the `VkDebugUtilsMessengerCreateInfoEXT` struct. Browse +the flags specified in the `vk::DebugUtilsMessengerCreateInfoEXT` 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.