From f290028cb5d0a1e20e9a807de742ee204cd551d1 Mon Sep 17 00:00:00 2001 From: Charles Giessen Date: Sun, 24 Sep 2023 21:20:50 -0600 Subject: [PATCH] Add new Queue query API The original queue query API has shortcomings which cause confusion and make it harder to do the things people want with vk-bootstrap. Design features of the new API: * Choose a queue given a combination of queue types - Instead of only choosing based on a single queue type * Allow picking the first or 'preferred' queue for any given combination of queue types * Allow picking a present queue with a specific surface handle - Instead of only using the surface the PhysicalDevice was selected with * Dont need to update the library to support new queue types over time --- docs/getting_started.md | 59 ++++++---- src/VkBootstrap.cpp | 152 ++++++++++++++++++------- src/VkBootstrap.h | 48 ++++++-- tests/bootstrap_tests.cpp | 6 +- tests/unit_tests.cpp | 227 +++++++++++++++++++++++++++++++++++--- tests/vulkan_mock.cpp | 4 +- 6 files changed, 409 insertions(+), 87 deletions(-) diff --git a/docs/getting_started.md b/docs/getting_started.md index 923404b..393bbd0 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -1,6 +1,6 @@ # 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 @@ -16,17 +16,17 @@ Because creating an instance may fail, the builder returns an 'Result' type. Thi if (!instance_builder_return) { std::cerr << "Failed to create Vulkan instance. Error: " << instance_builder_return.error().message() << "\n"; 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 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. -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 -instance_builder.request_validation_layers (); +instance_builder.request_validation_layers(); ``` The other common customization point is setting up the `Debug Messenger Callback`, the mechanism in which an application can control what and where the "Validation Layers" log its output. ```cpp @@ -34,7 +34,7 @@ instance_builder.set_debug_callback ( [] (VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, - void *pUserData) + void *pUserData) -> VkBool32 { auto severity = vkb::to_string_message_severity (messageSeverity); 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. ```cpp -auto inst_builder_ret = instance_builder +auto inst_builder_ret = instance_builder .set_app_name ("Awesome Vulkan Application") .set_engine_name("Excellent Game Engine") .require_api_version(1,0,0) @@ -83,9 +83,9 @@ CustomVulkanWrapper custom_vk_class; 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 -// cleanup +// cleanup vkb::destroy_instance(vkb_instance); ``` ### Instance Creation Summary @@ -98,7 +98,7 @@ auto instance_builder_return = instance_builder .build (); if (!instance_builder_return) { // Handle error -} +} vkb::Instance vkb_instance = instance_builder_return.value (); // at program end @@ -106,11 +106,11 @@ vkb::destroy_instance(vkb_instance); ``` ## 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. -Examples for GLFW and SDL2 are listed below. +Examples for GLFW and SDL2 are listed below. ```cpp vkb::Instance vkb_instance; //valid vkb::Instance VkSurfaceKHR surface = nullptr; @@ -134,7 +134,7 @@ Creating a `vkb::PhysicalDeviceSelector` requires a valid `vkb::Instance` to con It follows the same pattern laid out by `vkb::InstanceBuilder`. ```cpp -vkb::PhysicalDeviceSelector phys_device_selector (vkb_instance); +vkb::PhysicalDeviceSelector phys_device_selector (vkb_instance); auto physical_device_selector_return = phys_device_selector .set_surface(surface_handle) .select (); @@ -149,21 +149,19 @@ By default, this will prefer a discrete GPU. 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 // Application cannot function without this extension phys_device_selector.add_required_extension("VK_KHR_timeline_semaphore"); -// Application can deal with the lack of this extension -phys_device_selector.add_desired_extension("VK_KHR_imageless_framebuffer"); ``` -Note: +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. +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. ## Device Creation @@ -198,17 +196,30 @@ vkb::destroy_device(vkb_device); By default, `vkb::DeviceBuilder` will enable one queue from each queue family available on the `VkPhysicalDevice`. This is done because in practice, most use cases only need a single queue from each family. -To get a `VkQueue` or the index of a `VkQueue`, use the `get_queue(QueueType type)` and `get_queue_index(QueueType type)` functions of `vkb::Device`. These will return the appropriate `VkQueue` or `uint32_t` if they exist and were enabled, else they will return an error. +To get a `VkQueue` and the index of a `VkQueue`, use the `get_preferred_queue_and_index(VkQueueFlags flags)` functions of `vkb::Device`. These will return the appropriate `VkQueue` handle and `uint32_t` index stored in a `vkb::QueueAndIndex` struct if they exist and were enabled, else they will return an error. ```cpp -auto queue_ret = vkb_device.get_queue (vkb::QueueType::graphics); +auto queue_ret = vkb_device.get_preferred_queue_and_index (VK_QUEUE_GRAPHICS_BIT); if (!queue_ret) { // handle error } graphics_queue = queue_ret.value (); ``` -Queue families represent a set of queues with similar operations, such as graphics, transfer, and compute. Because not all Vulkan hardware has queue families for each operation category, an application should be able to handle the presence or lack of certain queue families. For this reason the `get_dedicated_queue` and `get_dedicated_queue_index` functions of `vkb::Device` exist to allow applications to easily know if there is a queue dedicated to a particular operation, such as compute or transfer operations. +To query a queue capable of multiple operations, use multiple `VkQueueFlags` combined with bitwise-or to look for a queue that supports all specified flags, if such a queue exists. + +```cpp +vkb_device.get_preferred_queue_and_index(VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT); +``` + +To get a `VkQueue` capable of presentation, use `get_first_presentation_queue_and_index()`. +To check if any given queue family index is capable of presentation operations, use `queue_family_index_supports_presentation(VkQueue queue)`. + +`get_preferred_queue_and_index(VkQueueFlags flags)` looks for a queue that supports the given queue flags while minimizing unsupported flags. +It does not require that the queue exclusively supports only the given queue flags, which may result in the same queue handle being returns by different queries. +Because not all Vulkan hardware has queue families for each operation category, an application needs to be careful that they didn't get the same queue handle multiple times. +While it is fine to query the same queue multiple times, it is not fine to use that queue in multiple threads at the same time, or to ignore other synchronization requirements of VkQueue's in the Vulkan API. + #### Custom queue setup @@ -222,14 +233,14 @@ for (uint32_t i = 0; i < static_cast(queue_families.size ()); i++) { if (queue_families[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) { // Find the first queue family with graphics operations supported queue_descriptions.push_back (vkb::CustomQueueDescription ( - i, queue_families[i].queueCount, + i, queue_families[i].queueCount, std::vector (queue_families[i].queueCount, 1.0f))); } } ``` ## 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 vkb::SwapchainBuilder swapchain_builder{ device }; diff --git a/src/VkBootstrap.cpp b/src/VkBootstrap.cpp index 2566c59..2efd05c 100644 --- a/src/VkBootstrap.cpp +++ b/src/VkBootstrap.cpp @@ -410,6 +410,7 @@ const char* to_string(QueueError err) { CASE_TO_STRING(QueueError, graphics_unavailable) CASE_TO_STRING(QueueError, compute_unavailable) CASE_TO_STRING(QueueError, transfer_unavailable) + CASE_TO_STRING(QueueError, queue_type_unavailable) CASE_TO_STRING(QueueError, queue_index_out_of_range) CASE_TO_STRING(QueueError, invalid_queue_family_index) default: @@ -932,6 +933,7 @@ bool supports_features(VkPhysicalDeviceFeatures supported, return true; } // clang-format on + // Finds the first queue which supports the desired operations. Returns QUEUE_INDEX_MAX_VALUE if none is found uint32_t get_first_queue_index(std::vector const& families, VkQueueFlags desired_flags) { for (uint32_t i = 0; i < static_cast(families.size()); i++) { @@ -939,21 +941,33 @@ uint32_t get_first_queue_index(std::vector const& famil } return QUEUE_INDEX_MAX_VALUE; } -// Finds the queue which is separate from the graphics queue and has the desired flag and not the -// undesired flag, but will select it if no better options are available compute support. Returns -// QUEUE_INDEX_MAX_VALUE if none is found. + +// Finds the that has the desired flag and the least number of undesired flags. +// Any queue family with invalid_flags will be ignored. +// Returns QUEUE_INDEX_MAX_VALUE if none is found. uint32_t get_separate_queue_index( - std::vector const& families, VkQueueFlags desired_flags, VkQueueFlags undesired_flags) { + std::vector const& families, VkQueueFlags desired_flags, VkQueueFlags undesired_flags, VkQueueFlags invalid_flags) { uint32_t index = QUEUE_INDEX_MAX_VALUE; + uint32_t least_undesired_flags_count = QUEUE_INDEX_MAX_VALUE; for (uint32_t i = 0; i < static_cast(families.size()); i++) { - if ((families[i].queueFlags & desired_flags) == desired_flags && ((families[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) == 0)) { - if ((families[i].queueFlags & undesired_flags) == 0) { - return i; - } else { + if ((invalid_flags != 0) && (families[i].queueFlags & invalid_flags) == invalid_flags) { + continue; + } + if ((families[i].queueFlags & desired_flags) == desired_flags) { + uint32_t undesired_flag_count = 0; + // Count the number of flags which are supported by the queue family but are undesired + for (uint32_t j = VK_QUEUE_FLAG_BITS_MAX_ENUM; j != 0; j >>= 1) { + if (families[i].queueFlags & undesired_flags & j) { + undesired_flag_count++; + } + } + if (undesired_flag_count < least_undesired_flags_count) { + least_undesired_flags_count = undesired_flag_count; index = i; } } } + return index; } @@ -961,8 +975,7 @@ uint32_t get_separate_queue_index( uint32_t get_dedicated_queue_index( std::vector const& families, VkQueueFlags desired_flags, VkQueueFlags undesired_flags) { for (uint32_t i = 0; i < static_cast(families.size()); i++) { - if ((families[i].queueFlags & desired_flags) == desired_flags && - (families[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) == 0 && (families[i].queueFlags & undesired_flags) == 0) + if ((families[i].queueFlags & desired_flags) == desired_flags && ((families[i].queueFlags & undesired_flags) == 0)) return i; } return QUEUE_INDEX_MAX_VALUE; @@ -1047,13 +1060,17 @@ PhysicalDevice::Suitable PhysicalDeviceSelector::is_device_suitable(PhysicalDevi if (criteria.required_version > pd.properties.apiVersion) return PhysicalDevice::Suitable::no; if (criteria.desired_version > pd.properties.apiVersion) suitable = PhysicalDevice::Suitable::partial; - bool dedicated_compute = detail::get_dedicated_queue_index(pd.queue_families, VK_QUEUE_COMPUTE_BIT, VK_QUEUE_TRANSFER_BIT) != - detail::QUEUE_INDEX_MAX_VALUE; - bool dedicated_transfer = detail::get_dedicated_queue_index(pd.queue_families, VK_QUEUE_TRANSFER_BIT, VK_QUEUE_COMPUTE_BIT) != - detail::QUEUE_INDEX_MAX_VALUE; - bool separate_compute = detail::get_separate_queue_index(pd.queue_families, VK_QUEUE_COMPUTE_BIT, VK_QUEUE_TRANSFER_BIT) != + bool dedicated_compute = detail::get_dedicated_queue_index(pd.queue_families, + VK_QUEUE_COMPUTE_BIT, + VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_TRANSFER_BIT) != detail::QUEUE_INDEX_MAX_VALUE; + bool dedicated_transfer = detail::get_dedicated_queue_index(pd.queue_families, + VK_QUEUE_TRANSFER_BIT, + VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT) != detail::QUEUE_INDEX_MAX_VALUE; + bool separate_compute = detail::get_separate_queue_index( + pd.queue_families, VK_QUEUE_COMPUTE_BIT, VK_QUEUE_TRANSFER_BIT, VK_QUEUE_GRAPHICS_BIT) != detail::QUEUE_INDEX_MAX_VALUE; - bool separate_transfer = detail::get_separate_queue_index(pd.queue_families, VK_QUEUE_TRANSFER_BIT, VK_QUEUE_COMPUTE_BIT) != + bool separate_transfer = detail::get_separate_queue_index( + pd.queue_families, VK_QUEUE_TRANSFER_BIT, VK_QUEUE_COMPUTE_BIT, VK_QUEUE_GRAPHICS_BIT) != detail::QUEUE_INDEX_MAX_VALUE; bool present_queue = detail::get_present_queue_index(pd.physical_device, instance_info.surface, pd.queue_families) != @@ -1356,16 +1373,20 @@ PhysicalDeviceSelector& PhysicalDeviceSelector::select_first_device_unconditiona // PhysicalDevice bool PhysicalDevice::has_dedicated_compute_queue() const { - return detail::get_dedicated_queue_index(queue_families, VK_QUEUE_COMPUTE_BIT, VK_QUEUE_TRANSFER_BIT) != detail::QUEUE_INDEX_MAX_VALUE; + return detail::get_dedicated_queue_index(queue_families, VK_QUEUE_COMPUTE_BIT, VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_TRANSFER_BIT) != + detail::QUEUE_INDEX_MAX_VALUE; } bool PhysicalDevice::has_separate_compute_queue() const { - return detail::get_separate_queue_index(queue_families, VK_QUEUE_COMPUTE_BIT, VK_QUEUE_TRANSFER_BIT) != detail::QUEUE_INDEX_MAX_VALUE; + return detail::get_separate_queue_index(queue_families, VK_QUEUE_COMPUTE_BIT, VK_QUEUE_TRANSFER_BIT, VK_QUEUE_GRAPHICS_BIT) != + detail::QUEUE_INDEX_MAX_VALUE; } bool PhysicalDevice::has_dedicated_transfer_queue() const { - return detail::get_dedicated_queue_index(queue_families, VK_QUEUE_TRANSFER_BIT, VK_QUEUE_COMPUTE_BIT) != detail::QUEUE_INDEX_MAX_VALUE; + return detail::get_dedicated_queue_index(queue_families, VK_QUEUE_TRANSFER_BIT, VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT) != + detail::QUEUE_INDEX_MAX_VALUE; } bool PhysicalDevice::has_separate_transfer_queue() const { - return detail::get_separate_queue_index(queue_families, VK_QUEUE_TRANSFER_BIT, VK_QUEUE_COMPUTE_BIT) != detail::QUEUE_INDEX_MAX_VALUE; + return detail::get_separate_queue_index(queue_families, VK_QUEUE_TRANSFER_BIT, VK_QUEUE_COMPUTE_BIT, VK_QUEUE_GRAPHICS_BIT) != + detail::QUEUE_INDEX_MAX_VALUE; } std::vector PhysicalDevice::get_queue_families() const { return queue_families; } std::vector PhysicalDevice::get_extensions() const { return extensions; } @@ -1373,6 +1394,7 @@ PhysicalDevice::operator VkPhysicalDevice() const { return this->physical_device // ---- Queues ---- // +// Legacy queue function Result Device::get_queue_index(QueueType type) const { uint32_t index = detail::QUEUE_INDEX_MAX_VALUE; switch (type) { @@ -1385,27 +1407,11 @@ Result Device::get_queue_index(QueueType type) const { if (index == detail::QUEUE_INDEX_MAX_VALUE) return Result{ QueueError::graphics_unavailable }; break; case QueueType::compute: - index = detail::get_separate_queue_index(queue_families, VK_QUEUE_COMPUTE_BIT, VK_QUEUE_TRANSFER_BIT); + index = detail::get_separate_queue_index(queue_families, VK_QUEUE_COMPUTE_BIT, VK_QUEUE_TRANSFER_BIT, VK_QUEUE_GRAPHICS_BIT); if (index == detail::QUEUE_INDEX_MAX_VALUE) return Result{ QueueError::compute_unavailable }; break; case QueueType::transfer: - index = detail::get_separate_queue_index(queue_families, VK_QUEUE_TRANSFER_BIT, VK_QUEUE_COMPUTE_BIT); - if (index == detail::QUEUE_INDEX_MAX_VALUE) return Result{ QueueError::transfer_unavailable }; - break; - default: - return Result{ QueueError::invalid_queue_family_index }; - } - return index; -} -Result Device::get_dedicated_queue_index(QueueType type) const { - uint32_t index = detail::QUEUE_INDEX_MAX_VALUE; - switch (type) { - case QueueType::compute: - index = detail::get_dedicated_queue_index(queue_families, VK_QUEUE_COMPUTE_BIT, VK_QUEUE_TRANSFER_BIT); - if (index == detail::QUEUE_INDEX_MAX_VALUE) return Result{ QueueError::compute_unavailable }; - break; - case QueueType::transfer: - index = detail::get_dedicated_queue_index(queue_families, VK_QUEUE_TRANSFER_BIT, VK_QUEUE_COMPUTE_BIT); + index = detail::get_separate_queue_index(queue_families, VK_QUEUE_TRANSFER_BIT, VK_QUEUE_COMPUTE_BIT, VK_QUEUE_GRAPHICS_BIT); if (index == detail::QUEUE_INDEX_MAX_VALUE) return Result{ QueueError::transfer_unavailable }; break; default: @@ -1414,6 +1420,25 @@ Result Device::get_dedicated_queue_index(QueueType type) const { return index; } +// Legacy queue function +Result Device::get_dedicated_queue_index(QueueType type) const { + uint32_t index = detail::QUEUE_INDEX_MAX_VALUE; + switch (type) { + case QueueType::compute: + index = detail::get_dedicated_queue_index(queue_families, VK_QUEUE_COMPUTE_BIT, VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_TRANSFER_BIT); + if (index == detail::QUEUE_INDEX_MAX_VALUE) return Result{ QueueError::compute_unavailable }; + break; + case QueueType::transfer: + index = detail::get_dedicated_queue_index(queue_families, VK_QUEUE_TRANSFER_BIT, VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT); + if (index == detail::QUEUE_INDEX_MAX_VALUE) return Result{ QueueError::transfer_unavailable }; + break; + default: + return Result{ QueueError::invalid_queue_family_index }; + } + return index; +} + +// Legacy queue function Result Device::get_queue(QueueType type) const { auto index = get_queue_index(type); if (!index.has_value()) return { index.error() }; @@ -1421,14 +1446,63 @@ Result Device::get_queue(QueueType type) const { internal_table.fp_vkGetDeviceQueue(device, index.value(), 0, &out_queue); return out_queue; } + +// Legacy queue function Result Device::get_dedicated_queue(QueueType type) const { auto index = get_dedicated_queue_index(type); if (!index.has_value()) return { index.error() }; - VkQueue out_queue; + VkQueue out_queue{}; internal_table.fp_vkGetDeviceQueue(device, index.value(), 0, &out_queue); return out_queue; } + +Result Device::get_first_queue_and_index(VkQueueFlags queue_flags) const { + uint32_t index = detail::get_first_queue_index(queue_families, queue_flags); + if (index == detail::QUEUE_INDEX_MAX_VALUE) return Result{ QueueError::queue_type_unavailable }; + + VkQueue out_queue{}; + internal_table.fp_vkGetDeviceQueue(device, index, 0, &out_queue); + return QueueAndIndex{ out_queue, index }; +} + +Result Device::get_preferred_queue_and_index(VkQueueFlags queue_flags) const { + uint32_t index = detail::get_separate_queue_index(queue_families, queue_flags, ~queue_flags, 0); + if (index == detail::QUEUE_INDEX_MAX_VALUE) return Result{ QueueError::queue_type_unavailable }; + + VkQueue out_queue{}; + internal_table.fp_vkGetDeviceQueue(device, index, 0, &out_queue); + return QueueAndIndex{ out_queue, index }; +} + +Result Device::get_first_presentation_queue_and_index(VkSurfaceKHR surface_to_check) const { + VkSurfaceKHR surface_to_use = surface; + if (surface_to_check != VK_NULL_HANDLE) { + surface_to_use = surface_to_check; + } + uint32_t index = detail::get_present_queue_index(physical_device.physical_device, surface_to_use, queue_families); + if (index == detail::QUEUE_INDEX_MAX_VALUE) return Result{ QueueError::present_unavailable }; + + VkQueue out_queue{}; + internal_table.fp_vkGetDeviceQueue(device, index, 0, &out_queue); + return QueueAndIndex{ out_queue, index }; +} + +bool Device::queue_family_index_supports_presentation(uint32_t index, VkSurfaceKHR surface_to_check) const { + VkBool32 presentSupport = 0; + VkSurfaceKHR surface_to_use = surface; + if (surface_to_check != VK_NULL_HANDLE) { + surface_to_use = surface_to_check; + } + if (surface_to_use != VK_NULL_HANDLE) { + VkResult res = detail::vulkan_functions().fp_vkGetPhysicalDeviceSurfaceSupportKHR( + physical_device, index, surface_to_use, &presentSupport); + if (res != VK_SUCCESS) return false; + } + return presentSupport == 1; +} + + // ---- Dispatch ---- // DispatchTable Device::make_table() const { return { device, fp_vkGetDeviceProcAddr }; } diff --git a/src/VkBootstrap.h b/src/VkBootstrap.h index f655d91..9a07619 100644 --- a/src/VkBootstrap.h +++ b/src/VkBootstrap.h @@ -200,6 +200,7 @@ enum class QueueError { graphics_unavailable, compute_unavailable, transfer_unavailable, + queue_type_unavailable, queue_index_out_of_range, invalid_queue_family_index }; @@ -675,13 +676,25 @@ class PhysicalDeviceSelector { }; // ---- Queue ---- // -enum class QueueType { present, graphics, compute, transfer }; + +enum class QueueType { + present, + graphics, + compute, + transfer, +}; namespace detail { // Sentinel value, used in implementation only const uint32_t QUEUE_INDEX_MAX_VALUE = 65536; } // namespace detail + +struct QueueAndIndex { + VkQueue queue = VK_NULL_HANDLE; + uint32_t index = detail::QUEUE_INDEX_MAX_VALUE; +}; + // ---- Device ---- // struct Device { @@ -693,13 +706,34 @@ struct Device { PFN_vkGetDeviceProcAddr fp_vkGetDeviceProcAddr = nullptr; uint32_t instance_version = VKB_VK_API_VERSION_1_0; - Result get_queue_index(QueueType type) const; - // Only a compute or transfer queue type is valid. All other queue types do not support a 'dedicated' queue index - Result get_dedicated_queue_index(QueueType type) const; + // Legacy function - gets the queue which supports queue_type operations using the following rules: + // If queue_type is graphics, return the first queue that supports graphics operations + // If queue_type is compute, return a queue that supports compute operations but not graphics, if one exists + // If queue_type is transfer, return a queue that supports transfer operations but not graphics, if one exists + // If queue_type is present, return the first queue that supports presentation with the VkSurfaceKHR given during physical device selection + Result get_queue_index(QueueType queue_type) const; + Result get_queue(QueueType queue_type) const; - Result get_queue(QueueType type) const; - // Only a compute or transfer queue type is valid. All other queue types do not support a 'dedicated' queue - Result get_dedicated_queue(QueueType type) const; + // Legacy function - gets the queue which supports only the queue_type operations using the following rules: + // If queue_type is compute, return a queue that supports compute operations but not graphics or transfer, if one + // exists If queue_type is transfer, return a queue that supports transfer operations but not graphics or compute, + // if one exists If queue_type is any other type, return an error code + Result get_dedicated_queue(QueueType queue_type) const; + Result get_dedicated_queue_index(QueueType queue_type) const; + + // Returns the first queue which supports the operations specified by queue_flags + Result get_first_queue_and_index(VkQueueFlags queue_flags) const; + + // Returns the queue which supports the operations specified by queue_flags and the least number of operations not specified by queue_flags + Result get_preferred_queue_and_index(VkQueueFlags queue_flags) const; + + // Get the first queue which supports presentation. Very unlikely to be unique. + // If surface_to_check is not VK_NULL_HANDLE, it will make sure the surface is compatible with the queue + // Otherwise if surface_to_check is VK_NULL_HANDLE, it will use the VkSurfaceKHR used duing physical device selection + Result get_first_presentation_queue_and_index(VkSurfaceKHR surface_to_check = VK_NULL_HANDLE) const; + + // Returns true if the given queue family index is capable of supporting presentation operations. + bool queue_family_index_supports_presentation(uint32_t index, VkSurfaceKHR surface_to_check = VK_NULL_HANDLE) const; // Return a loaded dispatch table DispatchTable make_table() const; diff --git a/tests/bootstrap_tests.cpp b/tests/bootstrap_tests.cpp index 7531540..9ec1f6f 100644 --- a/tests/bootstrap_tests.cpp +++ b/tests/bootstrap_tests.cpp @@ -334,6 +334,9 @@ TEST_CASE("Swapchain", "[VkBootstrap.bootstrap]") { auto graphics_queue_index = device.get_queue_index(vkb::QueueType::graphics).value(); auto present_queue_index = device.get_queue_index(vkb::QueueType::present).value(); + REQUIRE(device.get_first_presentation_queue_and_index().has_value()); + REQUIRE(device.queue_family_index_supports_presentation(present_queue_index)); + THEN("Swapchain can be made") { vkb::SwapchainBuilder swapchain_builder(device); auto swapchain_ret = swapchain_builder.build(); @@ -727,7 +730,7 @@ TEST_CASE("Querying Required Extension Features in 1.1", "[VkBootstrap.version]" } TEST_CASE("Querying Vulkan 1.1 and 1.2 features", "[VkBootstrap.version]") { - [[maybe_unused]] VulkanMock& mock = get_and_setup_default(); + VulkanMock& mock = get_and_setup_default(); mock.api_version = VK_API_VERSION_1_2; mock.physical_devices_details[0].properties.apiVersion = VK_API_VERSION_1_2; @@ -740,7 +743,6 @@ TEST_CASE("Querying Vulkan 1.1 and 1.2 features", "[VkBootstrap.version]") { mock.physical_devices_details[0].add_features_pNext_struct(mock_vulkan_12_features); GIVEN("A working instance") { - vkb::InstanceBuilder builder; auto instance = get_headless_instance(2); // make sure we use 1.2 SECTION("Requires a device that supports multiview and bufferDeviceAddress") { VkPhysicalDeviceVulkan11Features features_11{}; diff --git a/tests/unit_tests.cpp b/tests/unit_tests.cpp index c067328..075af1b 100644 --- a/tests/unit_tests.cpp +++ b/tests/unit_tests.cpp @@ -2,6 +2,7 @@ #include "VkBootstrap.cpp" +#include "vulkan_mock.hpp" TEST_CASE("Single Queue Device", "[UnitTests.queue_selection_logic]") { std::vector families = { VkQueueFamilyProperties{ @@ -9,9 +10,9 @@ TEST_CASE("Single Queue Device", "[UnitTests.queue_selection_logic]") { REQUIRE(0 == vkb::detail::get_first_queue_index(families, VK_QUEUE_GRAPHICS_BIT)); REQUIRE(vkb::detail::QUEUE_INDEX_MAX_VALUE == - vkb::detail::get_separate_queue_index(families, VK_QUEUE_COMPUTE_BIT, VK_QUEUE_TRANSFER_BIT)); + vkb::detail::get_separate_queue_index(families, VK_QUEUE_COMPUTE_BIT, VK_QUEUE_TRANSFER_BIT, VK_QUEUE_GRAPHICS_BIT)); REQUIRE(vkb::detail::QUEUE_INDEX_MAX_VALUE == - vkb::detail::get_separate_queue_index(families, VK_QUEUE_TRANSFER_BIT, VK_QUEUE_COMPUTE_BIT)); + vkb::detail::get_separate_queue_index(families, VK_QUEUE_TRANSFER_BIT, VK_QUEUE_COMPUTE_BIT, VK_QUEUE_GRAPHICS_BIT)); REQUIRE(vkb::detail::QUEUE_INDEX_MAX_VALUE == vkb::detail::get_dedicated_queue_index(families, VK_QUEUE_COMPUTE_BIT, VK_QUEUE_TRANSFER_BIT)); REQUIRE(vkb::detail::QUEUE_INDEX_MAX_VALUE == @@ -22,14 +23,16 @@ TEST_CASE("Dedicated Compute Queue, Separate Transfer", "[UnitTests.queue_select SECTION("Dedicated Queue First") { std::vector families = { VkQueueFamilyProperties{ - VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_GRAPHICS_BIT, 1, 0, VkExtent3D{ 1, 1, 1 } }, + VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT, 1, 0, VkExtent3D{ 1, 1, 1 } }, VkQueueFamilyProperties{ VK_QUEUE_COMPUTE_BIT, 1, 0, VkExtent3D{ 1, 1, 1 } }, VkQueueFamilyProperties{ VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT, 1, 0, VkExtent3D{ 1, 1, 1 } } }; REQUIRE(0 == vkb::detail::get_first_queue_index(families, VK_QUEUE_GRAPHICS_BIT)); - REQUIRE(1 == vkb::detail::get_separate_queue_index(families, VK_QUEUE_COMPUTE_BIT, VK_QUEUE_TRANSFER_BIT)); - REQUIRE(2 == vkb::detail::get_separate_queue_index(families, VK_QUEUE_TRANSFER_BIT, VK_QUEUE_COMPUTE_BIT)); + REQUIRE(1 == vkb::detail::get_separate_queue_index( + families, VK_QUEUE_COMPUTE_BIT, VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_TRANSFER_BIT, 0)); + REQUIRE(2 == vkb::detail::get_separate_queue_index( + families, VK_QUEUE_TRANSFER_BIT, VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT, 0)); REQUIRE(1 == vkb::detail::get_dedicated_queue_index(families, VK_QUEUE_COMPUTE_BIT, VK_QUEUE_TRANSFER_BIT)); REQUIRE(vkb::detail::QUEUE_INDEX_MAX_VALUE == vkb::detail::get_dedicated_queue_index(families, VK_QUEUE_TRANSFER_BIT, VK_QUEUE_COMPUTE_BIT)); @@ -37,14 +40,16 @@ TEST_CASE("Dedicated Compute Queue, Separate Transfer", "[UnitTests.queue_select SECTION("Dedicated Queue Last") { std::vector families = { VkQueueFamilyProperties{ - VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_GRAPHICS_BIT, 1, 0, VkExtent3D{ 1, 1, 1 } }, + VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT, 1, 0, VkExtent3D{ 1, 1, 1 } }, VkQueueFamilyProperties{ VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT, 1, 0, VkExtent3D{ 1, 1, 1 } }, VkQueueFamilyProperties{ VK_QUEUE_COMPUTE_BIT, 1, 0, VkExtent3D{ 1, 1, 1 } } }; REQUIRE(0 == vkb::detail::get_first_queue_index(families, VK_QUEUE_GRAPHICS_BIT)); - REQUIRE(2 == vkb::detail::get_separate_queue_index(families, VK_QUEUE_COMPUTE_BIT, VK_QUEUE_TRANSFER_BIT)); - REQUIRE(1 == vkb::detail::get_separate_queue_index(families, VK_QUEUE_TRANSFER_BIT, VK_QUEUE_COMPUTE_BIT)); + REQUIRE(2 == vkb::detail::get_separate_queue_index( + families, VK_QUEUE_COMPUTE_BIT, VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_TRANSFER_BIT, 0)); + REQUIRE(1 == vkb::detail::get_separate_queue_index( + families, VK_QUEUE_TRANSFER_BIT, VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT, 0)); REQUIRE(2 == vkb::detail::get_dedicated_queue_index(families, VK_QUEUE_COMPUTE_BIT, VK_QUEUE_TRANSFER_BIT)); REQUIRE(vkb::detail::QUEUE_INDEX_MAX_VALUE == vkb::detail::get_dedicated_queue_index(families, VK_QUEUE_TRANSFER_BIT, VK_QUEUE_COMPUTE_BIT)); @@ -55,14 +60,16 @@ TEST_CASE("Dedicated Transfer Queue, Separate Compute", "[UnitTests.queue_select SECTION("Dedicated Queue First") { std::vector families = { VkQueueFamilyProperties{ - VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_GRAPHICS_BIT, 1, 0, VkExtent3D{ 1, 1, 1 } }, + VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT, 1, 0, VkExtent3D{ 1, 1, 1 } }, VkQueueFamilyProperties{ VK_QUEUE_TRANSFER_BIT, 1, 0, VkExtent3D{ 1, 1, 1 } }, VkQueueFamilyProperties{ VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT, 1, 0, VkExtent3D{ 1, 1, 1 } } }; REQUIRE(0 == vkb::detail::get_first_queue_index(families, VK_QUEUE_GRAPHICS_BIT)); - REQUIRE(2 == vkb::detail::get_separate_queue_index(families, VK_QUEUE_COMPUTE_BIT, VK_QUEUE_TRANSFER_BIT)); - REQUIRE(1 == vkb::detail::get_separate_queue_index(families, VK_QUEUE_TRANSFER_BIT, VK_QUEUE_COMPUTE_BIT)); + REQUIRE(2 == vkb::detail::get_separate_queue_index( + families, VK_QUEUE_COMPUTE_BIT, VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_TRANSFER_BIT, 0)); + REQUIRE(1 == vkb::detail::get_separate_queue_index( + families, VK_QUEUE_TRANSFER_BIT, VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT, 0)); REQUIRE(vkb::detail::QUEUE_INDEX_MAX_VALUE == vkb::detail::get_dedicated_queue_index(families, VK_QUEUE_COMPUTE_BIT, VK_QUEUE_TRANSFER_BIT)); REQUIRE(1 == vkb::detail::get_dedicated_queue_index(families, VK_QUEUE_TRANSFER_BIT, VK_QUEUE_COMPUTE_BIT)); @@ -70,16 +77,208 @@ TEST_CASE("Dedicated Transfer Queue, Separate Compute", "[UnitTests.queue_select SECTION("Dedicated Queue Last") { std::vector families = { VkQueueFamilyProperties{ - VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_GRAPHICS_BIT, 1, 0, VkExtent3D{ 1, 1, 1 } }, + VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT, 1, 0, VkExtent3D{ 1, 1, 1 } }, VkQueueFamilyProperties{ VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT, 1, 0, VkExtent3D{ 1, 1, 1 } }, VkQueueFamilyProperties{ VK_QUEUE_TRANSFER_BIT, 1, 0, VkExtent3D{ 1, 1, 1 } } }; REQUIRE(0 == vkb::detail::get_first_queue_index(families, VK_QUEUE_GRAPHICS_BIT)); - REQUIRE(1 == vkb::detail::get_separate_queue_index(families, VK_QUEUE_COMPUTE_BIT, VK_QUEUE_TRANSFER_BIT)); - REQUIRE(2 == vkb::detail::get_separate_queue_index(families, VK_QUEUE_TRANSFER_BIT, VK_QUEUE_COMPUTE_BIT)); + REQUIRE(1 == vkb::detail::get_separate_queue_index( + families, VK_QUEUE_COMPUTE_BIT, VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_TRANSFER_BIT, 0)); + REQUIRE(2 == vkb::detail::get_separate_queue_index( + families, VK_QUEUE_TRANSFER_BIT, VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT, 0)); REQUIRE(vkb::detail::QUEUE_INDEX_MAX_VALUE == vkb::detail::get_dedicated_queue_index(families, VK_QUEUE_COMPUTE_BIT, VK_QUEUE_TRANSFER_BIT)); REQUIRE(2 == vkb::detail::get_dedicated_queue_index(families, VK_QUEUE_TRANSFER_BIT, VK_QUEUE_COMPUTE_BIT)); } } + +vkb::Device setup_mock_for_queue_selection(std::vector const& queue_families) { + VulkanMock& mock = get_vulkan_mock(); + VulkanMock::PhysicalDeviceDetails physical_device_details{}; + physical_device_details.properties.apiVersion = VK_API_VERSION_1_0; + physical_device_details.queue_family_properties = queue_families; + mock.add_physical_device(std::move(physical_device_details)); + + return vkb::DeviceBuilder{ + vkb::PhysicalDeviceSelector{ vkb::InstanceBuilder{}.set_headless().build().value() }.select().value() + } + .build() + .value(); +} + +TEST_CASE("Single Uber Queue", "[UnitTests.get_first_queue_and_index]") { + vkb::Device d = setup_mock_for_queue_selection( + { VkQueueFamilyProperties{ VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT, 1 } }); + + vkb::QueueAndIndex q0 = d.get_first_queue_and_index(VK_QUEUE_GRAPHICS_BIT).value(); + CHECK((q0.index == 0 && q0.queue != nullptr)); + + vkb::QueueAndIndex q1 = d.get_first_queue_and_index(VK_QUEUE_COMPUTE_BIT).value(); + CHECK((q1.index == 0 && q1.queue != nullptr)); + + vkb::QueueAndIndex q2 = d.get_first_queue_and_index(VK_QUEUE_TRANSFER_BIT).value(); + CHECK((q2.index == 0 && q2.queue != nullptr)); + + vkb::QueueAndIndex q3 = + d.get_first_queue_and_index(VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT).value(); + CHECK((q3.index == 0 && q3.queue != nullptr)); + + CHECK(d.get_first_queue_and_index(VK_QUEUE_PROTECTED_BIT).matches_error(vkb::QueueError::queue_type_unavailable)); + CHECK(d.get_first_queue_and_index(VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT | VK_QUEUE_PROTECTED_BIT) + .matches_error(vkb::QueueError::queue_type_unavailable)); +} + +TEST_CASE("Uber, Compute + Transfer", "[UnitTests.get_first_queue_and_index]") { + vkb::Device d = setup_mock_for_queue_selection( + { VkQueueFamilyProperties{ VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT, 1 }, + VkQueueFamilyProperties{ VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT, 1 } }); + + vkb::QueueAndIndex q0 = d.get_first_queue_and_index(VK_QUEUE_GRAPHICS_BIT).value(); + CHECK((q0.index == 0 && q0.queue != nullptr)); + + vkb::QueueAndIndex q1 = d.get_first_queue_and_index(VK_QUEUE_COMPUTE_BIT).value(); + CHECK((q1.index == 0 && q1.queue != nullptr)); + + vkb::QueueAndIndex q2 = d.get_first_queue_and_index(VK_QUEUE_TRANSFER_BIT).value(); + CHECK((q2.index == 0 && q2.queue != nullptr)); +} + +TEST_CASE("Single Uber Queue", "[UnitTests.get_preferred_queue_and_index]") { + vkb::Device d = setup_mock_for_queue_selection( + { VkQueueFamilyProperties{ VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT, 1 } }); + + vkb::QueueAndIndex q0 = d.get_preferred_queue_and_index(VK_QUEUE_GRAPHICS_BIT).value(); + CHECK((q0.index == 0 && q0.queue != nullptr)); + + vkb::QueueAndIndex q1 = d.get_preferred_queue_and_index(VK_QUEUE_COMPUTE_BIT).value(); + CHECK((q1.index == 0 && q1.queue != nullptr)); + + vkb::QueueAndIndex q2 = d.get_preferred_queue_and_index(VK_QUEUE_TRANSFER_BIT).value(); + CHECK((q2.index == 0 && q2.queue != nullptr)); + + vkb::QueueAndIndex q3 = + d.get_preferred_queue_and_index(VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT).value(); + CHECK((q3.index == 0 && q3.queue != nullptr)); + + CHECK(d.get_preferred_queue_and_index(VK_QUEUE_PROTECTED_BIT).matches_error(vkb::QueueError::queue_type_unavailable)); +} + +TEST_CASE("Uber, Compute + Transfer", "[UnitTests.get_preferred_queue_and_index]") { + vkb::Device d = setup_mock_for_queue_selection( + { VkQueueFamilyProperties{ VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT, 1 }, + VkQueueFamilyProperties{ VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT, 1 } }); + + vkb::QueueAndIndex q0 = d.get_preferred_queue_and_index(VK_QUEUE_GRAPHICS_BIT).value(); + CHECK((q0.index == 0 && q0.queue != nullptr)); + + vkb::QueueAndIndex q1 = d.get_preferred_queue_and_index(VK_QUEUE_COMPUTE_BIT).value(); + CHECK((q1.index == 1 && q1.queue != nullptr)); + + vkb::QueueAndIndex q2 = d.get_preferred_queue_and_index(VK_QUEUE_TRANSFER_BIT).value(); + CHECK((q2.index == 1 && q2.queue != nullptr)); + + vkb::QueueAndIndex q3 = + d.get_preferred_queue_and_index(VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT).value(); + CHECK((q3.index == 0 && q3.queue != nullptr)); + + CHECK(d.get_preferred_queue_and_index(VK_QUEUE_PROTECTED_BIT).matches_error(vkb::QueueError::queue_type_unavailable)); + CHECK(d.get_preferred_queue_and_index(VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT | VK_QUEUE_PROTECTED_BIT) + .matches_error(vkb::QueueError::queue_type_unavailable)); +} + +TEST_CASE("Uber, Compute + Transfer, Compute", "[UnitTests.get_preferred_queue_and_index]") { + vkb::Device d = setup_mock_for_queue_selection( + { VkQueueFamilyProperties{ VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT, 1 }, + VkQueueFamilyProperties{ VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT, 1 }, + VkQueueFamilyProperties{ VK_QUEUE_COMPUTE_BIT, 1 } }); + + vkb::QueueAndIndex q0 = d.get_preferred_queue_and_index(VK_QUEUE_GRAPHICS_BIT).value(); + CHECK((q0.index == 0 && q0.queue != nullptr)); + + vkb::QueueAndIndex q1 = d.get_preferred_queue_and_index(VK_QUEUE_COMPUTE_BIT).value(); + CHECK((q1.index == 2 && q1.queue != nullptr)); + + vkb::QueueAndIndex q2 = d.get_preferred_queue_and_index(VK_QUEUE_TRANSFER_BIT).value(); + CHECK((q2.index == 1 && q2.queue != nullptr)); + + vkb::QueueAndIndex q3 = + d.get_preferred_queue_and_index(VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT).value(); + CHECK((q3.index == 0 && q3.queue != nullptr)); + + CHECK(d.get_preferred_queue_and_index(VK_QUEUE_PROTECTED_BIT).matches_error(vkb::QueueError::queue_type_unavailable)); + CHECK(d.get_preferred_queue_and_index(VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT | VK_QUEUE_PROTECTED_BIT) + .matches_error(vkb::QueueError::queue_type_unavailable)); +} + +TEST_CASE("Uber, Compute, Compute + Transfer", "[UnitTests.get_preferred_queue_and_index]") { + vkb::Device d = setup_mock_for_queue_selection( + { VkQueueFamilyProperties{ VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT, 1 }, + VkQueueFamilyProperties{ VK_QUEUE_COMPUTE_BIT, 1 }, + VkQueueFamilyProperties{ VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT, 1 } }); + + vkb::QueueAndIndex q0 = d.get_preferred_queue_and_index(VK_QUEUE_GRAPHICS_BIT).value(); + CHECK((q0.index == 0 && q0.queue != nullptr)); + + vkb::QueueAndIndex q1 = d.get_preferred_queue_and_index(VK_QUEUE_COMPUTE_BIT).value(); + CHECK((q1.index == 1 && q1.queue != nullptr)); + + vkb::QueueAndIndex q2 = d.get_preferred_queue_and_index(VK_QUEUE_TRANSFER_BIT).value(); + CHECK((q2.index == 2 && q2.queue != nullptr)); + + vkb::QueueAndIndex q3 = + d.get_preferred_queue_and_index(VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT).value(); + CHECK((q3.index == 0 && q3.queue != nullptr)); + + CHECK(d.get_preferred_queue_and_index(VK_QUEUE_PROTECTED_BIT).matches_error(vkb::QueueError::queue_type_unavailable)); + CHECK(d.get_preferred_queue_and_index(VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT | VK_QUEUE_PROTECTED_BIT) + .matches_error(vkb::QueueError::queue_type_unavailable)); +} + +TEST_CASE("Uber, Transfer, Compute + Transfer", "[UnitTests.get_preferred_queue_and_index]") { + vkb::Device d = setup_mock_for_queue_selection( + { VkQueueFamilyProperties{ VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT, 1 }, + VkQueueFamilyProperties{ VK_QUEUE_TRANSFER_BIT, 1 }, + VkQueueFamilyProperties{ VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT, 1 } }); + + vkb::QueueAndIndex q0 = d.get_preferred_queue_and_index(VK_QUEUE_GRAPHICS_BIT).value(); + CHECK((q0.index == 0 && q0.queue != nullptr)); + + vkb::QueueAndIndex q1 = d.get_preferred_queue_and_index(VK_QUEUE_COMPUTE_BIT).value(); + CHECK((q1.index == 2 && q1.queue != nullptr)); + + vkb::QueueAndIndex q2 = d.get_preferred_queue_and_index(VK_QUEUE_TRANSFER_BIT).value(); + CHECK((q2.index == 1 && q2.queue != nullptr)); + + vkb::QueueAndIndex q3 = + d.get_preferred_queue_and_index(VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT).value(); + CHECK((q3.index == 0 && q3.queue != nullptr)); + + CHECK(d.get_preferred_queue_and_index(VK_QUEUE_PROTECTED_BIT).matches_error(vkb::QueueError::queue_type_unavailable)); + CHECK(d.get_preferred_queue_and_index(VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT | VK_QUEUE_PROTECTED_BIT) + .matches_error(vkb::QueueError::queue_type_unavailable)); +} + +TEST_CASE("Uber, Compute + Transfer, Transfer", "[UnitTests.get_preferred_queue_and_index]") { + vkb::Device d = setup_mock_for_queue_selection( + { VkQueueFamilyProperties{ VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT, 1 }, + VkQueueFamilyProperties{ VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT, 1 }, + VkQueueFamilyProperties{ VK_QUEUE_TRANSFER_BIT, 1 } }); + + vkb::QueueAndIndex q0 = d.get_preferred_queue_and_index(VK_QUEUE_GRAPHICS_BIT).value(); + CHECK((q0.index == 0 && q0.queue != nullptr)); + + vkb::QueueAndIndex q1 = d.get_preferred_queue_and_index(VK_QUEUE_COMPUTE_BIT).value(); + CHECK((q1.index == 1 && q1.queue != nullptr)); + + vkb::QueueAndIndex q2 = d.get_preferred_queue_and_index(VK_QUEUE_TRANSFER_BIT).value(); + CHECK((q2.index == 2 && q2.queue != nullptr)); + + vkb::QueueAndIndex q3 = + d.get_preferred_queue_and_index(VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT).value(); + CHECK((q3.index == 0 && q3.queue != nullptr)); + + CHECK(d.get_preferred_queue_and_index(VK_QUEUE_PROTECTED_BIT).matches_error(vkb::QueueError::queue_type_unavailable)); + CHECK(d.get_preferred_queue_and_index(VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT | VK_QUEUE_PROTECTED_BIT) + .matches_error(vkb::QueueError::queue_type_unavailable)); +} diff --git a/tests/vulkan_mock.cpp b/tests/vulkan_mock.cpp index 98b92ea..30b8dcd 100644 --- a/tests/vulkan_mock.cpp +++ b/tests/vulkan_mock.cpp @@ -296,7 +296,9 @@ VKAPI_ATTR VkResult VKAPI_CALL shim_vkGetPhysicalDeviceSurfaceSupportKHR( } } } - if (surface && pSupported) *pSupported = true; + if (surface && pSupported) { + *pSupported = true; + } return VK_SUCCESS; } VKAPI_ATTR VkResult VKAPI_CALL shim_vkGetPhysicalDeviceSurfaceFormatsKHR(