如何测量伏尔坎管道的执行时间 [英] How to measure execution time of Vulkan pipeline
问题描述
摘要
我希望能够在GPU上测量运行整个图形流水线的时间(以毫秒为单位)。目标:能够在优化代码之前/之后保存基准(下一步将是mipmap纹理)以查看改进。这在OpenGL中非常简单,但我是Vulkan新手,可能需要一些帮助。我已经浏览了相关的现有答案(here和here),但它们确实没有太大帮助。而且我在任何地方都找不到代码示例,所以我敢在这里问。
通过文档页,我发现了几个我认为应该使用的函数,因此我准备了如下内容:
1:创建查询池
void CreateQueryPool()
{
VkQueryPoolCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO;
createInfo.pNext = nullptr; // Optional
createInfo.flags = 0; // Reserved for future use, must be 0!
createInfo.queryType = VK_QUERY_TYPE_TIMESTAMP;
createInfo.queryCount = mCommandBuffers.size() * 2; // REVIEW
VkResult result = vkCreateQueryPool(mDevice, &createInfo, nullptr, &mTimeQueryPool);
if (result != VK_SUCCESS)
{
throw std::runtime_error("Failed to create time query pool!");
}
}
我的想法是queryCount = mCommandBuffers.size() * 2
在呈现前后留出空间来放置单独的查询时间戳,但是我不知道这个假设是否正确。
2:录制命令缓冲区
// recording command buffer i:
vkCmdWriteTimestamp(mCommandBuffers[i], VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, mTimeQueryPool, i);
// render pass ...
vkCmdWriteTimestamp(mCommandBuffers[i], VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, mTimeQueryPool, i);
vkCmdCopyQueryPoolResults(/* many parameters here */);
我想澄清几点:
- 写入同一查询索引的顺序是什么?我是否需要两个单独的查询池-一个用于渲染前,另一个用于渲染后?
- 我应该如何处理同步?我假设每个命令缓冲区都有单独的查询。
- 对于包含查询结果的目标缓冲区,是否可以使用";host可见位";存储在某个位置,或者我是否需要为";设备仅可见";存储临时内存?我在这个问题上也有点迷茫。
我找不到任何有关如何测量渲染时间的在线示例,但我只是假设这是一项非常常见的任务,因此肯定有某个示例。
推荐答案
所以,多亏了@karlschultz,我设法让一些东西正常工作。因此,如果其他人也在寻找同样的答案,我决定在这里发布我的发现。对于外面的伏尔坎专家:如果我犯了明显的错误,请告诉我,我会在这里纠正它们!
查询池创建
我按照问题中的描述填写了一个VkQueryPoolCreateInfo
结构,并让它的queryCount
字段等于命令缓冲区数的两倍,以便在之前和呈现后存储查询空间。
这里重要的是在使用查询之前重置查询池中的所有条目,和在写入查询之后重置查询。这需要进行一些更改:
1)询问图形队列是否支持时间戳
挑选图形队列族时,结构VkQueueFamilyProperties
有一个字段timestampValidBits
必须大于0,否则该队列族不能用于时间戳查询!
2)确定时间戳周期
物理设备包含一个特殊值,该值指示时间戳查询递增1所需的纳秒数。这对于将查询结果解释为例如纳秒或毫秒是必需的。该值是float
,可以通过调用vkGetPhysicalDeviceProperties
并查看字段VkPhysicalDeviceProperties.limits.timestampPeriod
来检索。
3)请求查询重置支持
在创建逻辑设备期间,必须填写结构并将其添加到pNext
链中才能启用主机查询重置功能:
VkDeviceCreateInfo createInfo{};
VkPhysicalDeviceHostQueryResetFeatures resetFeatures;
resetFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_HOST_QUERY_RESET_FEATURES;
resetFeatures.pNext = nullptr;
resetFeatures.hostQueryReset = VK_TRUE;
createInfo.pNext = &resetFeatures;
4)录制命令缓冲区
时间戳查询应该在呈现过程的范围之外,如下所示。由于流水线阶段的(潜在)时间重叠,无法测量单个着色器(例如片段着色器)、仅整个流水线或渲染过程范围之外的任何内容的运行时间。
vkCmdWriteTimestamp(mCommandBuffers[i], VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, mTimeQueryPool, i * 2);
vkCmdBeginRenderPass(/* ... */);
// render here...
vkCmdEndRenderPass(mCommandBuffers[i]);
vkCmdWriteTimestamp(mCommandBuffers[i], VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, mTimeQueryPool, i * 2 + 1);
5)检索查询结果
我们有两种方法:vkCmdCopyQueryPoolResults
和vkGetQueryPoolResults
。我选择使用后者,因为它极大地简化了设置,并且不需要与GPU缓冲区同步。
假设我有一个交换链索引(在我的场景中与命令缓冲区索引相同!),我的设置如下:
void FetchRenderTimeResults(uint32_t swapchainIndex)
{
uint64_t buffer[2];
VkResult result = vkGetQueryPoolResults(mDevice, mTimeQueryPool, swapchainIndex * 2, 2, sizeof(uint64_t) * 2, buffer, sizeof(uint64_t),
VK_QUERY_RESULT_64_BIT);
if (result == VK_NOT_READY)
{
return;
}
else if (result == VK_SUCCESS)
{
mTimeQueryResults[swapchainIndex] = buffer[1] - buffer[0];
}
else
{
throw std::runtime_error("Failed to receive query results!");
}
// Queries must be reset after each individual use.
vkResetQueryPool(mDevice, mTimeQueryPool, swapchainIndex * 2, 2);
}
变量mTimeQueryResults
引用包含每个交换链结果的std::vector<uint64_t>
。我使用它通过步骤2)中确定的时间戳周期计算每秒的平均渲染时间。
不要忘了调用vkDestroyQueryPool
清理查询池。
省略了很多细节,对于像我这样的Vulkan新手来说,这个设置非常可怕,花了几天时间才弄清楚。希望这能免除其他人的头疼。
有关详细信息,请参阅documentation。
这篇关于如何测量伏尔坎管道的执行时间的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!