Vulkan API:将渲染工作转移到自己的线程中
Vulkan API: moving rending work into its own thread
我正在学习 Khronos Vulkan API,但我很难跨多个线程进行同步。
我正在尝试将渲染工作分解到它自己的线程中。我的想法是有一个主线程,它将负责高级应用程序管理,重要的是,它将调用 VkQueuePresentKHR() 来呈现表面。然而,实际的 VkCmd* 渲染工作是在专用线程上完成的。
我希望我的应用程序使用三重缓冲,因为流畅的 60fps 对我来说非常重要。在视频演示中,NVIDIA 提到您需要每个帧缓冲区有 1 个 VkCommandPool,并且在每次使用后清除整个池;这比尝试清理单个命令缓冲区更有效。我要更进一步:我正在尝试将渲染工作分解为 6 个队列:
- 3 个图形队列:每个帧都有自己的专用队列。
- 1 个用于演示。
- 1 次杂项图形命令。
- 1 用于专门的传输工作
我的GTX770显卡有16个通用队列+1个专用传输队列,所以这应该没问题。每个队列都有自己的 VkCommandPool。 Queues、CommandPool、CommandBuffer对象在主线程中创建,然后在Render线程中使用。
我 运行 遇到以下问题:
- 在渲染线程中调用 VkAcquireNextImageKHR。如果线程获取 3 张图像,那么我不能再次调用该函数并依赖它阻塞直到下一张图像可用。相反,我得到了一个验证层错误,表明我已经获取了所有可用的图像。而且,无论如何,我更愿意检测到当前没有图像可用,这样我就可以在线程中做其他工作,比如整理内存碎片。我如何知道当前已获取所有图像,以及我不应该调用 vkAcquireNextImageKHR。
- 如何确定 vkQueuePresentKHR() 是否已完成呈现,以及我是否可以发送另一个要呈现的帧?我不知道如何判断何时可以再次开始使用该帧缓冲区。
- 在 vkQueueSubmit() returns 之后立即清除 CommandPool 是不安全的,对吗?要清除 CommandPool,我是否需要监视提供给 vkQueueSubmit() 的围栏?阅读规范对我来说并不完全清楚。
- 我知道从多个线程中使用 CommandPool 是不安全的,但是从一个线程中使用多个 Queues/CommandPools 可以吗?
- 我可能做错了什么,但看起来在某些栅栏上调用 vkResetFence() 之后,你不能在它上面调用 vkGetFenceStatus(),否则你会得到一个验证层错误。这个对吗?
vkAcquireNextImageKHR
获取图像的所有权,并且 vkQueuePresentKHR
将其所有权释放回呈现引擎。 PE 需要拥有它当前显示的图像,有时还需要拥有其他图像。获取将阻塞,直到图像可用或超时到期。当无法依赖 Acquire 到 return 时,您会收到验证错误,因为您没有提供足够的图像。规范说:
Let n be the total number of images in the swapchain, m be the value of VkSurfaceCapabilitiesKHR::minImageCount
, and a be the number of presentable images that the application has currently acquired (i.e. images acquired with vkAcquireNextImageKHR
, but not yet presented with vkQueuePresentKHR
). vkAcquireNextImageKHR
can always succeed if a ≤ n - m at the time vkAcquireNextImageKHR
is called. vkAcquireNextImageKHR
should not be called if a > n - m with a timeout of UINT64_MAX
; in such a case, vkAcquireNextImageKHR
may block indefinitely.
您传递给 vkAcquireNextImageKHR
的 semaphore/fence 将在 PE 完成从图像读取时发出信号。届时,您可以再次开始写入,并在准备好时出示它。
只要其中的任何命令缓冲区仍在执行,您就无法清除命令池。当栅栏传递给 vkQueueSubmit
信号时,你知道它们何时完成执行。
是的,您可以在单个线程中使用任意数量的任意类型的对象。 Vulkan 实现中的任何内容都不会绑定到它所使用的特定线程。从多个线程并发访问对象的限制类似于您对自己的对象的限制:某些对象在内部序列化,因此可以并发访问,而其他对象则不行。
这好像不对。您应该可以随时在任何有效的围栏上调用 vkGetFenceStatus
。
我正在学习 Khronos Vulkan API,但我很难跨多个线程进行同步。
我正在尝试将渲染工作分解到它自己的线程中。我的想法是有一个主线程,它将负责高级应用程序管理,重要的是,它将调用 VkQueuePresentKHR() 来呈现表面。然而,实际的 VkCmd* 渲染工作是在专用线程上完成的。
我希望我的应用程序使用三重缓冲,因为流畅的 60fps 对我来说非常重要。在视频演示中,NVIDIA 提到您需要每个帧缓冲区有 1 个 VkCommandPool,并且在每次使用后清除整个池;这比尝试清理单个命令缓冲区更有效。我要更进一步:我正在尝试将渲染工作分解为 6 个队列:
- 3 个图形队列:每个帧都有自己的专用队列。
- 1 个用于演示。
- 1 次杂项图形命令。
- 1 用于专门的传输工作
我的GTX770显卡有16个通用队列+1个专用传输队列,所以这应该没问题。每个队列都有自己的 VkCommandPool。 Queues、CommandPool、CommandBuffer对象在主线程中创建,然后在Render线程中使用。
我 运行 遇到以下问题:
- 在渲染线程中调用 VkAcquireNextImageKHR。如果线程获取 3 张图像,那么我不能再次调用该函数并依赖它阻塞直到下一张图像可用。相反,我得到了一个验证层错误,表明我已经获取了所有可用的图像。而且,无论如何,我更愿意检测到当前没有图像可用,这样我就可以在线程中做其他工作,比如整理内存碎片。我如何知道当前已获取所有图像,以及我不应该调用 vkAcquireNextImageKHR。
- 如何确定 vkQueuePresentKHR() 是否已完成呈现,以及我是否可以发送另一个要呈现的帧?我不知道如何判断何时可以再次开始使用该帧缓冲区。
- 在 vkQueueSubmit() returns 之后立即清除 CommandPool 是不安全的,对吗?要清除 CommandPool,我是否需要监视提供给 vkQueueSubmit() 的围栏?阅读规范对我来说并不完全清楚。
- 我知道从多个线程中使用 CommandPool 是不安全的,但是从一个线程中使用多个 Queues/CommandPools 可以吗?
- 我可能做错了什么,但看起来在某些栅栏上调用 vkResetFence() 之后,你不能在它上面调用 vkGetFenceStatus(),否则你会得到一个验证层错误。这个对吗?
vkAcquireNextImageKHR
获取图像的所有权,并且vkQueuePresentKHR
将其所有权释放回呈现引擎。 PE 需要拥有它当前显示的图像,有时还需要拥有其他图像。获取将阻塞,直到图像可用或超时到期。当无法依赖 Acquire 到 return 时,您会收到验证错误,因为您没有提供足够的图像。规范说:Let n be the total number of images in the swapchain, m be the value of
VkSurfaceCapabilitiesKHR::minImageCount
, and a be the number of presentable images that the application has currently acquired (i.e. images acquired withvkAcquireNextImageKHR
, but not yet presented withvkQueuePresentKHR
).vkAcquireNextImageKHR
can always succeed if a ≤ n - m at the timevkAcquireNextImageKHR
is called.vkAcquireNextImageKHR
should not be called if a > n - m with a timeout ofUINT64_MAX
; in such a case,vkAcquireNextImageKHR
may block indefinitely.您传递给
vkAcquireNextImageKHR
的 semaphore/fence 将在 PE 完成从图像读取时发出信号。届时,您可以再次开始写入,并在准备好时出示它。只要其中的任何命令缓冲区仍在执行,您就无法清除命令池。当栅栏传递给
vkQueueSubmit
信号时,你知道它们何时完成执行。是的,您可以在单个线程中使用任意数量的任意类型的对象。 Vulkan 实现中的任何内容都不会绑定到它所使用的特定线程。从多个线程并发访问对象的限制类似于您对自己的对象的限制:某些对象在内部序列化,因此可以并发访问,而其他对象则不行。
这好像不对。您应该可以随时在任何有效的围栏上调用
vkGetFenceStatus
。