I've found two variants of the two-call pattern for vkEnumerate*. I'm wondering what the merit of the second solution over the first is.
The first solution:
uint32_t count = 0;
vkEnumerateTs(ts..., &count, nullptr);
std::vector<T> results(count);
auto error = vkEnumerateTs(args..., &count, results.data());
What this solution doesn't take into account is that vkEnumerateInstanceLayerProperties
and vkEnumerateInstanceExtensionProperties may change at any time (see 37.4.1 of Vulkan Specification 1.2.151):
The list of available layers may change at any time due to actions outside of the Vulkan implementation, so two calls to
vkEnumerateInstanceLayerPropertieswith the same parameters may return different results, or retrieve differentpPropertyCountvalues orpPropertiescontents. Once an instance has been created, the layers enabled for that instance will continue to be enabled and valid for the lifetime of that instance, even if some of them become unavailable for future instances.
(Similar for vkEnumerateInstanceExtensionProperties.)
If count is larger than before, you're not getting all the info;
if count is smaller than before, the tail of results contains invalid data.
Note: The results of the other vkEnumerate* commands seem to have infinite
life time, according to 2.5.1 Lifetime of Retrieved Results of Vulkan Specification 1.2.151, so for the other vkEnumerate* commands, the first solution is sufficient:
Unless otherwise specified for an individual command, the results are invariant; that is, they will remain unchanged when retrieved again by calling the same command with the same parameters, so long as those parameters themselves all remain valid.
The latter problem can be solved by extending the solution above
(VK_INCOMPLETE is returned if count is smaller than the number of properties available):
// Check if too much space was allocated.
if (error != VK_INCOMPLETE)
{
results.resize(count);
}
The second variant (taken from
Vulkan-Tools;
f is vkEnumerateTs, init serves as type sentinel for template
deduction and default value) seems to serve to solve the first problem (not getting all the info):
// Helper for robustly executing the two-call pattern
template <typename T, typename F, typename... Ts>
auto GetVectorInit(const char *func_name, F &&f, T init, Ts &&... ts) -> std::vector<T> {
uint32_t count = 0;
std::vector<T> results;
VkResult err;
do {
err = f(ts..., &count, nullptr);
if (err) THROW_VK_ERR(func_name, err);
results.resize(count, init);
err = f(ts..., &count, results.data());
results.resize(count);
} while (err == VK_INCOMPLETE);
if (err) THROW_VK_ERR(func_name, err);
return results;
}
But what's the use of even trying to get "all" the info, if the amount of
available info may change at any time? Why keep iterating through the
while loop to get "complete" information if the moment you leave the
while loop to return the results, it may already be incomplete again?
Even worse, you may (theoretically) get stuck in this while loop forever
if the value of count after the first call to f is always smaller
than its value after the second call.
Am I misinterpreting the situation? Does the second variant have any merit over the first (with the modification I propose)?