Vulkan: support WaitForIdle before swap chain construction.
This commit is contained in:
@@ -11,7 +11,7 @@
|
||||
</style>
|
||||
|
||||
**Vulkan Backend**
|
||||
*Last updated: 27 July 2018*
|
||||
*Last updated: 31 Jan 2019*
|
||||
|
||||

|
||||
|
||||
@@ -81,6 +81,16 @@ override; it should have no private methods other than the handle allocators. Th
|
||||
complexity because it must implement every method in `DriverAPI` while dealing with Vulkan itself,
|
||||
which has an extremely low-level API.
|
||||
|
||||
## Command buffers
|
||||
|
||||
We have a separate `VkCommandBuffer` for each element in the swap context. This allows us to
|
||||
populate command buffer "A" while the GPU is busy consuming command buffer "B". Thus, we typically
|
||||
have two persistent `VkCommandBuffer` objects: for each element in the swap chain.
|
||||
|
||||
However, note that we can receive Driver API requests before the SwapChain has been created. One
|
||||
typical example is uploading texture data. For these types of operations, we simply create a one-off
|
||||
command buffer (which is supposedly a lightweight operation in Vulkan) and execute work on it.
|
||||
|
||||
## Handle types
|
||||
|
||||
This sole purpose of `VulkanHandles.h` is to declare structs that descend from the hardware handles
|
||||
|
||||
@@ -228,7 +228,8 @@ void VulkanDriver::beginFrame(int64_t monotonic_clock_ns, uint32_t frameId) {
|
||||
// VulkanDriver, such as reclaiming memory. There are two sets of work queues: one in the
|
||||
// global context, and one in the per-cmdbuffer contexts. Crucially, we have begun the frame
|
||||
// but not the render pass; we cannot perform arbitrary work during the render pass.
|
||||
performPendingWork(mContext, swapContext, swapContext.cmdbuffer);
|
||||
performPendingWork(swapContext.pendingWork, swapContext.cmdbuffer);
|
||||
performPendingWork(mContext.pendingWork, swapContext.cmdbuffer);
|
||||
|
||||
// Free old unused objects.
|
||||
mStagePool.gc();
|
||||
|
||||
@@ -788,24 +788,21 @@ void waitForIdle(VulkanContext& context) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If there's no surface, then there's no command buffer.
|
||||
if (!context.currentSurface) {
|
||||
return;
|
||||
}
|
||||
|
||||
// First, wait for submitted command buffer(s) to finish.
|
||||
VkFence fences[2];
|
||||
uint32_t nfences = 0;
|
||||
auto& surfaceContext = *context.currentSurface;
|
||||
for (auto& swapContext : surfaceContext.swapContexts) {
|
||||
assert(nfences < 2);
|
||||
if (swapContext.submitted && swapContext.fence) {
|
||||
fences[nfences++] = swapContext.fence;
|
||||
swapContext.submitted = false;
|
||||
if (context.currentSurface) {
|
||||
VkFence fences[2];
|
||||
uint32_t nfences = 0;
|
||||
auto& surfaceContext = *context.currentSurface;
|
||||
for (auto& swapContext : surfaceContext.swapContexts) {
|
||||
assert(nfences < 2);
|
||||
if (swapContext.submitted && swapContext.fence) {
|
||||
fences[nfences++] = swapContext.fence;
|
||||
swapContext.submitted = false;
|
||||
}
|
||||
}
|
||||
if (nfences > 0) {
|
||||
vkWaitForFences(context.device, nfences, fences, VK_FALSE, ~0ull);
|
||||
}
|
||||
}
|
||||
if (nfences > 0) {
|
||||
vkWaitForFences(context.device, nfences, fences, VK_FALSE, ~0ull);
|
||||
}
|
||||
|
||||
// If we don't have any pending work, we're done.
|
||||
@@ -847,15 +844,18 @@ void waitForIdle(VulkanContext& context) {
|
||||
utils::slog.e << "Unexpected daisychaining of pending work." << utils::io::endl;
|
||||
break;
|
||||
}
|
||||
for (auto& swapContext : context.currentSurface->swapContexts) {
|
||||
vkBeginCommandBuffer(cmdbuffer, &beginInfo);
|
||||
performPendingWork(context, swapContext, cmdbuffer);
|
||||
vkEndCommandBuffer(cmdbuffer);
|
||||
vkQueueSubmit(context.graphicsQueue, 1, &submitInfo, fence);
|
||||
vkWaitForFences(context.device, 1, &fence, VK_FALSE, UINT64_MAX);
|
||||
vkResetFences(context.device, 1, &fence);
|
||||
vkResetCommandBuffer(cmdbuffer, 0);
|
||||
vkBeginCommandBuffer(cmdbuffer, &beginInfo);
|
||||
if (context.currentSurface) {
|
||||
for (auto& swapContext : context.currentSurface->swapContexts) {
|
||||
performPendingWork(swapContext.pendingWork, cmdbuffer);
|
||||
}
|
||||
}
|
||||
performPendingWork(context.pendingWork, cmdbuffer);
|
||||
vkEndCommandBuffer(cmdbuffer);
|
||||
vkQueueSubmit(context.graphicsQueue, 1, &submitInfo, fence);
|
||||
vkWaitForFences(context.device, 1, &fence, VK_FALSE, UINT64_MAX);
|
||||
vkResetFences(context.device, 1, &fence);
|
||||
vkResetCommandBuffer(cmdbuffer, 0);
|
||||
}
|
||||
vkFreeCommandBuffers(context.device, context.commandPool, 1, &cmdbuffer);
|
||||
vkDestroyFence(context.device, fence, VKALLOC);
|
||||
@@ -917,18 +917,11 @@ void releaseCommandBuffer(VulkanContext& context) {
|
||||
swapContext.submitted = true;
|
||||
}
|
||||
|
||||
void performPendingWork(VulkanContext& context, SwapContext& swapContext, VkCommandBuffer cmdbuf) {
|
||||
// First, execute pending tasks that are specific to this swap context. Copy the tasks into a
|
||||
// local queue first, which allows newly added tasks to be deferred until the next frame.
|
||||
decltype(swapContext.pendingWork) tasks;
|
||||
tasks.swap(swapContext.pendingWork);
|
||||
for (auto& callback : tasks) {
|
||||
callback(cmdbuf);
|
||||
}
|
||||
// Next, execute the global pending work. Again, we copy the work queue into a local queue
|
||||
// to allow tasks to re-add themselves.
|
||||
tasks.clear();
|
||||
tasks.swap(context.pendingWork);
|
||||
void performPendingWork(VulkanTaskQueue& work, VkCommandBuffer cmdbuf) {
|
||||
// Copy the tasks into a local queue first, which allows newly added tasks to be deferred until
|
||||
// the next frame.
|
||||
VulkanTaskQueue tasks;
|
||||
tasks.swap(work);
|
||||
for (auto& callback : tasks) {
|
||||
callback(cmdbuf);
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ VkFrontFace getFrontFace(bool inverseFrontFaces);
|
||||
void waitForIdle(VulkanContext& context);
|
||||
void acquireCommandBuffer(VulkanContext& context);
|
||||
void releaseCommandBuffer(VulkanContext& context);
|
||||
void performPendingWork(VulkanContext& context, SwapContext& swapContext, VkCommandBuffer cmdbuf);
|
||||
void performPendingWork(VulkanTaskQueue& work, VkCommandBuffer cmdbuf);
|
||||
void flushCommandBuffer(VulkanContext& context);
|
||||
VkFormat findSupportedFormat(VulkanContext& context, const std::vector<VkFormat>& candidates,
|
||||
VkImageTiling tiling, VkFormatFeatureFlags features);
|
||||
|
||||
@@ -514,7 +514,7 @@ void VulkanTexture::update2DImage(const PixelBufferDescriptor& data, uint32_t wi
|
||||
copyBufferToImage(cmd, stage->buffer, textureImage, width, height, nullptr, miplevel);
|
||||
transitionImageLayout(cmd, textureImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, miplevel, 1);
|
||||
getSwapContext(mContext).pendingWork.emplace_back([this, stage] (VkCommandBuffer) {
|
||||
mContext.pendingWork.emplace_back([this, stage] (VkCommandBuffer) {
|
||||
mStagePool.releaseStage(stage);
|
||||
});
|
||||
};
|
||||
@@ -556,7 +556,7 @@ void VulkanTexture::updateCubeImage(const PixelBufferDescriptor& data,
|
||||
copyBufferToImage(cmd, stage->buffer, textureImage, width, height, &faceOffsets, miplevel);
|
||||
transitionImageLayout(cmd, textureImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, miplevel, 6);
|
||||
getSwapContext(mContext).pendingWork.emplace_back([this, stage] (VkCommandBuffer) {
|
||||
mContext.pendingWork.emplace_back([this, stage] (VkCommandBuffer) {
|
||||
mStagePool.releaseStage(stage);
|
||||
});
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user