【发布时间】:2020-12-25 04:00:45
【问题描述】:
我发现vkEnumerate* 的两次调用模式的两种变体。我想知道第二个解决方案比第一个解决方案的优点是什么。
第一种解决方案:
uint32_t count = 0;
vkEnumerateTs(ts..., &count, nullptr);
std::vector<T> results(count);
auto error = vkEnumerateTs(args..., &count, results.data());
这个解决方案没有考虑到vkEnumerateInstanceLayerProperties
和vkEnumerateInstanceExtensionProperties 可能随时更改(请参阅 Vulkan 规范 1.2.151 的 37.4.1):
由于 Vulkan 之外的操作,可用层列表可能随时更改 实现,因此对
vkEnumerateInstanceLayerProperties的两次调用可能具有相同的参数 返回不同的结果,或检索不同的pPropertyCount值或pProperties内容。创建实例后,为该实例启用的层将继续 在该实例的生命周期内启用并有效,即使其中一些变为 对未来的实例不可用。
(类似于vkEnumerateInstanceExtensionProperties。)
如果count 比以前大,您将无法获得所有信息;
如果count 比以前小,则results 的尾部包含无效数据。
注意:其他vkEnumerate* 命令的结果似乎有无穷大
life time,根据 Vulkan Specification 1.2.151 的 2.5.1 Lifetime of Retrieved Results,所以对于其他 vkEnumerate* 命令,第一个解决方案就足够了:
除非对单个命令另有规定,否则结果是不变的;也就是说,当通过使用相同参数调用相同命令再次检索它们时,它们将保持不变,只要这些参数本身都保持有效。
后一个问题可以通过扩展上面的解决方案来解决
(如果count 小于可用属性的数量,则返回VK_INCOMPLETE):
// Check if too much space was allocated.
if (error != VK_INCOMPLETE)
{
results.resize(count);
}
第二个变体(取自
Vulkan-Tools;
f 是 vkEnumerateTs,init 用作模板的类型标记
扣除和默认值)似乎可以解决第一个问题(没有得到所有信息):
// 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;
}
但是,即使尝试获取“所有”信息又有什么用,如果
可用信息可能随时更改?为什么要不断迭代
如果您离开的那一刻,while 循环以获取“完整”信息
while 循环返回结果,它可能已经不完整了?
更糟糕的是,你可能(理论上)永远卡在这个 while 循环中
如果第一次调用f 后count 的值总是更小
比第二次调用后的值。
我是否误解了情况?是否第二个变体 比第一个有什么优点(我提出的修改)?
【问题讨论】:
-
你只是想多了。不确定问题到底是什么,因为您已经列出了惯用方案。第二个版本的优点是它不会触发强迫症,看起来它可以很好地处理所有事情,而且只长了2 LOC。换句话说,让读者好奇和思考的代码通常是正确的。想要对事物“聪明”并过早地尝试优化某些事物的代码通常是错误的。
-
@krOoze:我不确定我是否理解你的观点。 OP 的重点是规范声明这些特定枚举函数返回的值不可靠。他们可以随时改变。如果它们可能不断变化,为什么还要麻烦循环呢?循环只是创造了它永远不会结束的可能性。枚举值最终会稳定到某个特定值的想法吗?因为规范没有要求。
-
@NicolBolas 我的意思是,我不确定 OP 试图解决什么问题,因为惯用版本非常好、健壮且难以使用错误。在这里寻找替代品的原因是什么?除非驱动程序是积极恶意的,否则它永远不会结束的可能性为零,在这种情况下,您会遇到更严重的问题。在 99.999 % 的情况下,它会在第一次迭代后结束。
-
@krOoze:“惯用的版本非常好,健壮,而且很难用错”并且被隐藏了。该函数不是 Vulkan API 的一部分;它在一个补充 API 中。许多人不使用该补充 API,因此自然会默认以正常方式使用。这就是成语的问题。它们要求您知道它们的存在。问题的重点是,如果现有方法也同样有效,那么成语的意义何在?如果现有方法不可靠,那么该成语如何更可靠,因为规范没有说明确保其可靠性?
-
@NicolBolas 他问的是模式,而不是 util 函数。 util 函数的重点是能够交换所有
vkEnumarate*和vkGet*命令,而不必以容易出错的方式手动为每个命令编写专门化。但他没有问这个。至于模式,大多数人都得出了相同的结论。有时,如果很确定它是不变的,可以编写较短的版本,但这很容易出错并且需要思考,以及为什么要进行另一种专门化以将jmp保存在热点之外。