Contributors to VK_KHR_acceleration_structure,
VK_KHR_ray_tracing_pipeline
Description
The 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 dependent
extensions. Commands must not be deferred unless the deferral is
specifically allowed by another extension which depends on
VK_KHR_deferred_host_operations.
The following examples will illustrate the concept of deferrable
operations using a hypothetical example. The command
vkDoSomethingExpensive denotes a deferrable command.
The following example illustrates how a vulkan application might request
deferral of an expensive operation:
// create a deferred operation
VkDeferredOperationKHR hOp;
VkResult result = vkCreateDeferredOperationKHR(device, pCallbacks, &hOp);
assert(result == VK_SUCCESS);
result = vkDoSomethingExpensive(device, hOp, ...);
assert( result == VK_OPERATION_DEFERRED_KHR );
// operation was deferred. Execute it asynchronously
std::async::launch(
[ hOp ] ( )
{
vkDeferredOperationJoinKHR(device, hOp);
result = vkGetDeferredOperationResultKHR(device, hOp);
// deferred operation is now complete. 'result' indicates success or failure
vkDestroyDeferredOperationKHR(device, hOp, pCallbacks);
}
);
The following example illustrates extracting concurrency from a single
deferred operation:
// create a deferred operation
VkDeferredOperationKHR hOp;
VkResult result = vkCreateDeferredOperationKHR(device, pCallbacks, &hOp);
assert(result == VK_SUCCESS);
result = vkDoSomethingExpensive(device, hOp, ...);
assert( result == VK_OPERATION_DEFERRED_KHR );
// Query the maximum amount of concurrency and clamp to the desired maximum
uint32_t numLaunches = std::min(vkGetDeferredOperationMaxConcurrencyKHR(device, hOp), maxThreads);
std::vector<std::future<void> > joins;
for (uint32_t i = 0; i < numLaunches; i++) {
joins.emplace_back(std::async::launch(
[ hOp ] ( )
{
vkDeferredOperationJoinKHR(device, hOp);
// in a job system, a return of VK_THREAD_IDLE_KHR should queue another
// job, but it is not functionally required
}
));
}
for (auto &f : joins) {
f.get();
}
result = vkGetDeferredOperationResultKHR(device, hOp);
// deferred operation is now complete. 'result' indicates success or failure
vkDestroyDeferredOperationKHR(device, hOp, pCallbacks);
The following example shows a subroutine which guarantees completion of
a deferred operation, in the presence of multiple worker threads, and
returns the result of the operation.
VkResult FinishDeferredOperation(VkDeferredOperationKHR hOp)
{
// Attempt to join the operation until the implementation indicates that we should stop
VkResult result = vkDeferredOperationJoinKHR(device, hOp);
while( result == VK_THREAD_IDLE_KHR )
{
std::this_thread::yield();
result = vkDeferredOperationJoinKHR(device, hOp);
}
switch( result )
{
case VK_SUCCESS:
{
// deferred operation has finished. Query its result.
result = vkGetDeferredOperationResultKHR(device, hOp);
}
break;
case VK_THREAD_DONE_KHR:
{
// deferred operation is being wrapped up by another thread
// wait for that thread to finish
do
{
std::this_thread::yield();
result = vkGetDeferredOperationResultKHR(device, hOp);
} while( result == VK_NOT_READY );
}
break;
default:
assert(false); // other conditions are illegal.
break;
}
return result;
}
Issues
Should this extension have a VkPhysicalDevice*FeaturesKHR structure?
RESOLVED: No. This extension does not add any functionality on its
own and requires a dependent extension to actually enable functionality
and thus there is no value in adding a feature structure. If necessary,
any dependent extension could add a feature boolean if it wanted to
indicate that it is adding optional deferral support.
Version History
Revision 1, 2019-12-05 (Josh Barczak, Daniel Koch)
To ensure that destroyDeferredOperationKHR is always called: pass
bracket (or the allocate function from your
favourite resource management library) as the last argument.
To just extract the pair pass (,) as the last argument.
vkGetDeferredOperationMaxConcurrencyKHR - Query the maximum concurrency
on a deferred operation
Description
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 THREAD_DONE_KHR immediately,
performing no useful work.
If operation is currently joined to any threads, the value returned by
this command may immediately be out of date.
If operation is pending, implementations must not return zero unless
at least one thread is currently executing deferredOperationJoinKHR on
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 232-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.
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
THREAD_IDLE_KHR, the application should
schedule an additional join at some point in the future, but is not
required to do so.
If the deferred operation is complete, it returns the appropriate return
value from the original command. This value must be one of the
Result values which could have been
returned by the original command if the operation had not been deferred.
vkDeferredOperationJoinKHR - Assign a thread to a deferred operation
Description
The deferredOperationJoinKHR 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 SUCCESS indicates
that operation is complete. The application should use
getDeferredOperationResultKHR to retrieve the result of
operation.
A return value of THREAD_DONE_KHR
indicates that the deferred operation is not complete, but there is
no work remaining to assign to threads. Future calls to
deferredOperationJoinKHR are not necessary and will simply harm
performance. This situation may occur when other threads executing
deferredOperationJoinKHR are about to complete operation, and
the implementation is unable to partition the workload any further.
A return value of 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
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
deferredOperationJoinKHRcan 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:
If only one thread has invoked deferredOperationJoinKHR on a given
operation, that thread must execute the operation to completion
and return SUCCESS.
If multiple threads have concurrently invoked
deferredOperationJoinKHR on the same operation, then at least one
of them must complete the operation and return
SUCCESS.