延迟主机操作

某些 Vulkan 命令对于主机 CPU 执行来说本质上是昂贵的。通常希望将此类工作卸载到后台线程,并在多个 CPU 上并行执行该工作。延迟操作的概念允许应用程序和驱动程序使用应用程序管理的线程池来协调昂贵的主机命令的执行。

VK_KHR_deferred_host_operations 扩展定义了可延迟命令的基础结构和使用模式,但没有指定任何命令为可延迟的。这留给其他依赖的扩展。除非另一个依赖于 VK_KHR_deferred_host_operations 的扩展明确允许延迟,否则命令不得被延迟。本规范将此类扩展称为延迟扩展

请求延迟

当应用程序请求操作延迟时,实现可以延迟该操作。当请求延迟并且实现延迟任何操作时,如果没有发生错误,则实现必须返回 VK_OPERATION_DEFERRED_KHR 作为成功代码。当请求延迟时,当工作负载很大时,实现应该延迟操作,但是如果实现选择不延迟任何请求的操作而立即执行所有操作,如果没有发生错误,则实现必须返回 VK_OPERATION_NOT_DEFERRED_KHR 作为成功代码。

延迟操作的创建是完成的,初始结果值为 VK_SUCCESS。当操作已使用该延迟操作对象成功延迟时,延迟操作将变为待处理

在延迟操作完成之前,该操作被视为待处理。当延迟操作已被一个或多个线程完全执行时,待处理的延迟操作将变为完成。在应用程序线程使用 vkDeferredOperationJoinKHR加入之前,待处理的延迟操作永远不会完成。应用程序可以将多个线程加入到同一延迟操作,从而实现该操作中子任务的并发执行。

在延迟操作进入完成状态之前,实现可能随时访问请求延迟操作的命令的参数。当延迟操作处于待处理状态时,应用程序必须遵守以下规则

  • 不得访问外部同步的参数。

  • 不得修改指针参数(例如,重新分配/释放)。

  • 命令可以读取的指针参数的内容不得修改。

  • 命令可以写入的指针参数的内容不得读取。

  • Vulkan 对象参数不得作为外部同步参数传递给任何其他命令。

当延迟操作完成时,应用程序应该调用 vkGetDeferredOperationResultKHR 以获取 VkResult,指示操作的成功或失败。返回的 VkResult 值将是请求延迟操作的命令能够返回的值之一。对请求命令的输出参数的写入将在延迟操作完成之前发生。

当请求命令的延迟时,如 内存分配章节中所述,实现可以对提供给 vkCreateDeferredOperationKHR 的分配器执行内存管理操作,用于延迟操作对象。此类分配必须在请求延迟的线程上发生。

如果在请求延迟时为延迟命令提供了分配器,则实现可以在执行 vkDeferredOperationJoinKHR 期间对该分配器执行内存管理操作。这些操作可以并发发生,并且可以由任何加入的线程执行。应用程序必须确保提供的分配器能够在这些条件下正常运行。

延迟主机操作 API

VkDeferredOperationKHR 句柄定义为

// Provided by VK_KHR_deferred_host_operations
VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkDeferredOperationKHR)

此句柄引用管理延迟命令执行状态的跟踪结构。

要构建延迟命令的跟踪对象,请调用

// Provided by VK_KHR_deferred_host_operations
VkResult vkCreateDeferredOperationKHR(
    VkDevice                                    device,
    const VkAllocationCallbacks*                pAllocator,
    VkDeferredOperationKHR*                     pDeferredOperation);
  • device 是拥有 operation 的设备。

  • pAllocator 控制主机内存分配,如内存分配章节所述。

  • pDeferredOperation 是一个指向句柄的指针,创建的 VkDeferredOperationKHR 将返回到该句柄中。

有效使用(隐式)
  • VUID-vkCreateDeferredOperationKHR-device-parameter
    device 必须是有效的 VkDevice 句柄

  • VUID-vkCreateDeferredOperationKHR-pAllocator-parameter
    如果 pAllocator 不是 NULL,则 pAllocator 必须是指向有效 VkAllocationCallbacks 结构的有效指针

  • VUID-vkCreateDeferredOperationKHR-pDeferredOperation-parameter
    pDeferredOperation 必须是指向 VkDeferredOperationKHR 句柄的有效指针

返回代码
成功
  • VK_SUCCESS

失败
  • VK_ERROR_OUT_OF_HOST_MEMORY

要将线程分配给延迟操作,请调用

// Provided by VK_KHR_deferred_host_operations
VkResult vkDeferredOperationJoinKHR(
    VkDevice                                    device,
    VkDeferredOperationKHR                      operation);
  • device 是拥有 operation 的设备。

  • operation 是调用线程应处理的延迟操作。

vkDeferredOperationJoinKHR 命令将在调用线程上执行延迟操作的一部分。

返回值将是以下值之一

  • 返回 VK_SUCCESS 表示 operation 已完成。应用程序应该使用 vkGetDeferredOperationResultKHR 来检索 operation 的结果。

  • 返回 VK_THREAD_DONE_KHR 表示延迟操作未完成,但没有剩余的工作可以分配给线程。未来调用 vkDeferredOperationJoinKHR 是不必要的,只会损害性能。当其他执行 vkDeferredOperationJoinKHR 的线程即将完成 operation,并且实现无法进一步划分工作负载时,可能会发生这种情况。

  • 返回 VK_THREAD_IDLE_KHR 表示延迟操作未完成,并且在调用时没有线程可执行的工作。如果操作遇到临时并行性降低,则可能会发生这种情况。通过返回 VK_THREAD_IDLE_KHR,实现表示它期望随着执行的进行,将出现更多并行机会,并且未来调用 vkDeferredOperationJoinKHR 可以是有益的。同时,应用程序可以在调用线程上执行其他工作。

实现必须通过强制执行以下不变式来保证向前推进

  1. 如果只有一个线程在给定的操作上调用了 vkDeferredOperationJoinKHR,则该线程必须执行该操作直至完成并返回 VK_SUCCESS

  2. 如果多个线程在同一操作上并发调用 vkDeferredOperationJoinKHR,那么至少其中一个线程必须完成该操作并返回 VK_SUCCESS

有效使用(隐式)
  • VUID-vkDeferredOperationJoinKHR-device-parameter
    device 必须是有效的 VkDevice 句柄

  • VUID-vkDeferredOperationJoinKHR-operation-parameter
    operation 必须是有效的 VkDeferredOperationKHR 句柄

  • VUID-vkDeferredOperationJoinKHR-operation-parent
    operation 必须是从 device 创建、分配或检索的

返回代码
成功
  • VK_SUCCESS

  • VK_THREAD_DONE_KHR

  • VK_THREAD_IDLE_KHR

失败
  • VK_ERROR_OUT_OF_HOST_MEMORY

  • VK_ERROR_OUT_OF_DEVICE_MEMORY

当延迟操作完成时,应用程序可以通过调用以下命令销毁跟踪对象

// Provided by VK_KHR_deferred_host_operations
void vkDestroyDeferredOperationKHR(
    VkDevice                                    device,
    VkDeferredOperationKHR                      operation,
    const VkAllocationCallbacks*                pAllocator);
  • device 是拥有 operation 的设备。

  • operation 是要销毁的已完成操作。

  • pAllocator 控制主机内存分配,如内存分配章节所述。

有效使用
  • VUID-vkDestroyDeferredOperationKHR-operation-03434
    如果在创建 operation 时提供了 VkAllocationCallbacks,则此处必须提供一组兼容的回调

  • VUID-vkDestroyDeferredOperationKHR-operation-03435
    如果在创建 operation 时未提供 VkAllocationCallbacks,则 pAllocator 必须NULL

  • VUID-vkDestroyDeferredOperationKHR-operation-03436
    operation 必须已完成

有效使用(隐式)
  • VUID-vkDestroyDeferredOperationKHR-device-parameter
    device 必须是有效的 VkDevice 句柄

  • VUID-vkDestroyDeferredOperationKHR-operation-parameter
    如果 operation 不是 VK_NULL_HANDLE,则 operation 必须是有效的 VkDeferredOperationKHR 句柄

  • VUID-vkDestroyDeferredOperationKHR-pAllocator-parameter
    如果 pAllocator 不是 NULL,则 pAllocator 必须是指向有效 VkAllocationCallbacks 结构的有效指针

  • VUID-vkDestroyDeferredOperationKHR-operation-parent
    如果 operation 是有效的句柄,则它必须是从 device 创建、分配或检索的

主机同步
  • operation 的主机访问必须进行外部同步

要查询可以有效地加入延迟操作的其他线程数,请调用

// Provided by VK_KHR_deferred_host_operations
uint32_t vkGetDeferredOperationMaxConcurrencyKHR(
    VkDevice                                    device,
    VkDeferredOperationKHR                      operation);
  • device 是拥有 operation 的设备。

  • operation 是要查询的延迟操作。

返回的值是可以有效地并发执行延迟操作的最大线程数,该值是针对调用此命令时的延迟操作状态报告的。此值旨在更好地将工作调度到可用线程上。应用程序可以将任意数量的线程加入到延迟操作中,并期望它最终完成,尽管过多的加入可能会立即返回 VK_THREAD_DONE_KHR,从而不执行任何有用的工作。

如果 operation 已完成,则 vkGetDeferredOperationMaxConcurrencyKHR 返回零。

如果 operation 当前已加入到任何线程,则此命令返回的值可能会立即过期。

如果 operation 处于挂起状态,除非当前至少有一个线程正在对 operation 执行 vkDeferredOperationJoinKHR,否则实现必须不返回零。如果存在此类线程,则实现应该返回一个估计值,表示它可以有效利用的额外线程数量。

实现可以返回 232-1 以表示最大并发数未知且无法轻易推导。实现可以返回大于主机 CPU 上可用最大并发数的值。在这种情况下,应用程序应该限制返回值,而不是过度订阅机器。

应用程序的推荐使用模式是在延迟操作后查询此值一次,并安排不超过指定数量的线程来加入该操作。每次加入的线程收到 VK_THREAD_IDLE_KHR 时,应用程序应在未来的某个时间安排额外的加入,但这不是必需的。

有效使用(隐式)
  • VUID-vkGetDeferredOperationMaxConcurrencyKHR-device-parameter
    device 必须是有效的 VkDevice 句柄

  • VUID-vkGetDeferredOperationMaxConcurrencyKHR-operation-parameter
    operation 必须是有效的 VkDeferredOperationKHR 句柄

  • VUID-vkGetDeferredOperationMaxConcurrencyKHR-operation-parent
    operation 必须是从 device 创建、分配或检索的

vkGetDeferredOperationResultKHR 函数定义如下:

// Provided by VK_KHR_deferred_host_operations
VkResult vkGetDeferredOperationResultKHR(
    VkDevice                                    device,
    VkDeferredOperationKHR                      operation);
  • device 是拥有 operation 的设备。

  • operation 是正在查询其延迟结果的操作。

如果 operation 上没有延迟的命令,vkGetDeferredOperationResultKHR 返回 VK_SUCCESS

如果延迟操作处于挂起状态,vkGetDeferredOperationResultKHR 返回 VK_NOT_READY

如果延迟操作已完成,则它将返回原始命令的相应返回值。此值必须是如果操作未被延迟,则原始命令可能返回的 VkResult 值之一。

有效使用(隐式)
  • VUID-vkGetDeferredOperationResultKHR-device-parameter
    device 必须是有效的 VkDevice 句柄

  • VUID-vkGetDeferredOperationResultKHR-operation-parameter
    operation 必须是有效的 VkDeferredOperationKHR 句柄

  • VUID-vkGetDeferredOperationResultKHR-operation-parent
    operation 必须是从 device 创建、分配或检索的

返回代码
成功
  • VK_SUCCESS

  • VK_NOT_READY

失败