Skip to content

Commit

Permalink
Add new Queue query API
Browse files Browse the repository at this point in the history
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 'best' 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
  • Loading branch information
charles-lunarg committed Oct 17, 2023
1 parent be0df09 commit 0b3b152
Show file tree
Hide file tree
Showing 4 changed files with 338 additions and 49 deletions.
110 changes: 85 additions & 25 deletions src/VkBootstrap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -932,37 +933,49 @@ 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<VkQueueFamilyProperties> const& families, VkQueueFlags desired_flags) {
for (uint32_t i = 0; i < static_cast<uint32_t>(families.size()); i++) {
if ((families[i].queueFlags & desired_flags) == desired_flags) return i;
}
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<VkQueueFamilyProperties> const& families, VkQueueFlags desired_flags, VkQueueFlags undesired_flags) {
std::vector<VkQueueFamilyProperties> 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<uint32_t>(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;
}

// finds the first queue which supports only the desired flag (not graphics or transfer). Returns QUEUE_INDEX_MAX_VALUE if none is found.
uint32_t get_dedicated_queue_index(
std::vector<VkQueueFamilyProperties> const& families, VkQueueFlags desired_flags, VkQueueFlags undesired_flags) {
for (uint32_t i = 0; i < static_cast<uint32_t>(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;
Expand Down Expand Up @@ -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) !=
Expand Down Expand Up @@ -1356,23 +1373,28 @@ 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<VkQueueFamilyProperties> PhysicalDevice::get_queue_families() const { return queue_families; }
std::vector<std::string> PhysicalDevice::get_extensions() const { return extensions; }
PhysicalDevice::operator VkPhysicalDevice() const { return this->physical_device; }

// ---- Queues ---- //

// Legacy queue function
Result<uint32_t> Device::get_queue_index(QueueType type) const {
uint32_t index = detail::QUEUE_INDEX_MAX_VALUE;
switch (type) {
Expand All @@ -1385,27 +1407,29 @@ Result<uint32_t> Device::get_queue_index(QueueType type) const {
if (index == detail::QUEUE_INDEX_MAX_VALUE) return Result<uint32_t>{ 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<uint32_t>{ QueueError::compute_unavailable };
break;
case QueueType::transfer:
index = detail::get_separate_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<uint32_t>{ QueueError::transfer_unavailable };
break;
default:
return Result<uint32_t>{ QueueError::invalid_queue_family_index };
}
return index;
}

// Legacy queue function
Result<uint32_t> 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);
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<uint32_t>{ 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_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<uint32_t>{ QueueError::transfer_unavailable };
break;
default:
Expand All @@ -1414,21 +1438,57 @@ Result<uint32_t> Device::get_dedicated_queue_index(QueueType type) const {
return index;
}

// Legacy queue function
Result<VkQueue> Device::get_queue(QueueType type) const {
auto index = get_queue_index(type);
if (!index.has_value()) return { index.error() };
VkQueue out_queue;
internal_table.fp_vkGetDeviceQueue(device, index.value(), 0, &out_queue);
return out_queue;
}

// Legacy queue function
Result<VkQueue> 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<QueueAndIndex> 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<QueueAndIndex>{ QueueError::queue_type_unavailable };

VkQueue out_queue{};
internal_table.fp_vkGetDeviceQueue(device, index, 0, &out_queue);
return QueueAndIndex{ out_queue, index };
}

Result<QueueAndIndex> 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<QueueAndIndex>{ QueueError::queue_type_unavailable };

VkQueue out_queue{};
internal_table.fp_vkGetDeviceQueue(device, index, 0, &out_queue);
return QueueAndIndex{ out_queue, index };
}

Result<QueueAndIndex> 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<QueueAndIndex>{ QueueError::present_unavailable };

VkQueue out_queue{};
internal_table.fp_vkGetDeviceQueue(device, index, 0, &out_queue);
return QueueAndIndex{ out_queue, index };
}


// ---- Dispatch ---- //

DispatchTable Device::make_table() const { return { device, fp_vkGetDeviceProcAddr }; }
Expand Down
47 changes: 39 additions & 8 deletions src/VkBootstrap.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
};
Expand Down Expand Up @@ -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 {
Expand All @@ -693,13 +706,31 @@ struct Device {
PFN_vkGetDeviceProcAddr fp_vkGetDeviceProcAddr = nullptr;
uint32_t instance_version = VKB_VK_API_VERSION_1_0;

Result<uint32_t> 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<uint32_t> get_dedicated_queue_index(QueueType type) const;

Result<VkQueue> 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<VkQueue> get_dedicated_queue(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<uint32_t> get_queue_index(QueueType queue_type) const;
Result<VkQueue> get_queue(QueueType queue_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<VkQueue> get_dedicated_queue(QueueType queue_type) const;
Result<uint32_t> get_dedicated_queue_index(QueueType queue_type) const;

// Returns the first queue which supports the operations specified by queue_flags
Result<QueueAndIndex> 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<QueueAndIndex> get_preferred_queue_and_index(VkQueueFlags queue_flags) const;

// Get the first queue which supports presentation
// 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<QueueAndIndex> get_first_presentation_queue_and_index(VkSurfaceKHR surface_to_check = VK_NULL_HANDLE) const;

// Return a loaded dispatch table
DispatchTable make_table() const;
Expand Down
3 changes: 1 addition & 2 deletions tests/bootstrap_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -727,7 +727,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;

Expand All @@ -740,7 +740,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{};
Expand Down
Loading

0 comments on commit 0b3b152

Please sign in to comment.