-
Notifications
You must be signed in to change notification settings - Fork 79
library: Split capabilities creation from init #924
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
ea10a8b
f081b05
2d4691b
d72ac33
a257321
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,6 +22,8 @@ | |
| - [API reference](#api-reference) | ||
| - [Preprocessor definitions](#preprocessor-definitions) | ||
| - [Profile support and usage](#profile-support-and-usage) | ||
| - [Initializing capabilities](#initializing-capabilities) | ||
| - [Loading instance-level functions](#loading-instance-level-functions) | ||
| - [Checking instance level support](#checking-instance-level-support) | ||
| - [Creating instance with profile](#creating-instance-with-profile) | ||
| - [Checking device level support](#checking-device-level-support) | ||
|
|
@@ -87,14 +89,21 @@ In order to use the Profiles API library, the application first has to create a | |
| ```C++ | ||
| VpCapabilities capabilities = VK_NULL_HANDLE; | ||
|
|
||
| VpCapabilitiesCreateInfo createInfo; | ||
| createInfo.apiVersion = VK_API_VERSION_1_1; | ||
| createInfo.flags = VP_PROFILE_CREATE_STATIC_BIT; | ||
| vpCreateCapabilities(nullptr, &capabilities); | ||
| ``` | ||
|
|
||
| `vpCreateCapabilities` only allocates the `VpCapabilities` object; it no longer takes a `VpCapabilitiesCreateInfo` argument. The object must then be initialized with the global-level Vulkan functions (such as `vkCreateInstance` and `vkEnumerateInstanceExtensionProperties`) it needs by calling `vpInitialize`: | ||
|
|
||
| ```C++ | ||
| VpCapabilitiesCreateInfo createInfo{}; | ||
| createInfo.flags = VP_CAPABILITIES_CREATE_STATIC_BIT; | ||
| createInfo.pVulkanFunctions = nullptr; | ||
|
|
||
| vpCreateCapabilities(&createInfo, nullptr, &capabilities); | ||
| vpInitialize(capabilities, &createInfo); | ||
| ``` | ||
|
|
||
| `VP_CAPABILITIES_CREATE_STATIC_BIT` tells the library to resolve the Vulkan functions it needs from the statically linked Vulkan loader; this flag is only available when the application links against the Vulkan loader directly (i.e. `VK_NO_PROTOTYPES` and `VP_DISABLE_STATIC_LINKING` are not defined). Applications that load Vulkan dynamically should instead use `VP_CAPABILITIES_CREATE_DYNAMIC_BIT` and provide a `VpVulkanFunctions::GetInstanceProcAddr` pointer through `pVulkanFunctions`, from which the library will resolve the remaining global-level functions it needs. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not clear to me what's the purpose of
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I created this in case the user doesn't want to use function declarations even if |
||
|
|
||
| Then the application has to make sure that the Vulkan implementation supports the selected profile as follows: | ||
| ```C++ | ||
| VkResult result = VK_SUCCESS; | ||
|
|
@@ -146,7 +155,17 @@ The above code example will create a Vulkan instance with the API version and in | |
|
|
||
| Make sure to set the `apiVersion` in the `VkApplicationInfo` structure at least to the minimum API version required by the profile, as seen above, to ensure the correct Vulkan API version is used. | ||
|
|
||
| Once a Vulkan instance is created, the application can check whether individual physical devices support the selected profile as follows: | ||
| Once a Vulkan instance is created, the `VpCapabilities` object must be told about it so that it can resolve the instance-level Vulkan functions (such as `vkCreateDevice` and `vkGetPhysicalDeviceFeatures2`) it needs for device-level queries and device creation: | ||
|
|
||
| ```C++ | ||
| result = vpLoadInstance(capabilities, instance, {}); | ||
| if (result != VK_SUCCESS) { | ||
| // something went wrong | ||
| ... | ||
| } | ||
| ``` | ||
|
|
||
| Once the instance-level functions are loaded, the application can check whether individual physical devices support the selected profile as follows: | ||
|
|
||
| ```C++ | ||
| result = vpGetPhysicalDeviceProfileSupport(capabilities, instance, physicalDevice, | ||
|
|
@@ -264,6 +283,96 @@ Where: | |
|
|
||
| The Vulkan Profile library offers a set of APIs to verify support for a particular Vulkan profile and to create Vulkan instances and devices using the extensions and features required by the profile. | ||
|
|
||
| #### Initializing capabilities | ||
|
|
||
| After a `VpCapabilities` object is allocated with `vpCreateCapabilities`, it must be initialized with the global-level Vulkan functions it needs by calling the following command: | ||
|
|
||
| ```C++ | ||
| VkResult vpInitialize( | ||
| VpCapabilities capabilities, | ||
| const VpCapabilitiesCreateInfo* pCreateInfo); | ||
| ``` | ||
|
|
||
| Where: | ||
| * `capabilities` must be one of the capabilities handles returned from a call to `vpCreateCapabilities`. | ||
| * `pCreateInfo` is a pointer to the `VpCapabilitiesCreateInfo` structure specifying how the library should resolve the Vulkan functions it needs. | ||
|
|
||
| The `VpCapabilitiesCreateInfo` structure is defined as follows: | ||
|
|
||
| ```C++ | ||
| typedef struct VpCapabilitiesCreateInfo | ||
| { | ||
| VpCapabilitiesCreateFlags flags; | ||
| const VpVulkanFunctions* pVulkanFunctions; | ||
| } VpCapabilitiesCreateInfo; | ||
| ``` | ||
|
|
||
| Where: | ||
| * `flags` is a bitmask of `VpCapabilitiesCreateFlagBits` indicating how the Vulkan functions are to be resolved. | ||
| * `pVulkanFunctions` is either `NULL` or a pointer to a `VpVulkanFunctions` structure providing application-supplied Vulkan function pointers, as described below. | ||
|
|
||
| The `VpCapabilitiesCreateFlagBits` enumeration is defined as follows: | ||
|
|
||
| ```C++ | ||
| typedef enum VpCapabilitiesCreateFlagBits { | ||
| VP_CAPABILITIES_CREATE_STATIC_BIT = (1 << 0), | ||
| VP_CAPABILITIES_CREATE_DYNAMIC_BIT = (1 << 1), | ||
| VP_CAPABILITIES_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF | ||
| } VpCapabilitiesCreateFlagBits; | ||
| ``` | ||
|
|
||
| If `VP_CAPABILITIES_CREATE_STATIC_BIT` is specified, the library resolves the Vulkan functions it needs from the statically linked Vulkan loader. This flag is only available when `VK_NO_PROTOTYPES` and `VP_DISABLE_STATIC_LINKING` are not defined. | ||
|
|
||
| If `VP_CAPABILITIES_CREATE_DYNAMIC_BIT` is specified, the application must provide a `VpVulkanFunctions::GetInstanceProcAddr` pointer through `pVulkanFunctions`, and the library resolves the remaining global-level functions it needs by calling it. | ||
|
|
||
| The `VpVulkanFunctions` structure allows the application to explicitly provide any of the Vulkan function pointers the library needs (overriding or supplementing the ones resolved automatically): | ||
|
|
||
| ```C++ | ||
| typedef struct VpVulkanFunctions { | ||
| PFN_vkGetInstanceProcAddr GetInstanceProcAddr; | ||
| PFN_vkEnumerateInstanceVersion EnumerateInstanceVersion; | ||
| PFN_vkEnumerateInstanceExtensionProperties EnumerateInstanceExtensionProperties; | ||
| PFN_vkEnumerateDeviceExtensionProperties EnumerateDeviceExtensionProperties; | ||
| PFN_vkGetPhysicalDeviceFeatures2 GetPhysicalDeviceFeatures2; | ||
| PFN_vkGetPhysicalDeviceProperties2 GetPhysicalDeviceProperties2; | ||
| PFN_vkGetPhysicalDeviceFormatProperties2 GetPhysicalDeviceFormatProperties2; | ||
| PFN_vkGetPhysicalDeviceQueueFamilyProperties2 GetPhysicalDeviceQueueFamilyProperties2; | ||
| PFN_vkCreateInstance CreateInstance; | ||
| PFN_vkCreateDevice CreateDevice; | ||
| } VpVulkanFunctions; | ||
| ``` | ||
|
|
||
| Note that `VpVulkanFunctions` no longer includes a `GetDeviceProcAddr` member, as device-level functions are now resolved through the instance rather than through a device proc addr loader. | ||
|
|
||
| #### Loading instance-level functions | ||
|
|
||
| Once a Vulkan instance has been created, the instance-level Vulkan functions the library needs for device-level queries and device creation (such as `vkCreateDevice` and `vkGetPhysicalDeviceFeatures2`) must be loaded by calling the following command: | ||
|
|
||
| ```C++ | ||
| VkResult vpLoadInstance( | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Did you consider automatically calling this function in
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I copied the behavior of these functions from volk and thought about it. I initially did this because I thought it was always explicit > implicit, plus, architecturally, instance creation shouldn't be tied to initialization. In theory, if a library user decides to create multiple
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But what should happen if the instance was created successfully, but vpLoadInstance failed? Should vkInstance be automatically deleted?
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, it makes sense to me to keep the option of calling Also, I am not convinced |
||
| VpCapabilities capabilities, | ||
| VkInstance instance, | ||
| VpInstanceFunctionsLoadFlags flags); | ||
| ``` | ||
|
|
||
| Where: | ||
| * `capabilities` must be one of the capabilities handles returned from a call to `vpCreateCapabilities`. | ||
| * `instance` is the Vulkan instance to load the instance-level functions from. | ||
| * `flags` is a bitmask of `VpInstanceFunctionsLoadFlagBits` indicating additional entry point variants to try when loading. | ||
|
|
||
| The `VpInstanceFunctionsLoadFlagBits` enumeration is defined as follows: | ||
|
|
||
| ```C++ | ||
| typedef enum VpInstanceFunctionsLoadFlagBits { | ||
| VP_INSTANCE_FUNCTIONS_LOAD_KHR_GET_PHYSICAL_DEVICE_PROPERTIES2_BIT = (1 << 0), | ||
| VP_INSTANCE_FUNCTIONS_LOAD_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF | ||
| } VpInstanceFunctionsLoadFlagBits; | ||
| ``` | ||
|
|
||
| If `VP_INSTANCE_FUNCTIONS_LOAD_KHR_GET_PHYSICAL_DEVICE_PROPERTIES2_BIT` is specified, and the core `vkGetPhysicalDeviceFeatures2`/`vkGetPhysicalDeviceProperties2`/`vkGetPhysicalDeviceFormatProperties2`/`vkGetPhysicalDeviceQueueFamilyProperties2` entry points are not available, the library falls back to loading their `VK_KHR_get_physical_device_properties2` (`...KHR`) variants instead. This is useful when targeting a Vulkan 1.0 implementation that only supports the extension. | ||
|
|
||
| `vpLoadInstance` must be called after `vpCreateInstance` (or after any other means of instance creation) and before querying device-level profile support or creating a device with `vpCreateDevice`. | ||
|
|
||
| #### Checking instance level support | ||
|
|
||
| In order to query whether the Vulkan implementation supports the necessary instance level requirements (API version and instance extensions) of a particular profile, use the following command: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| /* | ||
| * Copyright (c) 2021-2026 LunarG, Inc. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| #ifndef VK_NO_PROTOTYPES | ||
| #define VK_NO_PROTOTYPES 1 | ||
| #endif | ||
|
|
||
| #ifndef VP_DISABLE_STATIC_LINKING | ||
| #define VP_DISABLE_STATIC_LINKING 1 | ||
| #endif | ||
|
|
||
| #ifndef VULKAN_PROFILES_HEADER_ONLY | ||
| #include <vulkan/vulkan_profiles.hpp> | ||
| #else | ||
| #include <vulkan/debug/vulkan_profiles.h> | ||
| #endif | ||
|
|
||
| #include <gtest/gtest.h> | ||
| #include <vulkan/vulkan.hpp> | ||
|
|
||
| int main(int argc, char** argv) { | ||
| ::testing::InitGoogleTest(&argc, argv); | ||
|
|
||
| int result = RUN_ALL_TESTS(); | ||
|
|
||
| return result; | ||
| } | ||
|
|
||
| TEST(no_prototypes, create_instance_with_dynamic_pointers) { | ||
| vk::detail::DispatchLoaderDynamic dl; | ||
| dl.init(); | ||
|
|
||
| VpCapabilitiesCreateInfo cci{}; | ||
| cci.flags = VP_CAPABILITIES_CREATE_DYNAMIC_BIT; | ||
| VpVulkanFunctions vf{}; | ||
| vf.GetInstanceProcAddr = dl.vkGetInstanceProcAddr; | ||
| cci.pVulkanFunctions = &vf; | ||
| EXPECT_TRUE(vpInitialize(&cci) == VK_SUCCESS); | ||
|
|
||
| VkApplicationInfo ai{}; | ||
| ai.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; | ||
| ai.pApplicationName = "Testing scaffold"; | ||
| ai.applicationVersion = VK_MAKE_VERSION(1, 2, 0); | ||
| ai.pEngineName = "No Engine"; | ||
| ai.engineVersion = VK_MAKE_VERSION(1, 2, 0); | ||
| ai.apiVersion = VK_API_VERSION_1_2; | ||
|
|
||
| VkInstanceCreateInfo ici{}; | ||
| ici.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; | ||
| ici.pApplicationInfo = &ai; | ||
|
|
||
| #if defined(__APPLE__) | ||
| const char* extensions[] = { VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME }; | ||
| ici.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; | ||
| ici.enabledExtensionCount = 1; | ||
| ici.ppEnabledExtensionNames = extensions; | ||
| #endif | ||
|
|
||
| VkInstance instance{}; | ||
| EXPECT_TRUE(dl.vkCreateInstance(&ici, nullptr, &instance) == VK_SUCCESS); | ||
|
|
||
| EXPECT_TRUE(vpLoadInstance(instance, {}) == VK_SUCCESS); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -52,9 +52,10 @@ struct Capabilities { | |
| VpCapabilities handle = VK_NULL_HANDLE; | ||
|
|
||
| Capabilities() { | ||
| vpCreateCapabilities(nullptr, &handle); | ||
|
|
||
| VpVulkanFunctions vulkanFunctions; | ||
| vulkanFunctions.GetInstanceProcAddr = vkGetInstanceProcAddr; | ||
| vulkanFunctions.GetDeviceProcAddr = vkGetDeviceProcAddr; | ||
| vulkanFunctions.EnumerateInstanceVersion = vkEnumerateInstanceVersion; | ||
| vulkanFunctions.EnumerateInstanceExtensionProperties = vkEnumerateInstanceExtensionProperties; | ||
| vulkanFunctions.EnumerateDeviceExtensionProperties = vkEnumerateDeviceExtensionProperties; | ||
|
|
@@ -66,11 +67,9 @@ struct Capabilities { | |
| vulkanFunctions.CreateDevice = vkCreateDevice; | ||
|
|
||
| VpCapabilitiesCreateInfo createInfo; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would have be great to keep the API backward compatible. I created
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How much should backward compatibility be maintained?
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's fine to break compatibility with the VP_USE_OBJECT code path which is explicitly marked "beta". |
||
| createInfo.apiVersion = VK_API_VERSION_1_1; | ||
| createInfo.flags = VP_PROFILE_CREATE_STATIC_BIT; | ||
| createInfo.flags = VP_CAPABILITIES_CREATE_STATIC_BIT; | ||
| createInfo.pVulkanFunctions = &vulkanFunctions; | ||
|
|
||
| vpCreateCapabilities(&createInfo, nullptr, &handle); | ||
| vpInitialize(handle, &createInfo); | ||
| } | ||
|
|
||
| ~Capabilities() { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the
vpLoadInstanceandvpInitializefunctions are a little be confusing to me. Probably just an naming issue.Maybe the
VpCapabilities_Tshould be completely redesigned and considering it's not enabled by default I think it would be ok. ProbablyVpCapabilitiesis not a great name either and in a first place. MaybeVpInstance?For example, we could create a per
VkInstancetable to store the function loaded withImportInstanceVulkanFunctions_Dynamic.I could picture something like
vpLoadGlobalFuncreplacingvpInitialize:Where
vpLoadGlobalFuncis just a helper function to fill global functions usingdl.vkGetInstanceProcAddr. This leave the posibility for Vulkan developer to fill either function manually.vpCreateInstancewould returnVK_ERROR_INITIALIZATION_FAILEDif pVulkanFunctions is not initialized/validated whenVK_NO_PROTOTYPESandVP_DISABLE_STATIC_LINKINGare not defined.Otherwise,
vpCreateInstancewould use the static function automatically.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think a table for instance -> VpCapabilities object would be an unnecessarily complex functionality
It's a good idea to use the vpLoadGlobalFunc helper. This could eliminate the
_STATIC/_DYNAMIC_BITaltogether.I think it could look something like this:
By the way, this will also solve the problem of backward compatibility, so
apiVersionandflagscan just be deprecatedbut how to dynamically initialize a singleton in this case?
I can leave the global vpInitialize function, but without the first capabilities parameter, which would initialize the global singleton, so it could look something like this:
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can also remove the public
vpLoadGlobalFuncand load functions dynamically invpInitializeorvpCreateCapabilitiesif the user passed gipa topVulkanFunctionsin create infoThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed with removing
_STATIC/_DYNAMIC_BIT.Regarding "I think a table for instance -> VpCapabilities object would be an unnecessarily complex functionality" I was thinking about this as an implementation detail of a
VpInstanceobject, removing the VpCapabilities object.I'll try some things...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Regarding "but how to dynamically initialize a singleton in this case?"
The VpCapabilities was introduced to support custom function pointers and the singleton was introduced for API backward compatibility.
Once stable, I would like to simplify all this and only have the VP_USE_OBJECT code path, deprecate the other code path and them remove it.
So I suggest to support the dynamic mode only with the VP_USE_OBJECT code path.
What do you think of this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dynamic loading support only for VP_USE_OBJECT sounds logical, considering that the old code was actually compiled only for static linking
Old code with singleton will use implicit static initialization, if static functions are not available (VK_NO_PROTOTYPES is defined and VP_USE_OBJECT is not defined), code can produce explicit #error macro (or left the user with function nullptr dereference)
we can also rename VpCapabilities to
VpLoaderorVpDispatcher(dispatcher is already used in vulkan.hpp), so we can avoid name collisionsI think the final version could look something like this:
VkInstance instance{}; VpDispatcher dispatcher{}; if (wantDynamicLoading) { VpVulkanFunctions vulkanFunctions{}; vulkanFunctions.GetInstanceProcAddr = reinterpret_cast<PFN_vkGetInstanceProcAddr>(SDL_Vulkan_GetVkGetInstanceProcAddr()); VpDispatcherCreateInfo dispatcherCreateInfo{}; dispatcherCreateInfo.pVulkanFunctions = vulkanFunctions; // If pVulkanFunctions->GetInstanceProcAddr is not null, it will get global functions here via GetInstanceProcAddr, if it fails, return an error vpCreateDispatcher(&dispatcherCreateInfo, nullptr, &dispatcher); if (wantCreateInstanceByMyCode) { instance = create_instance_by_my_code(); vpLoadInstance(dispatcher, instance, VP_LOAD_INSTANCE_HAS_GPDP2_BIT); } else { ... // If created with GetInstanceProcAddr (we can add internal bool flag for it), it will try to get rest of the functions via gipa, if it fails, return the valid VkInstance, left instance deletion to the user, and return an error vpCreateInstance(capabilities, ..., &instance); } vpDestroyDispatcher(nullptr, &dispatcher); } else if (wantStaticLoading) { VpVulkanFunctions vulkanFunctions{}; VpDispatcherCreateInfo dispatcherCreateInfo{}; dispatcherCreateInfo.pVulkanFunctions = vulkanFunctions; // or dispatcherCreateInfo.pVulkanFunctions = nullptr; // if VK_NO_PROTOTYPES is defined, return an error vpCreateDispatcher(&dispatcherCreateInfo, nullptr, &dispatcher); if (wantCreateInstanceByMyCode) { // Will not do anything instance = create_instance_by_my_code(); vpLoadInstance(dispatcher, instance, VP_LOAD_INSTANCE_HAS_GPDP2_BIT); } else { ... // Also will not load any functions vpCreateInstance(capabilities, ..., &instance); } vpDestroyDispatcher(nullptr, &dispatcher); }There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would push the changes with renamed functions and structures, but I do not know how to maintain backward compatibility. Wait for vulkan 1.5?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here is the design I am suggesting: #926
There are still some issues that I didn't had time to fix, mocked tests not passing because the static functions are used instead of the Mock functions I believe.
I'll continue tomorrow but feel free to give me your feedback.