Add enable_features_if_present to PhysicalDevice

Allows users to enable features if they are present, getting back a bool
telling them whether the feature is supported and will be enabled on the
device.

Also:
* Removes redundant VkPhysicalDeviceFeatures2 struct in vkb::PhysicalDevice.
* Adds test copying of details when creating a VkDevice so that test can check
what features were actually enabled on the device.
* Creates GenericFeatureChain struct for managing pNext chains.
* Allow multiple calls to set_require_features by combining the fields
This commit is contained in:
Charles Giessen 2024-04-18 14:55:35 -05:00 committed by Charles Giessen
parent a78a7f38da
commit c9d94287a5
6 changed files with 435 additions and 72 deletions

View File

@ -47,6 +47,54 @@ bool GenericFeaturesPNextNode::match(GenericFeaturesPNextNode const& requested,
return true; return true;
} }
void GenericFeaturesPNextNode::combine(GenericFeaturesPNextNode const& right) noexcept {
assert(sType == right.sType && "Non-matching sTypes in features nodes!");
for (uint32_t i = 0; i < GenericFeaturesPNextNode::field_capacity; i++) {
fields[i] = fields[i] || right.fields[i];
}
}
bool GenericFeatureChain::match(GenericFeatureChain const& extension_requested) const noexcept {
// Should only be false if extension_supported was unable to be filled out, due to the
// physical device not supporting vkGetPhysicalDeviceFeatures2 in any capacity.
if (extension_requested.nodes.size() != nodes.size()) {
return false;
}
for (size_t i = 0; i < nodes.size() && i < nodes.size(); ++i) {
if (!GenericFeaturesPNextNode::match(extension_requested.nodes[i], nodes[i])) return false;
}
return true;
}
void GenericFeatureChain::chain_up(VkPhysicalDeviceFeatures2& feats2) noexcept {
detail::GenericFeaturesPNextNode* prev = nullptr;
for (auto& extension : nodes) {
if (prev != nullptr) {
prev->pNext = &extension;
}
prev = &extension;
}
feats2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
feats2.pNext = !nodes.empty() ? &nodes.at(0) : nullptr;
}
void GenericFeatureChain::combine(GenericFeatureChain const& right) noexcept {
for (const auto& right_node : right.nodes) {
bool already_contained = false;
for (auto& left_node : nodes) {
if (left_node.sType == right_node.sType) {
left_node.combine(right_node);
already_contained = true;
}
}
if (!already_contained) {
nodes.push_back(right_node);
}
}
}
class VulkanFunctions { class VulkanFunctions {
private: private:
std::mutex init_mutex; std::mutex init_mutex;
@ -863,10 +911,68 @@ std::vector<std::string> check_device_extension_support(
} }
// clang-format off // clang-format off
void combine_features(VkPhysicalDeviceFeatures& dest, VkPhysicalDeviceFeatures src){
dest.robustBufferAccess = dest.robustBufferAccess || src.robustBufferAccess;
dest.fullDrawIndexUint32 = dest.fullDrawIndexUint32 || src.fullDrawIndexUint32;
dest.imageCubeArray = dest.imageCubeArray || src.imageCubeArray;
dest.independentBlend = dest.independentBlend || src.independentBlend;
dest.geometryShader = dest.geometryShader || src.geometryShader;
dest.tessellationShader = dest.tessellationShader || src.tessellationShader;
dest.sampleRateShading = dest.sampleRateShading || src.sampleRateShading;
dest.dualSrcBlend = dest.dualSrcBlend || src.dualSrcBlend;
dest.logicOp = dest.logicOp || src.logicOp;
dest.multiDrawIndirect = dest.multiDrawIndirect || src.multiDrawIndirect;
dest.drawIndirectFirstInstance = dest.drawIndirectFirstInstance || src.drawIndirectFirstInstance;
dest.depthClamp = dest.depthClamp || src.depthClamp;
dest.depthBiasClamp = dest.depthBiasClamp || src.depthBiasClamp;
dest.fillModeNonSolid = dest.fillModeNonSolid || src.fillModeNonSolid;
dest.depthBounds = dest.depthBounds || src.depthBounds;
dest.wideLines = dest.wideLines || src.wideLines;
dest.largePoints = dest.largePoints || src.largePoints;
dest.alphaToOne = dest.alphaToOne || src.alphaToOne;
dest.multiViewport = dest.multiViewport || src.multiViewport;
dest.samplerAnisotropy = dest.samplerAnisotropy || src.samplerAnisotropy;
dest.textureCompressionETC2 = dest.textureCompressionETC2 || src.textureCompressionETC2;
dest.textureCompressionASTC_LDR = dest.textureCompressionASTC_LDR || src.textureCompressionASTC_LDR;
dest.textureCompressionBC = dest.textureCompressionBC || src.textureCompressionBC;
dest.occlusionQueryPrecise = dest.occlusionQueryPrecise || src.occlusionQueryPrecise;
dest.pipelineStatisticsQuery = dest.pipelineStatisticsQuery || src.pipelineStatisticsQuery;
dest.vertexPipelineStoresAndAtomics = dest.vertexPipelineStoresAndAtomics || src.vertexPipelineStoresAndAtomics;
dest.fragmentStoresAndAtomics = dest.fragmentStoresAndAtomics || src.fragmentStoresAndAtomics;
dest.shaderTessellationAndGeometryPointSize = dest.shaderTessellationAndGeometryPointSize || src.shaderTessellationAndGeometryPointSize;
dest.shaderImageGatherExtended = dest.shaderImageGatherExtended || src.shaderImageGatherExtended;
dest.shaderStorageImageExtendedFormats = dest.shaderStorageImageExtendedFormats || src.shaderStorageImageExtendedFormats;
dest.shaderStorageImageMultisample = dest.shaderStorageImageMultisample || src.shaderStorageImageMultisample;
dest.shaderStorageImageReadWithoutFormat = dest.shaderStorageImageReadWithoutFormat || src.shaderStorageImageReadWithoutFormat;
dest.shaderStorageImageWriteWithoutFormat = dest.shaderStorageImageWriteWithoutFormat || src.shaderStorageImageWriteWithoutFormat;
dest.shaderUniformBufferArrayDynamicIndexing = dest.shaderUniformBufferArrayDynamicIndexing || src.shaderUniformBufferArrayDynamicIndexing;
dest.shaderSampledImageArrayDynamicIndexing = dest.shaderSampledImageArrayDynamicIndexing || src.shaderSampledImageArrayDynamicIndexing;
dest.shaderStorageBufferArrayDynamicIndexing = dest.shaderStorageBufferArrayDynamicIndexing || src.shaderStorageBufferArrayDynamicIndexing;
dest.shaderStorageImageArrayDynamicIndexing = dest.shaderStorageImageArrayDynamicIndexing || src.shaderStorageImageArrayDynamicIndexing;
dest.shaderClipDistance = dest.shaderClipDistance || src.shaderClipDistance;
dest.shaderCullDistance = dest.shaderCullDistance || src.shaderCullDistance;
dest.shaderFloat64 = dest.shaderFloat64 || src.shaderFloat64;
dest.shaderInt64 = dest.shaderInt64 || src.shaderInt64;
dest.shaderInt16 = dest.shaderInt16 || src.shaderInt16;
dest.shaderResourceResidency = dest.shaderResourceResidency || src.shaderResourceResidency;
dest.shaderResourceMinLod = dest.shaderResourceMinLod || src.shaderResourceMinLod;
dest.sparseBinding = dest.sparseBinding || src.sparseBinding;
dest.sparseResidencyBuffer = dest.sparseResidencyBuffer || src.sparseResidencyBuffer;
dest.sparseResidencyImage2D = dest.sparseResidencyImage2D || src.sparseResidencyImage2D;
dest.sparseResidencyImage3D = dest.sparseResidencyImage3D || src.sparseResidencyImage3D;
dest.sparseResidency2Samples = dest.sparseResidency2Samples || src.sparseResidency2Samples;
dest.sparseResidency4Samples = dest.sparseResidency4Samples || src.sparseResidency4Samples;
dest.sparseResidency8Samples = dest.sparseResidency8Samples || src.sparseResidency8Samples;
dest.sparseResidency16Samples = dest.sparseResidency16Samples || src.sparseResidency16Samples;
dest.sparseResidencyAliased = dest.sparseResidencyAliased || src.sparseResidencyAliased;
dest.variableMultisampleRate = dest.variableMultisampleRate || src.variableMultisampleRate;
dest.inheritedQueries = dest.inheritedQueries || src.inheritedQueries;
}
bool supports_features(VkPhysicalDeviceFeatures supported, bool supports_features(VkPhysicalDeviceFeatures supported,
VkPhysicalDeviceFeatures requested, VkPhysicalDeviceFeatures requested,
std::vector<GenericFeaturesPNextNode> const& extension_supported, GenericFeatureChain const& extension_supported,
std::vector<GenericFeaturesPNextNode> const& extension_requested) { GenericFeatureChain const& extension_requested) {
if (requested.robustBufferAccess && !supported.robustBufferAccess) return false; if (requested.robustBufferAccess && !supported.robustBufferAccess) return false;
if (requested.fullDrawIndexUint32 && !supported.fullDrawIndexUint32) return false; if (requested.fullDrawIndexUint32 && !supported.fullDrawIndexUint32) return false;
if (requested.imageCubeArray && !supported.imageCubeArray) return false; if (requested.imageCubeArray && !supported.imageCubeArray) return false;
@ -923,18 +1029,7 @@ bool supports_features(VkPhysicalDeviceFeatures supported,
if (requested.variableMultisampleRate && !supported.variableMultisampleRate) return false; if (requested.variableMultisampleRate && !supported.variableMultisampleRate) return false;
if (requested.inheritedQueries && !supported.inheritedQueries) return false; if (requested.inheritedQueries && !supported.inheritedQueries) return false;
// Should only be false if extension_supported was unable to be filled out, due to the return extension_supported.match(extension_requested);
// physical device not supporting vkGetPhysicalDeviceFeatures2 in any capacity.
if (extension_requested.size() != extension_supported.size()) {
return false;
}
for(size_t i = 0; i < extension_requested.size() && i < extension_supported.size(); ++i) {
auto res = GenericFeaturesPNextNode::match(extension_requested[i], extension_supported[i]);
if(!res) return false;
}
return true;
} }
// clang-format on // clang-format on
// Finds the first queue which supports the desired operations. Returns QUEUE_INDEX_MAX_VALUE if none is found // Finds the first queue which supports the desired operations. Returns QUEUE_INDEX_MAX_VALUE if none is found
@ -988,8 +1083,8 @@ uint32_t get_present_queue_index(
} }
} // namespace detail } // namespace detail
PhysicalDevice PhysicalDeviceSelector::populate_device_details(VkPhysicalDevice vk_phys_device, PhysicalDevice PhysicalDeviceSelector::populate_device_details(
std::vector<detail::GenericFeaturesPNextNode> const& src_extended_features_chain) const { VkPhysicalDevice vk_phys_device, detail::GenericFeatureChain const& src_extended_features_chain) const {
PhysicalDevice physical_device{}; PhysicalDevice physical_device{};
physical_device.physical_device = vk_phys_device; physical_device.physical_device = vk_phys_device;
physical_device.surface = instance_info.surface; physical_device.surface = instance_info.surface;
@ -1013,25 +1108,14 @@ PhysicalDevice PhysicalDeviceSelector::populate_device_details(VkPhysicalDevice
physical_device.available_extensions.push_back(&ext.extensionName[0]); physical_device.available_extensions.push_back(&ext.extensionName[0]);
} }
physical_device.features2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; // same value as the non-KHR version
physical_device.properties2_ext_enabled = instance_info.properties2_ext_enabled; physical_device.properties2_ext_enabled = instance_info.properties2_ext_enabled;
auto fill_chain = src_extended_features_chain; auto fill_chain = src_extended_features_chain;
bool instance_is_1_1 = instance_info.version >= VKB_VK_API_VERSION_1_1; bool instance_is_1_1 = instance_info.version >= VKB_VK_API_VERSION_1_1;
if (!fill_chain.empty() && (instance_is_1_1 || instance_info.properties2_ext_enabled)) { if (!fill_chain.nodes.empty() && (instance_is_1_1 || instance_info.properties2_ext_enabled)) {
detail::GenericFeaturesPNextNode* prev = nullptr;
for (auto& extension : fill_chain) {
if (prev != nullptr) {
prev->pNext = &extension;
}
prev = &extension;
}
VkPhysicalDeviceFeatures2 local_features{}; VkPhysicalDeviceFeatures2 local_features{};
local_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; // KHR is same as core here fill_chain.chain_up(local_features);
local_features.pNext = &fill_chain.front();
// Use KHR function if not able to use the core function // Use KHR function if not able to use the core function
if (instance_is_1_1) { if (instance_is_1_1) {
detail::vulkan_functions().fp_vkGetPhysicalDeviceFeatures2(vk_phys_device, &local_features); detail::vulkan_functions().fp_vkGetPhysicalDeviceFeatures2(vk_phys_device, &local_features);
@ -1135,7 +1219,7 @@ PhysicalDeviceSelector::PhysicalDeviceSelector(Instance const& instance, VkSurfa
Result<std::vector<PhysicalDevice>> PhysicalDeviceSelector::select_impl(DeviceSelectionMode selection) const { Result<std::vector<PhysicalDevice>> PhysicalDeviceSelector::select_impl(DeviceSelectionMode selection) const {
#if !defined(NDEBUG) #if !defined(NDEBUG)
// Validation // Validation
for (const auto& node : criteria.extended_features_chain) { for (const auto& node : criteria.extended_features_chain.nodes) {
assert(node.sType != static_cast<VkStructureType>(0) && assert(node.sType != static_cast<VkStructureType>(0) &&
"Features struct sType must be filled with the struct's " "Features struct sType must be filled with the struct's "
"corresponding VkStructureType enum"); "corresponding VkStructureType enum");
@ -1336,7 +1420,7 @@ PhysicalDeviceSelector& PhysicalDeviceSelector::disable_portability_subset() {
} }
PhysicalDeviceSelector& PhysicalDeviceSelector::set_required_features(VkPhysicalDeviceFeatures const& features) { PhysicalDeviceSelector& PhysicalDeviceSelector::set_required_features(VkPhysicalDeviceFeatures const& features) {
criteria.required_features = features; detail::combine_features(criteria.required_features, features);
return *this; return *this;
} }
#if defined(VKB_VK_API_VERSION_1_2) #if defined(VKB_VK_API_VERSION_1_2)
@ -1411,6 +1495,39 @@ bool PhysicalDevice::enable_extensions_if_present(const std::vector<const char*>
return true; return true;
} }
bool PhysicalDevice::enable_features_if_present(const VkPhysicalDeviceFeatures& features_to_enable) {
VkPhysicalDeviceFeatures actual_pdf{};
detail::vulkan_functions().fp_vkGetPhysicalDeviceFeatures(physical_device, &actual_pdf);
bool required_features_supported = detail::supports_features(actual_pdf, features_to_enable, {}, {});
if (required_features_supported) {
detail::combine_features(features, features_to_enable);
}
return required_features_supported;
}
bool PhysicalDevice::enable_features_node_if_present(detail::GenericFeaturesPNextNode const& node) {
VkPhysicalDeviceFeatures2 actual_pdf2{};
detail::GenericFeatureChain requested_features;
requested_features.nodes.push_back(node);
detail::GenericFeatureChain fill_chain = requested_features;
// Zero out supported features
memset(fill_chain.nodes.front().fields, UINT8_MAX, sizeof(VkBool32) * detail::GenericFeaturesPNextNode::field_capacity);
actual_pdf2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
fill_chain.chain_up(actual_pdf2);
detail::vulkan_functions().fp_vkGetPhysicalDeviceFeatures2(physical_device, &actual_pdf2);
bool required_features_supported = detail::supports_features({}, {}, fill_chain, requested_features);
if (required_features_supported) {
extended_features_chain.combine(requested_features);
}
return required_features_supported;
}
PhysicalDevice::operator VkPhysicalDevice() const { return this->physical_device; } PhysicalDevice::operator VkPhysicalDevice() const { return this->physical_device; }
// ---- Queues ---- // // ---- Queues ---- //
@ -1527,7 +1644,7 @@ Result<Device> DeviceBuilder::build() const {
} }
} }
if (user_defined_phys_dev_features_2 && !physical_device.extended_features_chain.empty()) { if (user_defined_phys_dev_features_2 && !physical_device.extended_features_chain.nodes.empty()) {
return { DeviceError::VkPhysicalDeviceFeatures2_in_pNext_chain_while_using_add_required_extension_features }; return { DeviceError::VkPhysicalDeviceFeatures2_in_pNext_chain_while_using_add_required_extension_features };
} }
@ -1535,12 +1652,12 @@ Result<Device> DeviceBuilder::build() const {
auto physical_device_extension_features_copy = physical_device.extended_features_chain; auto physical_device_extension_features_copy = physical_device.extended_features_chain;
VkPhysicalDeviceFeatures2 local_features2{}; VkPhysicalDeviceFeatures2 local_features2{};
local_features2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; local_features2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
local_features2.features = physical_device.features;
if (!user_defined_phys_dev_features_2) { if (!user_defined_phys_dev_features_2) {
if (physical_device.instance_version >= VKB_VK_API_VERSION_1_1 || physical_device.properties2_ext_enabled) { if (physical_device.instance_version >= VKB_VK_API_VERSION_1_1 || physical_device.properties2_ext_enabled) {
local_features2.features = physical_device.features;
final_pnext_chain.push_back(reinterpret_cast<VkBaseOutStructure*>(&local_features2)); final_pnext_chain.push_back(reinterpret_cast<VkBaseOutStructure*>(&local_features2));
for (auto& features_node : physical_device_extension_features_copy) { for (auto& features_node : physical_device_extension_features_copy.nodes) {
final_pnext_chain.push_back(reinterpret_cast<VkBaseOutStructure*>(&features_node)); final_pnext_chain.push_back(reinterpret_cast<VkBaseOutStructure*>(&features_node));
} }
} else { } else {

View File

@ -172,11 +172,35 @@ struct GenericFeaturesPNextNode {
static bool match(GenericFeaturesPNextNode const& requested, GenericFeaturesPNextNode const& supported) noexcept; static bool match(GenericFeaturesPNextNode const& requested, GenericFeaturesPNextNode const& supported) noexcept;
void combine(GenericFeaturesPNextNode const& right) noexcept;
VkStructureType sType = static_cast<VkStructureType>(0); VkStructureType sType = static_cast<VkStructureType>(0);
void* pNext = nullptr; void* pNext = nullptr;
VkBool32 fields[field_capacity]; VkBool32 fields[field_capacity];
}; };
struct GenericFeatureChain {
std::vector<GenericFeaturesPNextNode> nodes;
template <typename T> void add(T const& features) noexcept {
// If this struct is already in the list, combine it
for (auto& node : nodes) {
if (features.sType == node.sType) {
node.combine(features);
return;
}
}
// Otherwise append to the end
nodes.push_back(features);
}
bool match(GenericFeatureChain const& extension_requested) const noexcept;
void chain_up(VkPhysicalDeviceFeatures2& feats2) noexcept;
void combine(GenericFeatureChain const& right) noexcept;
};
} // namespace detail } // namespace detail
enum class InstanceError { enum class InstanceError {
@ -512,6 +536,16 @@ struct PhysicalDevice {
// Returns true if all the extensions are present. // Returns true if all the extensions are present.
bool enable_extensions_if_present(const std::vector<const char*>& extensions); bool enable_extensions_if_present(const std::vector<const char*>& extensions);
// If the features from VkPhysicalDeviceFeatures are all present, make all of the features be enable on the device.
// Returns true all of the features are present.
bool enable_features_if_present(const VkPhysicalDeviceFeatures& features_to_enable);
// If the features from the provided features struct are all present, make all of the features be enable on the
// device. Returns true all of the features are present.
template <typename T> bool enable_extension_features_if_present(T const& features) {
return enable_features_node_if_present(detail::GenericFeaturesPNextNode(features));
}
// A conversion function which allows this PhysicalDevice to be used // A conversion function which allows this PhysicalDevice to be used
// in places where VkPhysicalDevice would have been used. // in places where VkPhysicalDevice would have been used.
operator VkPhysicalDevice() const; operator VkPhysicalDevice() const;
@ -521,8 +555,7 @@ struct PhysicalDevice {
std::vector<std::string> extensions_to_enable; std::vector<std::string> extensions_to_enable;
std::vector<std::string> available_extensions; std::vector<std::string> available_extensions;
std::vector<VkQueueFamilyProperties> queue_families; std::vector<VkQueueFamilyProperties> queue_families;
std::vector<detail::GenericFeaturesPNextNode> extended_features_chain; detail::GenericFeatureChain extended_features_chain;
VkPhysicalDeviceFeatures2 features2{};
bool defer_surface_initialization = false; bool defer_surface_initialization = false;
bool properties2_ext_enabled = false; bool properties2_ext_enabled = false;
@ -530,6 +563,8 @@ struct PhysicalDevice {
Suitable suitable = Suitable::yes; Suitable suitable = Suitable::yes;
friend class PhysicalDeviceSelector; friend class PhysicalDeviceSelector;
friend class DeviceBuilder; friend class DeviceBuilder;
bool enable_features_node_if_present(detail::GenericFeaturesPNextNode const& node);
}; };
enum class PreferredDeviceType { other = 0, integrated = 1, discrete = 2, virtual_gpu = 3, cpu = 4 }; enum class PreferredDeviceType { other = 0, integrated = 1, discrete = 2, virtual_gpu = 3, cpu = 4 };
@ -620,7 +655,7 @@ class PhysicalDeviceSelector {
// If this function is used, the user should not put their own VkPhysicalDeviceFeatures2 in // If this function is used, the user should not put their own VkPhysicalDeviceFeatures2 in
// the pNext chain of VkDeviceCreateInfo. // the pNext chain of VkDeviceCreateInfo.
template <typename T> PhysicalDeviceSelector& add_required_extension_features(T const& features) { template <typename T> PhysicalDeviceSelector& add_required_extension_features(T const& features) {
criteria.extended_features_chain.push_back(features); criteria.extended_features_chain.add(features);
return *this; return *this;
} }
@ -681,14 +716,14 @@ class PhysicalDeviceSelector {
VkPhysicalDeviceFeatures required_features{}; VkPhysicalDeviceFeatures required_features{};
VkPhysicalDeviceFeatures2 required_features2{}; VkPhysicalDeviceFeatures2 required_features2{};
std::vector<detail::GenericFeaturesPNextNode> extended_features_chain; detail::GenericFeatureChain extended_features_chain;
bool defer_surface_initialization = false; bool defer_surface_initialization = false;
bool use_first_gpu_unconditionally = false; bool use_first_gpu_unconditionally = false;
bool enable_portability_subset = true; bool enable_portability_subset = true;
} criteria; } criteria;
PhysicalDevice populate_device_details(VkPhysicalDevice phys_device, PhysicalDevice populate_device_details(
std::vector<detail::GenericFeaturesPNextNode> const& src_extended_features_chain) const; VkPhysicalDevice phys_device, detail::GenericFeatureChain const& src_extended_features_chain) const;
PhysicalDevice::Suitable is_device_suitable(PhysicalDevice const& phys_device) const; PhysicalDevice::Suitable is_device_suitable(PhysicalDevice const& phys_device) const;

View File

@ -7,7 +7,7 @@ else()
endif() endif()
target_link_libraries(VulkanMock target_link_libraries(VulkanMock
PUBLIC PUBLIC
Vulkan::Headers Vulkan::Headers vk-bootstrap
PRIVATE PRIVATE
vk-bootstrap-compiler-warnings vk-bootstrap-compiler-warnings
) )

View File

@ -599,7 +599,6 @@ TEST_CASE("Querying Required Extension Features but with 1.0", "[VkBootstrap.sel
.add_required_extension(VK_KHR_MAINTENANCE3_EXTENSION_NAME) .add_required_extension(VK_KHR_MAINTENANCE3_EXTENSION_NAME)
.add_required_extension_features(descriptor_indexing_features) .add_required_extension_features(descriptor_indexing_features)
.select(); .select();
// Ignore if hardware support isn't true
REQUIRE(phys_dev_ret.has_value()); REQUIRE(phys_dev_ret.has_value());
vkb::DeviceBuilder device_builder(phys_dev_ret.value()); vkb::DeviceBuilder device_builder(phys_dev_ret.value());
@ -631,7 +630,6 @@ TEST_CASE("Querying Required Extension Features", "[VkBootstrap.select_features]
.add_required_extension(VK_KHR_MAINTENANCE3_EXTENSION_NAME) .add_required_extension(VK_KHR_MAINTENANCE3_EXTENSION_NAME)
.add_required_extension_features(descriptor_indexing_features) .add_required_extension_features(descriptor_indexing_features)
.select(); .select();
// Ignore if hardware support isn't true
REQUIRE(phys_dev_ret.has_value()); REQUIRE(phys_dev_ret.has_value());
vkb::DeviceBuilder device_builder(phys_dev_ret.value()); vkb::DeviceBuilder device_builder(phys_dev_ret.value());
@ -643,6 +641,102 @@ TEST_CASE("Querying Required Extension Features", "[VkBootstrap.select_features]
} }
} }
TEST_CASE("Adding Optional Extension Features", "[VkBootstrap.enable_features_if_present]") {
VulkanMock& mock = get_and_setup_default();
mock.api_version = VK_API_VERSION_1_1;
mock.physical_devices_details[0].properties.apiVersion = VK_API_VERSION_1_1;
mock.instance_extensions.push_back(get_extension_properties("VK_KHR_get_physical_device_properties2"));
auto vulkan_10_features = VkPhysicalDeviceFeatures{};
vulkan_10_features.multiViewport = true;
mock.physical_devices_details[0].features = vulkan_10_features;
auto vulkan_11_features = VkPhysicalDeviceVulkan11Features{};
vulkan_11_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES;
vulkan_11_features.shaderDrawParameters = true;
mock.physical_devices_details[0].add_features_pNext_struct(vulkan_11_features);
auto vulkan_12_features = VkPhysicalDeviceVulkan12Features{};
vulkan_12_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES;
vulkan_12_features.bufferDeviceAddress = true;
mock.physical_devices_details[0].add_features_pNext_struct(vulkan_12_features);
GIVEN("A working instance and physical device which has a VkPhysicalDeviceVulkan12Features in its features pNext "
"chain") {
auto instance = get_headless_instance();
VkPhysicalDeviceVulkan12Features physical_device_features_12{};
physical_device_features_12.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES;
physical_device_features_12.bufferDeviceAddress = true;
vkb::PhysicalDeviceSelector selector(instance);
selector.add_required_extension_features(physical_device_features_12);
{
SECTION("Require enable_features_if_present to work with an empty feature struct") {
auto phys_dev = selector.select().value();
VkPhysicalDeviceFeatures phys_dev_features_empty{};
REQUIRE(phys_dev.enable_features_if_present(phys_dev_features_empty));
auto device = vkb::DeviceBuilder(phys_dev).build().value();
vkb::destroy_device(device);
}
SECTION("Require enable_features_if_present to fail with an unsupported feature struct") {
auto phys_dev = selector.select().value();
VkPhysicalDeviceFeatures phys_dev_features_bad{};
phys_dev_features_bad.depthClamp = true;
REQUIRE(!phys_dev.enable_features_if_present(phys_dev_features_bad));
auto device = vkb::DeviceBuilder(phys_dev).build().value();
REQUIRE(!mock.physical_devices_details.at(0).created_device_details.at(0).features.depthClamp);
vkb::destroy_device(device);
}
SECTION("Require enable_features_if_present to work with a supported feature struct") {
auto phys_dev = selector.select().value();
VkPhysicalDeviceFeatures phys_dev_features_good{};
phys_dev_features_good.multiViewport = true;
REQUIRE(phys_dev.enable_features_if_present(phys_dev_features_good));
auto device = vkb::DeviceBuilder(phys_dev).build().value();
REQUIRE(mock.physical_devices_details.at(0).created_device_details.at(0).features.multiViewport);
vkb::destroy_device(device);
}
SECTION("Require enable_extension_features_if_present to work with an empty 1.1 feature struct") {
auto phys_dev = selector.select().value();
VkPhysicalDeviceVulkan11Features phys_dev_vulkan_11_features{};
phys_dev_vulkan_11_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES;
REQUIRE(phys_dev.enable_extension_features_if_present(phys_dev_vulkan_11_features));
auto device = vkb::DeviceBuilder(phys_dev).build().value();
vkb::destroy_device(device);
}
SECTION("Require enable_extension_features_if_present to fail with an unsupported 1.1 feature struct") {
auto phys_dev = selector.select().value();
VkPhysicalDeviceVulkan11Features phys_dev_vulkan_11_features{};
phys_dev_vulkan_11_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES;
phys_dev_vulkan_11_features.multiview = true;
REQUIRE(!phys_dev.enable_extension_features_if_present(phys_dev_vulkan_11_features));
auto device = vkb::DeviceBuilder(phys_dev).build().value();
auto* s = reinterpret_cast<VkPhysicalDeviceVulkan12Features*>(
&mock.physical_devices_details.at(0).created_device_details.at(0).features_pNextChain.at(0));
REQUIRE(s->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES);
vkb::destroy_device(device);
}
SECTION("Require enable_extension_features_if_present to work with a supported 1.1 feature struct") {
auto phys_dev = selector.select().value();
VkPhysicalDeviceVulkan11Features phys_dev_vulkan_11_features{};
phys_dev_vulkan_11_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES;
phys_dev_vulkan_11_features.shaderDrawParameters = true;
REQUIRE(phys_dev.enable_extension_features_if_present(phys_dev_vulkan_11_features));
auto device = vkb::DeviceBuilder(phys_dev).build().value();
auto* s = reinterpret_cast<VkPhysicalDeviceVulkan11Features*>(
&mock.physical_devices_details.at(0).created_device_details.at(0).features_pNextChain.at(1));
REQUIRE(s->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES);
REQUIRE(s->shaderDrawParameters);
vkb::destroy_device(device);
}
}
vkb::destroy_instance(instance);
}
}
TEST_CASE("Passing vkb classes to Vulkan handles", "[VkBootstrap.pass_class_to_handle]") { TEST_CASE("Passing vkb classes to Vulkan handles", "[VkBootstrap.pass_class_to_handle]") {
VulkanMock& mock = get_and_setup_default(); VulkanMock& mock = get_and_setup_default();
auto surface = mock.get_new_surface(get_basic_surface_details()); auto surface = mock.get_new_surface(get_basic_surface_details());
@ -687,7 +781,6 @@ TEST_CASE("Querying Required Extension Features in 1.1", "[VkBootstrap.version]"
.add_required_extension(VK_KHR_MAINTENANCE3_EXTENSION_NAME) .add_required_extension(VK_KHR_MAINTENANCE3_EXTENSION_NAME)
.add_required_extension_features(descriptor_indexing_features) .add_required_extension_features(descriptor_indexing_features)
.select(); .select();
// Ignore if hardware support isn't true
REQUIRE(phys_dev_ret.has_value()); REQUIRE(phys_dev_ret.has_value());
vkb::DeviceBuilder device_builder(phys_dev_ret.value()); vkb::DeviceBuilder device_builder(phys_dev_ret.value());
@ -705,7 +798,6 @@ TEST_CASE("Querying Required Extension Features in 1.1", "[VkBootstrap.version]"
auto phys_dev_ret = selector.add_required_extension(VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME) auto phys_dev_ret = selector.add_required_extension(VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME)
.add_required_extension(VK_KHR_MAINTENANCE3_EXTENSION_NAME) .add_required_extension(VK_KHR_MAINTENANCE3_EXTENSION_NAME)
.select(); .select();
// Ignore if hardware support isn't true
REQUIRE(phys_dev_ret.has_value()); REQUIRE(phys_dev_ret.has_value());
VkPhysicalDeviceFeatures2 phys_dev_feats2{}; VkPhysicalDeviceFeatures2 phys_dev_feats2{};
@ -728,7 +820,6 @@ TEST_CASE("Querying Required Extension Features in 1.1", "[VkBootstrap.version]"
.add_required_extension(VK_KHR_MAINTENANCE3_EXTENSION_NAME) .add_required_extension(VK_KHR_MAINTENANCE3_EXTENSION_NAME)
.add_required_extension_features(descriptor_indexing_features) .add_required_extension_features(descriptor_indexing_features)
.select(); .select();
// Ignore if hardware support isn't true
REQUIRE(phys_dev_ret.has_value()); REQUIRE(phys_dev_ret.has_value());
VkPhysicalDeviceFeatures2 phys_dev_feats2{}; VkPhysicalDeviceFeatures2 phys_dev_feats2{};
@ -765,17 +856,23 @@ TEST_CASE("Querying Vulkan 1.1 and 1.2 features", "[VkBootstrap.version]") {
features_11.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES; features_11.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES;
features_11.multiview = true; features_11.multiview = true;
VkPhysicalDeviceVulkan12Features features_12{}; VkPhysicalDeviceVulkan12Features features_12{};
features_11.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES; features_12.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES;
features_12.bufferDeviceAddress = true; features_12.bufferDeviceAddress = true;
vkb::PhysicalDeviceSelector selector(instance); vkb::PhysicalDeviceSelector selector(instance);
auto phys_dev_ret = selector.set_required_features_11(features_11).set_required_features_12(features_12).select(); auto phys_dev_ret = selector.set_required_features_11(features_11).set_required_features_12(features_12).select();
// Ignore if hardware support isn't true
REQUIRE(phys_dev_ret.has_value()); REQUIRE(phys_dev_ret.has_value());
vkb::DeviceBuilder device_builder(phys_dev_ret.value()); vkb::DeviceBuilder device_builder(phys_dev_ret.value());
auto device_ret = device_builder.build(); auto device_ret = device_builder.build();
REQUIRE(device_ret.has_value()); REQUIRE(device_ret.has_value());
auto* s1 = reinterpret_cast<VkPhysicalDeviceVulkan11Features*>(
&mock.physical_devices_details.at(0).created_device_details.at(0).features_pNextChain.at(0));
REQUIRE(s1->multiview);
auto* s2 = reinterpret_cast<VkPhysicalDeviceVulkan12Features*>(
&mock.physical_devices_details.at(0).created_device_details.at(0).features_pNextChain.at(1));
REQUIRE(s2->bufferDeviceAddress);
vkb::destroy_device(device_ret.value()); vkb::destroy_device(device_ret.value());
} }
mock.api_version = VK_API_VERSION_1_1; mock.api_version = VK_API_VERSION_1_1;
@ -787,9 +884,80 @@ TEST_CASE("Querying Vulkan 1.1 and 1.2 features", "[VkBootstrap.version]") {
vkb::PhysicalDeviceSelector selector(instance); vkb::PhysicalDeviceSelector selector(instance);
auto phys_dev_ret = selector.set_required_features_11(features_11).select(); auto phys_dev_ret = selector.set_required_features_11(features_11).select();
// Ignore if hardware support differs
REQUIRE(!phys_dev_ret.has_value()); REQUIRE(!phys_dev_ret.has_value());
} }
vkb::destroy_instance(instance); vkb::destroy_instance(instance);
} }
} }
TEST_CASE("Add required features in multiple calls", "[VkBootstrap.required_features]") {
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;
mock.physical_devices_details[0].features.independentBlend = true;
mock.physical_devices_details[0].features.shaderInt64 = true;
GIVEN("A working instance") {
auto instance = get_headless_instance(1); // make sure we use 1.1
SECTION("Requires a device that supports independentBlend and shaderInt64") {
VkPhysicalDeviceFeatures features1{};
features1.independentBlend = true;
VkPhysicalDeviceFeatures features2{};
features2.shaderInt64 = true;
vkb::PhysicalDeviceSelector selector(instance);
auto phys_dev_ret = selector.set_required_features(features1).set_required_features(features2).select();
REQUIRE(phys_dev_ret.has_value());
vkb::DeviceBuilder device_builder(phys_dev_ret.value());
auto device_ret = device_builder.build();
REQUIRE(device_ret.has_value());
REQUIRE(mock.physical_devices_details.at(0).created_device_details.at(0).features.independentBlend);
REQUIRE(mock.physical_devices_details.at(0).created_device_details.at(0).features.shaderInt64);
vkb::destroy_device(device_ret.value());
}
vkb::destroy_instance(instance);
}
}
TEST_CASE("Add required extension features in multiple calls", "[VkBootstrap.required_features]") {
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;
auto mock_vulkan_11_features = VkPhysicalDeviceVulkan11Features{};
mock_vulkan_11_features.multiview = true;
mock_vulkan_11_features.samplerYcbcrConversion = true;
mock.physical_devices_details[0].add_features_pNext_struct(mock_vulkan_11_features);
GIVEN("A working instance") {
auto instance = get_headless_instance(1); // make sure we use 1.1
SECTION("Requires a device that supports multiview and samplerYcbcrConversion") {
VkPhysicalDeviceVulkan11Features features1{};
features1.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES;
features1.multiview = true;
VkPhysicalDeviceVulkan11Features features2{};
features2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES;
features2.samplerYcbcrConversion = true;
vkb::PhysicalDeviceSelector selector(instance);
auto phys_dev_ret = selector.set_required_features_11(features1).set_required_features_11(features2).select();
REQUIRE(phys_dev_ret.has_value());
vkb::DeviceBuilder device_builder(phys_dev_ret.value());
auto device_ret = device_builder.build();
REQUIRE(device_ret.has_value());
auto* s1 = reinterpret_cast<VkPhysicalDeviceVulkan11Features*>(
&mock.physical_devices_details.at(0).created_device_details.at(0).features_pNextChain.at(0));
REQUIRE(s1->multiview);
REQUIRE(s1->samplerYcbcrConversion);
vkb::destroy_device(device_ret.value());
}
vkb::destroy_instance(instance);
}
}

View File

@ -138,13 +138,17 @@ VKAPI_ATTR void VKAPI_CALL shim_vkGetPhysicalDeviceMemoryProperties(
} }
VKAPI_ATTR void VKAPI_CALL shim_vkGetPhysicalDeviceFeatures2KHR(VkPhysicalDevice physicalDevice, VkPhysicalDeviceFeatures2* pFeatures) { VKAPI_ATTR void VKAPI_CALL shim_vkGetPhysicalDeviceFeatures2KHR(VkPhysicalDevice physicalDevice, VkPhysicalDeviceFeatures2* pFeatures) {
pFeatures->features = get_physical_device_details(physicalDevice).features;
const auto& phys_dev = get_physical_device_details(physicalDevice); const auto& phys_dev = get_physical_device_details(physicalDevice);
VkBaseOutStructure* current = static_cast<VkBaseOutStructure*>(pFeatures->pNext); VkBaseOutStructure* current = static_cast<VkBaseOutStructure*>(pFeatures->pNext);
while (current) { while (current) {
for (const auto& features_pNext : phys_dev.features_pNextChain) { for (const auto& features_pNext : phys_dev.features_pNextChain) {
if (features_pNext->sType == current->sType) { if (features_pNext.sType == current->sType) {
VkBaseOutStructure* next = static_cast<VkBaseOutStructure*>(current->pNext); VkBaseOutStructure* next = static_cast<VkBaseOutStructure*>(current->pNext);
std::memcpy(current, features_pNext.get(), get_pnext_chain_struct_size(features_pNext->sType)); std::memcpy(static_cast<void*>(current),
static_cast<const void*>(&features_pNext),
get_pnext_chain_struct_size(features_pNext.sType));
// Repair pNext void* since we clobbered it in the memcpy
current->pNext = next; current->pNext = next;
break; break;
} }
@ -179,27 +183,56 @@ VKAPI_ATTR VkResult VKAPI_CALL shim_vkCreateDevice(VkPhysicalDevice physicalDevi
return VK_ERROR_INITIALIZATION_FAILED; return VK_ERROR_INITIALIZATION_FAILED;
} }
*pDevice = get_handle<VkDevice>(0x0000ABCDU); *pDevice = get_handle<VkDevice>(0x0000ABCDU);
get_physical_device_details(physicalDevice).created_devices.push_back(*pDevice); auto& physical_device_details = get_physical_device_details(physicalDevice);
physical_device_details.created_device_handles.push_back(*pDevice);
VkPhysicalDeviceFeatures new_feats{};
if (pCreateInfo->pEnabledFeatures) {
new_feats = *pCreateInfo->pEnabledFeatures;
}
std::vector<vkb::detail::GenericFeaturesPNextNode> new_chain;
std::vector<const char*> created_extensions;
for (uint32_t i = 0; i < pCreateInfo->enabledExtensionCount; i++) {
created_extensions.push_back(pCreateInfo->ppEnabledExtensionNames[i]);
}
const void* pNext_chain = pCreateInfo->pNext;
while (pNext_chain) {
const auto* chain = static_cast<const VkBaseOutStructure*>(pNext_chain);
const void* next = chain->pNext;
if (check_if_features2_struct(chain->sType) > 0) {
vkb::detail::GenericFeaturesPNextNode node;
std::memcpy(static_cast<void*>(&node), pNext_chain, get_pnext_chain_struct_size(chain->sType));
new_chain.push_back(node);
}
if (chain->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2) {
new_feats = static_cast<const VkPhysicalDeviceFeatures2*>(pNext_chain)->features;
}
pNext_chain = next;
}
physical_device_details.created_device_details.push_back({ new_feats, created_extensions, new_chain });
return VK_SUCCESS; return VK_SUCCESS;
} }
VKAPI_ATTR void VKAPI_CALL shim_vkDestroyDevice( VKAPI_ATTR void VKAPI_CALL shim_vkDestroyDevice(
[[maybe_unused]] VkDevice device, [[maybe_unused]] const VkAllocationCallbacks* pAllocator) { [[maybe_unused]] VkDevice device, [[maybe_unused]] const VkAllocationCallbacks* pAllocator) {
for (auto& physical_devices : mock.physical_devices_details) { for (auto& physical_devices : mock.physical_devices_details) {
auto it = std::find(std::begin(physical_devices.created_devices), std::end(physical_devices.created_devices), device); auto it = std::find(
if (it != std::end(physical_devices.created_devices)) { std::begin(physical_devices.created_device_handles), std::end(physical_devices.created_device_handles), device);
physical_devices.created_devices.erase(it); if (it != std::end(physical_devices.created_device_handles)) {
auto index = it - physical_devices.created_device_handles.begin();
physical_devices.created_device_handles.erase(it);
physical_devices.created_device_details.erase(physical_devices.created_device_details.begin() + index);
} }
} }
} }
VKAPI_ATTR void VKAPI_CALL shim_vkGetDeviceQueue(VkDevice device, uint32_t queueFamilyIndex, uint32_t queueIndex, VkQueue* pQueue) { VKAPI_ATTR void VKAPI_CALL shim_vkGetDeviceQueue(VkDevice device, uint32_t queueFamilyIndex, uint32_t queueIndex, VkQueue* pQueue) {
for (auto& physical_devices : mock.physical_devices_details) { for (auto& physical_devices : mock.physical_devices_details) {
auto it = std::find(std::begin(physical_devices.created_devices), std::end(physical_devices.created_devices), device); auto it = std::find(
if (it != std::end(physical_devices.created_devices)) { std::begin(physical_devices.created_device_handles), std::end(physical_devices.created_device_handles), device);
if (it != std::end(physical_devices.created_device_handles)) {
if (queueFamilyIndex < physical_devices.queue_family_properties.size() && if (queueFamilyIndex < physical_devices.queue_family_properties.size() &&
queueIndex < physical_devices.queue_family_properties[queueFamilyIndex].queueCount) { queueIndex < physical_devices.queue_family_properties[queueFamilyIndex].queueCount) {
*pQueue = get_handle<VkQueue>(0x0000CCEEU); *pQueue = get_handle<VkQueue>(0x0000CCEEU + queueIndex);
return; return;
} }
} }

View File

@ -10,9 +10,11 @@
#include <vulkan/vulkan_core.h> #include <vulkan/vulkan_core.h>
// Helper function to get the size of a struct given a VkStructureType #include <VkBootstrap.h>
// Helper function to return the size of the sType if it is a known features struct, otherwise return 0
// Hand written, must be updated to include any used struct. // Hand written, must be updated to include any used struct.
inline size_t get_pnext_chain_struct_size(VkStructureType type) { inline size_t check_if_features2_struct(VkStructureType type) {
switch (type) { switch (type) {
case (VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES): case (VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES):
return sizeof(VkPhysicalDeviceDescriptorIndexingFeatures); return sizeof(VkPhysicalDeviceDescriptorIndexingFeatures);
@ -21,9 +23,14 @@ inline size_t get_pnext_chain_struct_size(VkStructureType type) {
case (VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES): case (VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES):
return sizeof(VkPhysicalDeviceVulkan12Features); return sizeof(VkPhysicalDeviceVulkan12Features);
default: default:
assert(false && "Must update get_pnext_chain_struct_size(VkStructureType type) to add type!");
}
return 0; return 0;
}
}
inline size_t get_pnext_chain_struct_size(VkStructureType type) {
auto size = check_if_features2_struct(type);
assert(size > 0 && "Must update get_pnext_chain_struct_size(VkStructureType type) to add type!");
return size;
} }
template <typename T> T get_handle(size_t value) { return reinterpret_cast<T>(value); } template <typename T> T get_handle(size_t value) { return reinterpret_cast<T>(value); }
@ -65,21 +72,24 @@ struct VulkanMock {
return surface_handles.back(); return surface_handles.back();
} }
struct CreatedDeviceDetails {
VkPhysicalDeviceFeatures features{};
std::vector<const char*> extensions;
std::vector<vkb::detail::GenericFeaturesPNextNode> features_pNextChain;
};
struct PhysicalDeviceDetails { struct PhysicalDeviceDetails {
VkPhysicalDeviceProperties properties{}; VkPhysicalDeviceProperties properties{};
VkPhysicalDeviceFeatures features{}; VkPhysicalDeviceFeatures features{};
VkPhysicalDeviceMemoryProperties memory_properties{}; VkPhysicalDeviceMemoryProperties memory_properties{};
std::vector<VkExtensionProperties> extensions; std::vector<VkExtensionProperties> extensions;
std::vector<VkQueueFamilyProperties> queue_family_properties; std::vector<VkQueueFamilyProperties> queue_family_properties;
std::vector<std::unique_ptr<VkBaseOutStructure>> features_pNextChain; std::vector<vkb::detail::GenericFeaturesPNextNode> features_pNextChain;
std::vector<VkDevice> created_devices; std::vector<VkDevice> created_device_handles;
std::vector<CreatedDeviceDetails> created_device_details;
template <typename T> void add_features_pNext_struct(T t) { template <typename T> void add_features_pNext_struct(T features) { features_pNextChain.push_back(features); }
T* new_type = new T();
*new_type = t;
features_pNextChain.emplace_back(reinterpret_cast<VkBaseOutStructure*>(new_type));
}
}; };
std::vector<VkPhysicalDevice> physical_device_handles; std::vector<VkPhysicalDevice> physical_device_handles;