| // Copyright 2014-2021 The Khronos Group, Inc. |
| // |
| // SPDX-License-Identifier: CC-BY-4.0 |
| |
| [[deferred-host-operations]] |
| = Deferred Host Operations |
| |
| Certain Vulkan commands are inherently expensive for the host CPU to |
| execute. |
| It is often desirable to offload such work onto background threads, and to |
| parallelize the work across multiple CPUs. |
| The concept of _deferred operations_ allows applications and drivers to |
| coordinate the execution of expensive host commands using an |
| application-managed thread pool. |
| |
| The `apiext:VK_KHR_deferred_host_operations` extension defines the |
| infrastructure and usage patterns for _deferrable commands_, but does not |
| specify any commands as deferrable. |
| This is left to additional dependant extensions. |
| Commands must: not be deferred unless the deferral is specifically allowed |
| by another extension which depends on |
| `apiext:VK_KHR_deferred_host_operations`. |
| This specification will refer to such extensions as _deferral extensions_. |
| |
| |
| [[deferred-host-operations-requesting]] |
| == Requesting Deferral |
| |
| When an application requests an operation deferral, the implementation may: |
| defer the operation. |
| When deferral is requested and the implementation defers any operation, the |
| implementation must: return ename:VK_OPERATION_DEFERRED_KHR as the success |
| code if no errors occurred. |
| When deferral is requested, the implementation should: defer the operation |
| when the workload is significant, however if the implementation chooses not |
| to defer any of the requested operations and instead executes all of them |
| immediately, the implementation must: return |
| ename:VK_OPERATION_NOT_DEFERRED_KHR as the success code if no errors |
| occurred. |
| |
| A deferred operation is created _complete_ with an initial result value of |
| ename:VK_SUCCESS. |
| The deferred operation becomes _pending_ when an operation has been |
| successfully deferred with that deferred operation object. |
| |
| A deferred operation is considered pending until the deferred operation |
| completes. |
| A pending deferred operation becomes _complete_ when it has been fully |
| executed by one or more threads. |
| Pending deferred operations will never complete until they are _joined_ by |
| an application thread, using flink:vkDeferredOperationJoinKHR. |
| Applications can: join multiple threads to the same deferred operation, |
| enabling concurrent execution of subtasks within that operation. |
| |
| The application can: query the status of a slink:VkDeferredOperationKHR |
| using the flink:vkGetDeferredOperationMaxConcurrencyKHR or |
| flink:vkGetDeferredOperationResultKHR commands. |
| |
| Parameters to the command requesting a deferred operation may: be accessed |
| at any time until the deferred operation enters the pending state. |
| While a deferred operation is pending: |
| |
| * Externally synchronized parameters must: not be accessed. |
| * Pointer parameters must: not be modified (e.g. reallocated/freed). |
| * The contents of pointer parameters which may: be read by the command |
| must: not be modified. |
| * The contents of pointer parameters which may: be written by the command |
| must: not be read. |
| * Vulkan object parameters must: not be passed as externally synchronized |
| parameters to any other command. |
| |
| When the deferred operation is complete, the application should: call |
| flink:vkGetDeferredOperationResultKHR to obtain the elink:VkResult |
| indicating success or failure of the operation. |
| The elink:VkResult value returned will be one of the values that the command |
| requesting the deferred operation is able to return. |
| Writes to output parameters of the requesting command will happen-before the |
| deferred operation is complete. |
| |
| |
| == Deferred Host Operations API |
| |
| [open,refpage='VkDeferredOperationKHR',desc='A deferred operation',type='handles'] |
| -- |
| The sname:VkDeferredOperationKHR handle is defined as: |
| |
| include::{generated}/api/handles/VkDeferredOperationKHR.txt[] |
| |
| This handle refers to a tracking structure which manages the execution state |
| for a deferred command. |
| -- |
| |
| [open,refpage='vkCreateDeferredOperationKHR',desc='Create a deferred operation handle',type='protos'] |
| -- |
| :refpage: vkCreateDeferredOperationKHR |
| |
| To construct the tracking object for a deferred command, call: |
| |
| include::{generated}/api/protos/vkCreateDeferredOperationKHR.txt[] |
| |
| * pname:device is the device which owns pname:operation. |
| * pname:pAllocator controls host memory allocation as described in the |
| <<memory-allocation,Memory Allocation>> chapter. |
| * pname:pDeferredOperation is a pointer to a handle in which the created |
| slink:VkDeferredOperationKHR is returned. |
| |
| include::{generated}/validity/protos/vkCreateDeferredOperationKHR.txt[] |
| -- |
| |
| [open,refpage='vkDeferredOperationJoinKHR',desc='Assign a thread to a deferred operation',type='protos'] |
| -- |
| :refpage: vkDeferredOperationJoinKHR |
| |
| To assign a thread to a deferred operation, call: |
| |
| include::{generated}/api/protos/vkDeferredOperationJoinKHR.txt[] |
| |
| * pname:device is the device which owns pname:operation. |
| * pname:operation is the deferred operation that the calling thread should |
| work on. |
| |
| The fname:vkDeferredOperationJoinKHR command will execute a portion of the |
| deferred operation on the calling thread. |
| |
| The return value will be one of the following: |
| |
| * A return value of ename:VK_SUCCESS indicates that pname:operation is |
| complete. |
| The application should: use flink:vkGetDeferredOperationResultKHR to |
| retrieve the result of pname:operation. |
| * A return value of ename:VK_THREAD_DONE_KHR indicates that the deferred |
| operation is not complete, but there is no work remaining to assign to |
| threads. |
| Future calls to flink:vkDeferredOperationJoinKHR are not necessary and |
| will simply harm performance. |
| This situation may: occur when other threads executing |
| flink:vkDeferredOperationJoinKHR are about to complete pname:operation, |
| and the implementation is unable to partition the workload any further. |
| * A return value of ename:VK_THREAD_IDLE_KHR indicates that the deferred |
| operation is not complete, and there is no work for the thread to do at |
| the time of the call. |
| This situation may: occur if the operation encounters a temporary |
| reduction in parallelism. |
| By returning ename:VK_THREAD_IDLE_KHR, the implementation is signaling |
| that it expects that more opportunities for parallelism will emerge as |
| execution progresses, and that future calls to |
| flink:vkDeferredOperationJoinKHR can: be beneficial. |
| In the meantime, the application can: perform other work on the calling |
| thread. |
| |
| Implementations must: guarantee forward progress by enforcing the following |
| invariants: |
| |
| 1. If only one thread has invoked flink:vkDeferredOperationJoinKHR on a |
| given operation, that thread must: execute the operation to completion |
| and return ename:VK_SUCCESS. |
| 2. If multiple threads have concurrently invoked |
| flink:vkDeferredOperationJoinKHR on the same operation, then at least |
| one of them must: complete the operation and return ename:VK_SUCCESS. |
| |
| include::{generated}/validity/protos/vkDeferredOperationJoinKHR.txt[] |
| -- |
| |
| [open,refpage='vkDestroyDeferredOperationKHR',desc='Destroy a deferred operation handle',type='protos'] |
| -- |
| :refpage: vkDestroyDeferredOperationKHR |
| |
| When a deferred operation is completed, the application can: destroy the |
| tracking object by calling: |
| |
| include::{generated}/api/protos/vkDestroyDeferredOperationKHR.txt[] |
| |
| * pname:device is the device which owns pname:operation. |
| * pname:operation is the completed operation to be destroyed. |
| * pname:pAllocator controls host memory allocation as described in the |
| <<memory-allocation,Memory Allocation>> chapter. |
| |
| .Valid Usage |
| **** |
| * [[VUID-vkDestroyDeferredOperationKHR-operation-03434]] |
| If sname:VkAllocationCallbacks were provided when pname:operation was |
| created, a compatible set of callbacks must: be provided here |
| * [[VUID-vkDestroyDeferredOperationKHR-operation-03435]] |
| If no sname:VkAllocationCallbacks were provided when pname:operation was |
| created, pname:pAllocator must: be `NULL` |
| * [[VUID-vkDestroyDeferredOperationKHR-operation-03436]] |
| pname:operation must: be completed |
| **** |
| include::{generated}/validity/protos/vkDestroyDeferredOperationKHR.txt[] |
| -- |
| |
| [open,refpage='vkGetDeferredOperationMaxConcurrencyKHR',desc='Query the maximum concurrency on a deferred operation',type='protos'] |
| -- |
| :refpage: vkGetDeferredOperationMaxConcurrencyKHR |
| |
| To query the number of additional threads that can usefully be joined to a |
| deferred operation, call: |
| |
| include::{generated}/api/protos/vkGetDeferredOperationMaxConcurrencyKHR.txt[] |
| |
| * pname:device is the device which owns pname:operation. |
| * pname:operation is the deferred operation to be queried. |
| |
| The returned value is the maximum number of threads that can usefully |
| execute a deferred operation concurrently, reported for the state of the |
| deferred operation at the point this command is called. |
| This value is intended to be used to better schedule work onto available |
| threads. |
| Applications can: join any number of threads to the deferred operation and |
| expect it to eventually complete, though excessive joins may: return |
| ename:VK_THREAD_DONE_KHR immediately, performing no useful work. |
| |
| If pname:operation is complete, |
| fname:vkGetDeferredOperationMaxConcurrencyKHR returns zero. |
| |
| If pname:operation is currently joined to any threads, the value returned by |
| this command may: immediately be out of date. |
| |
| If pname:operation is pending, implementations must: not return zero unless |
| at least one thread is currently executing flink:vkDeferredOperationJoinKHR |
| on pname:operation. |
| If there are such threads, the implementation should: return an estimate of |
| the number of additional threads which it could profitably use. |
| |
| Implementations may: return [eq]#2^32^-1# to indicate that the maximum |
| concurrency is unknown and cannot be easily derived. |
| Implementations may: return values larger than the maximum concurrency |
| available on the host CPU. |
| In these situations, an application should: clamp the return value rather |
| than oversubscribing the machine. |
| |
| [NOTE] |
| .Note |
| ==== |
| The recommended usage pattern for applications is to query this value once, |
| after deferral, and schedule no more than the specified number of threads to |
| join the operation. |
| Each time a joined thread receives ename:VK_THREAD_IDLE_KHR, the application |
| should schedule an additional join at some point in the future, but is not |
| required to do so. |
| ==== |
| |
| include::{generated}/validity/protos/vkGetDeferredOperationMaxConcurrencyKHR.txt[] |
| -- |
| |
| [open,refpage='vkGetDeferredOperationResultKHR',desc='Query the result of a deferred operation',type='protos'] |
| -- |
| :refpage: vkGetDeferredOperationResultKHR |
| |
| The fname:vkGetDeferredOperationResultKHR function is defined as: |
| |
| include::{generated}/api/protos/vkGetDeferredOperationResultKHR.txt[] |
| |
| * pname:device is the device which owns pname:operation. |
| * pname:operation is the operation whose deferred result is being queried. |
| |
| If no command has been deferred on pname:operation, |
| fname:vkGetDeferredOperationResultKHR returns ename:VK_SUCCESS. |
| |
| If the deferred operation is pending, fname:vkGetDeferredOperationResultKHR |
| returns ename:VK_NOT_READY. |
| |
| If the deferred operation is complete, it returns the appropriate return |
| value from the original command. |
| This value must: be one of the elink:VkResult values which could have been |
| returned by the original command if the operation had not been deferred. |
| |
| include::{generated}/validity/protos/vkGetDeferredOperationResultKHR.txt[] |
| -- |
| |