如何测量Vulkan管道的执行时间

Ale*_*der 7 time rendering vulkan

概括

我希望能够测量 GPU 上运行整个图形管道所花费的时间(以毫秒为单位)。目标:能够在优化代码之前/之后保存基准(下一步将是 mipmap 纹理)以查看改进。这在 OpenGL 中非常简单,但我是 Vulkan 的新手,需要一些帮助。

我浏览了相关的现有答案(此处此处),但它们并没有太大帮助。而且我在任何地方都找不到代码示例,所以我敢在这里提问。

通过文档页面,我发现了一些我认为应该使用的函数,因此我设置了如下所示的功能:

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!");
    }
}
Run Code Online (Sandbox Code Playgroud)

我的想法是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 */);
Run Code Online (Sandbox Code Playgroud)

我正在寻找一些澄清:

  • 写入同一个查询索引会产生什么后果?我是否需要两个单独的查询池 - 一个用于渲染前,一个用于渲染后?
  • 我应该如何处理同步?我假设每个命令缓冲区都有一个单独的查询。
  • 对于包含查询结果的目标缓冲区,是否足以存储具有“主机可见位”的地方,或者我是否需要暂存内存以用于“仅设备可见”?我对这个也有点迷失。

我无法找到任何有关如何测量渲染时间的在线示例,但我只是假设这是一项常见的任务,肯定在某个地方有一个示例。

Ale*_*der 10

所以,感谢@karlschultz,我设法让一些东西发挥作用。因此,为了防止其他人寻找相同的答案,我决定在这里发布我的发现。对于那里的 Vulkan 专家:如果我犯了明显的错误,请告诉我,我会在这里纠正它们!

查询池创建

VkQueryPoolCreateInfo我按照问题中的描述填写了一个结构体,并让它的字段等于命令缓冲区数量的两倍,以在渲染之前queryCount之后存储查询的空间。

这里重要的是在使用查询之前重置查询池中的所有条目,在写入查询后重置查询。这需要进行一些更改:

1)询问图形队列是否支持时间戳

当选择图形队列族时,结构体中VkQueueFamilyProperties有一个字段timestampValidBits必须大于0,否则队列族不能用于时间戳查询!

2)确定时间戳周期

物理设备包含一个特殊值,该值指示时间戳查询加 1 所需的纳秒数。这对于将查询结果解释为纳秒或毫秒是必要的。该值是 a 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;
Run Code Online (Sandbox Code Playgroud)

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);
Run Code Online (Sandbox Code Playgroud)

5) 检索查询结果

为此,我们有两种方法:vkCmdCopyQueryPoolResultsvkGetQueryPoolResults。我选择使用后者,因为它极大地简化了设置并且不需要与 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);
}
Run Code Online (Sandbox Code Playgroud)

该变量mTimeQueryResults指的是std::vector<uint64_t>包含每个交换链的结果的变量。我使用它通过使用步骤 2) 中确定的时间戳周期来计算每秒的平均渲染时间。

并且一定不要忘记通过调用 来清理查询池vkDestroyQueryPool

省略了很多细节,对于像我这样的 Vulkan 菜鸟来说,这个设置很可怕,花了几天时间才弄清楚。希望这能减轻其他人的头痛。

文档中的更多信息。

  • 这是一篇很好的文章。作为 (3) 的替代方案,您可以使用命令缓冲区中的“vkCmdResetQueryPool”命令重置查询池,该命令放置在写入时间戳的命令之前。这些与查询相关的命令存在隐式执行依赖性,因此重置将在写入时间戳之前完成。两种方法之间的选择可能取决于应用程序的其他方面,但使用基于设备的重置,您不需要启用设备功能并考虑与“vkResetQueryPool”的主机同步。 (2认同)