Skip to content
Open
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
58 changes: 31 additions & 27 deletions attachments/02_validation_layers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<uint32_t>(requiredLayers.size()),
.ppEnabledLayerNames = requiredLayers.data(),
.enabledExtensionCount = static_cast<uint32_t>(requiredExtensions.size()),
.ppEnabledExtensionNames = requiredExtensions.data()};
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 All @@ -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<const char *> getRequiredExtensions()
std::vector<const char *> getRequiredInstanceExtensions()
{
uint32_t glfwExtensionCount = 0;
auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
Expand Down
49 changes: 25 additions & 24 deletions en/03_Drawing_a_triangle/00_Setup/00_Base_code.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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++]
Expand All @@ -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++]
----
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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 `<memory>` 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.
Expand All @@ -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";
Expand All @@ -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!");
Expand All @@ -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
Expand Down
115 changes: 61 additions & 54 deletions en/03_Drawing_a_triangle/00_Setup/01_Instance.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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};
}
----

Expand All @@ -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++]
----
Expand Down Expand Up @@ -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++]
----
Expand All @@ -122,25 +123,30 @@ 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();
vk::raii::Device device(physicalDevice, vk::DeviceCreateInfo{});

// 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;
}
Expand All @@ -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,
Expand All @@ -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):

Expand All @@ -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].
Expand Down
Loading
Loading