Update getting_started.md

Include reference to add_required_extension_features and per-version feature enables.
Fix type with CustomQueueDescription.
This commit is contained in:
Charles Giessen 2024-02-14 14:15:45 -06:00
parent a330227666
commit 3849dafadb

View File

@ -1,6 +1,6 @@
# Getting Started # Getting Started
`vk-bootstrap` reduces the complexity of setting up a vulkan application by simplifying the three initial steps; instance creation, Physical device selection, and device creation. `vk-bootstrap` reduces the complexity of setting up a vulkan application by simplifying the three initial steps; instance creation, Physical device selection, and device creation.
## Instance Creation ## Instance Creation
@ -16,15 +16,15 @@ Because creating an instance may fail, the builder returns an 'Result' type. Thi
if (!instance_builder_return) { if (!instance_builder_return) {
std::cerr << "Failed to create Vulkan instance. Error: " << instance_builder_return.error().message() << "\n"; std::cerr << "Failed to create Vulkan instance. Error: " << instance_builder_return.error().message() << "\n";
return -1; return -1;
} }
``` ```
Once any possible errors have been dealt with, we can pull the `vkb::Instance` struct out of the `Result`. Once any possible errors have been dealt with, we can pull the `vkb::Instance` struct out of the `Result`.
```cpp ```cpp
vkb::Instance vkb_instance = instance_builder_return.value(); vkb::Instance vkb_instance = instance_builder_return.value();
``` ```
This is enough to create a usable `VkInstance` handle but many will want to customize it a bit. To configure instance creation, simply call the member functions on the `vkb::InstanceBuilder` object before `build()` is called. This is enough to create a usable `VkInstance` handle but many will want to customize it a bit. To configure instance creation, simply call the member functions on the `vkb::InstanceBuilder` object before `build()` is called.
The most common customization to instance creation is enabling the "Validation Layers", an invaluable tool for any vulkan application developer. The most common customization to instance creation is enabling the "Validation Layers", an invaluable tool for any vulkan application developer.
```cpp ```cpp
instance_builder.request_validation_layers (); instance_builder.request_validation_layers ();
``` ```
@ -34,7 +34,7 @@ instance_builder.set_debug_callback (
[] (VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, [] (VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
VkDebugUtilsMessageTypeFlagsEXT messageType, VkDebugUtilsMessageTypeFlagsEXT messageType,
const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
void *pUserData) void *pUserData)
-> VkBool32 { -> VkBool32 {
auto severity = vkb::to_string_message_severity (messageSeverity); auto severity = vkb::to_string_message_severity (messageSeverity);
auto type = vkb::to_string_message_type (messageType); auto type = vkb::to_string_message_type (messageType);
@ -49,7 +49,7 @@ instance_builder.use_default_debug_messenger();
``` ```
Configuration can be chained together and done inline with building, like so. Configuration can be chained together and done inline with building, like so.
```cpp ```cpp
auto inst_builder_ret = instance_builder auto inst_builder_ret = instance_builder
.set_app_name ("Awesome Vulkan Application") .set_app_name ("Awesome Vulkan Application")
.set_engine_name("Excellent Game Engine") .set_engine_name("Excellent Game Engine")
.require_api_version(1,0,0) .require_api_version(1,0,0)
@ -83,9 +83,9 @@ CustomVulkanWrapper custom_vk_class;
custom_vk_class.instance = vkb_instance.instance; custom_vk_class.instance = vkb_instance.instance;
``` ```
When the application is finished with the vulkan, call `vkb::destroy_instance()` to dispose of the instance and associated data. When the application is finished with the vulkan, call `vkb::destroy_instance()` to dispose of the instance and associated data.
```cpp ```cpp
// cleanup // cleanup
vkb::destroy_instance(vkb_instance); vkb::destroy_instance(vkb_instance);
``` ```
### Instance Creation Summary ### Instance Creation Summary
@ -98,7 +98,7 @@ auto instance_builder_return = instance_builder
.build (); .build ();
if (!instance_builder_return) { if (!instance_builder_return) {
// Handle error // Handle error
} }
vkb::Instance vkb_instance = instance_builder_return.value (); vkb::Instance vkb_instance = instance_builder_return.value ();
// at program end // at program end
@ -106,11 +106,11 @@ vkb::destroy_instance(vkb_instance);
``` ```
## Surface Creation ## Surface Creation
Presenting images to the screen Vulkan requires creating a surface, encapsulated in a `VkSurfaceKHR` handle. Creating a surface is the responsibility of the windowing system, thus is out of scope for `vk-bootstrap`. However, `vk-bootstrap` does try to make the process as painless as possible by automatically enabling the correct windowing extensions in `VkInstance` creation. Presenting images to the screen Vulkan requires creating a surface, encapsulated in a `VkSurfaceKHR` handle. Creating a surface is the responsibility of the windowing system, thus is out of scope for `vk-bootstrap`. However, `vk-bootstrap` does try to make the process as painless as possible by automatically enabling the correct windowing extensions in `VkInstance` creation.
Windowing libraries which support Vulkan usually provide a way of getting the `VkSurfaceKHR` handle for the window. These methods require a valid Vulkan instance, thus must be done after instance creation. Windowing libraries which support Vulkan usually provide a way of getting the `VkSurfaceKHR` handle for the window. These methods require a valid Vulkan instance, thus must be done after instance creation.
Examples for GLFW and SDL2 are listed below. Examples for GLFW and SDL2 are listed below.
```cpp ```cpp
vkb::Instance vkb_instance; //valid vkb::Instance vkb::Instance vkb_instance; //valid vkb::Instance
VkSurfaceKHR surface = nullptr; VkSurfaceKHR surface = nullptr;
@ -134,14 +134,14 @@ Creating a `vkb::PhysicalDeviceSelector` requires a valid `vkb::Instance` to con
It follows the same pattern laid out by `vkb::InstanceBuilder`. It follows the same pattern laid out by `vkb::InstanceBuilder`.
```cpp ```cpp
vkb::PhysicalDeviceSelector phys_device_selector (vkb_instance); vkb::PhysicalDeviceSelector phys_device_selector (vkb_instance);
auto physical_device_selector_return = phys_device_selector auto physical_device_selector_return = phys_device_selector
.set_surface(surface_handle) .set_surface(surface_handle)
.select (); .select ();
if (!physical_device_selector_return) { if (!physical_device_selector_return) {
// Handle error // Handle error
} }
auto phys_device = phys_device_ret.value (); auto phys_device = physical_device_selector_return.value ();
``` ```
To select a physical device, call `select()` on the `vkb::PhysicalDeviceSelector` object. To select a physical device, call `select()` on the `vkb::PhysicalDeviceSelector` object.
@ -149,11 +149,13 @@ By default, this will prefer a discrete GPU.
No cleanup is required for `vkb::PhysicalDevice`. No cleanup is required for `vkb::PhysicalDevice`.
The `vkb::PhysicalDeviceSelector` will look for the first device in the list that satisfied all the specified criteria, and if none is found, will return the first device that partially satisfies the criteria. The `vkb::PhysicalDeviceSelector` will look for the first device in the list that satisfied all the specified criteria, and if none is found, will return the first device that partially satisfies the criteria.
The various "require" and "desire" pairs of functions indicate to `vk-bootstrap` what features and capabilities are necessary for an application and what are simply preferred. A "require" function will fail any `VkPhysicalDevice` that doesn't satisfy the constraint, while any criteria that doesn't satisfy the "desire" functions will make the `VkPhysicalDevice` only 'partially satisfy'. The various "require" functions indicate to `vk-bootstrap` what features and capabilities are necessary for an application. A "require" function will fail any `VkPhysicalDevice` that doesn't satisfy the constraint.
```c For example, "requiring" certain device extensions to be supported is done as follows:
```cpp
// Application cannot function without this extension // Application cannot function without this extension
phys_device_selector.add_required_extension("VK_KHR_timeline_semaphore"); phys_device_selector.add_required_extension("VK_KHR_timeline_semaphore");
@ -161,9 +163,50 @@ phys_device_selector.add_required_extension("VK_KHR_timeline_semaphore");
phys_device_selector.add_desired_extension("VK_KHR_imageless_framebuffer"); phys_device_selector.add_desired_extension("VK_KHR_imageless_framebuffer");
``` ```
Note: While requiring that certain features are available is as follows:
Because `vk-bootstrap` does not manage creating a `VkSurfaceKHR` handle, it is explicitly passed into the `vkb::PhysicalDeviceSelector` for proper querying of surface support details. Unless the `vkb::InstanceBuilder::set_headless()` function was called, the physical device selector will emit `no_surface_provided` error. If an application does intend to present but cannot create a `VkSurfaceKHR` handle before physical device selection, use `defer_surface_initialization()` to disable the `no_surface_provided` error. ```cpp
VkPhysicalDeviceFeatures required_features{};
required_features.multiViewport = true;
phys_device_selector.set_required_features(required_features);
```
To enable features for newer versions of Vulkan, use `set_required_features_11()`, `set_required_features_12()`, and `set_required_features_13()` and follow the same pattern as `set_required_features()` of passing in the features struct, corresponding to the version.
Note that `set_required_features_11()` was released with 1.2, so it cannot be used for 1.1 only capable Vulkan devices.
Features only available through extensions need to use `add_required_extension_features()`. For example:
```cpp
VkPhysicalDeviceDescriptorIndexingFeatures descriptor_indexing_features{};
descriptor_indexing_features.<features_used> = true;
phys_device_selector.add_required_extension_features(&descriptor_indexing_features);
```
The features and extensions used as selection criteria in `vkb::PhysicalDeviceSelector` automatically propagate into `vkb::DeviceBuilder`. That means the application only needs to state the feature requirement once, and `vk-bootstrap` will handle enabling it on the resulting device.
Note:
Because `vk-bootstrap` does not manage creating a `VkSurfaceKHR` handle, it is explicitly passed into the `vkb::PhysicalDeviceSelector` for proper querying of surface support details. Unless the `vkb::InstanceBuilder::set_headless()` function was called, the physical device selector will emit `no_surface_provided` error. If an application does intend to present but cannot create a `VkSurfaceKHR` handle before physical device selection, use `defer_surface_initialization()` to disable the `no_surface_provided` error.
## Physical Device
The `vkb::PhysicalDevice` represents a chosen physical device, along with all the necessary details about how to create a `VkDevice` from it with the requested features and extensions. While most use cases will simply give the `vkb::PhysicalDevice` to `vkb::DeviceBuilder`, there are a handful of useful things that can be done with it.
Adding optional extensions. It is occasionally useful to enable features if they are present but not require that they be available on the physical device.
This is done using `enable_extension_if_present()` as follows.
```cpp
bool supported = phys_device.enable_extension_if_present("VK_KHR_timeline_semaphore");
if (supported){
// allows easy feedback whether an extension is supported or not.
}
```
Use `enable_extensions_if_present()` to check if a group of extensions are available, and enable all of them if they are all present. This will *not* enable any extension unless they are all present, useful for handling dependencies between extensions, where one extension requires another one to be enabled.
## Device Creation ## Device Creation
@ -178,17 +221,6 @@ if (!dev_ret) {
vkb::Device vkb_device = dev_ret.value(); vkb::Device vkb_device = dev_ret.value();
``` ```
The features and extensions used as selection criteria in `vkb::PhysicalDeviceSelector` automatically propagate into `vkb::DeviceBuilder`. Because of this, there is no way to enable features or extensions that were not specified during `vkb::PhysicalDeviceSelector`. This is by design as any feature or extension enabled in a device *must* have support from the `VkPhysicalDevice` it is created with.
The common method to extend Vulkan functionality in existing API calls is to use the pNext chain. This is accounted for `VkDevice` creation with the `add_pNext` member function of `vkb::DeviceBuilder`. Note: Any structures added to the pNext chain must remain valid until `build()` is called.
```cpp
VkPhysicalDeviceDescriptorIndexingFeatures descriptor_indexing_features{};
auto dev_ret = device_builder.add_pNext(&descriptor_indexing_features)
.build ();
```
To destroy a `vkb::Device`, call `vkb::destroy_device()`. To destroy a `vkb::Device`, call `vkb::destroy_device()`.
```cpp ```cpp
vkb::destroy_device(vkb_device); vkb::destroy_device(vkb_device);
@ -222,14 +254,13 @@ for (uint32_t i = 0; i < static_cast<uint32_t>(queue_families.size ()); i++) {
if (queue_families[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) { if (queue_families[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
// Find the first queue family with graphics operations supported // Find the first queue family with graphics operations supported
queue_descriptions.push_back (vkb::CustomQueueDescription ( queue_descriptions.push_back (vkb::CustomQueueDescription (
i, queue_families[i].queueCount,
std::vector<float> (queue_families[i].queueCount, 1.0f))); std::vector<float> (queue_families[i].queueCount, 1.0f)));
} }
} }
``` ```
## Swapchain ## Swapchain
Creating a swapchain follows the same form outlined by `vkb::InstanceBuilder` and `vkb::DeviceBuilder`. Create the `vkb::SwapchainBuilder`, provide `vkb::Device`, call the appropriate builder functions, and call `build()`. Creating a swapchain follows the same form outlined by `vkb::InstanceBuilder` and `vkb::DeviceBuilder`. Create the `vkb::SwapchainBuilder`, provide `vkb::Device`, call the appropriate builder functions, and call `build()`.
```cpp ```cpp
vkb::SwapchainBuilder swapchain_builder{ device }; vkb::SwapchainBuilder swapchain_builder{ device };