From 6556345941bc3d3e910ddcde9b655bb1efd827a9 Mon Sep 17 00:00:00 2001 From: Philip Rideout Date: Thu, 31 Jan 2019 15:35:47 -0800 Subject: [PATCH] Vulkan: support WaitForIdle before swap chain construction. --- filament/docs/Vulkan.md.html | 12 +++- filament/src/driver/vulkan/VulkanDriver.cpp | 3 +- .../src/driver/vulkan/VulkanDriverImpl.cpp | 65 +++++++++---------- filament/src/driver/vulkan/VulkanDriverImpl.h | 2 +- filament/src/driver/vulkan/VulkanHandles.cpp | 4 +- 5 files changed, 45 insertions(+), 41 deletions(-) diff --git a/filament/docs/Vulkan.md.html b/filament/docs/Vulkan.md.html index 81467464ed..75e2724f26 100644 --- a/filament/docs/Vulkan.md.html +++ b/filament/docs/Vulkan.md.html @@ -11,7 +11,7 @@ **Vulkan Backend** - *Last updated: 27 July 2018* + *Last updated: 31 Jan 2019* ![](../../docs/images/filament_logo.png) @@ -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 diff --git a/filament/src/driver/vulkan/VulkanDriver.cpp b/filament/src/driver/vulkan/VulkanDriver.cpp index e983435caf..3d04413313 100644 --- a/filament/src/driver/vulkan/VulkanDriver.cpp +++ b/filament/src/driver/vulkan/VulkanDriver.cpp @@ -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(); diff --git a/filament/src/driver/vulkan/VulkanDriverImpl.cpp b/filament/src/driver/vulkan/VulkanDriverImpl.cpp index 747154f0f7..15ceb7b358 100644 --- a/filament/src/driver/vulkan/VulkanDriverImpl.cpp +++ b/filament/src/driver/vulkan/VulkanDriverImpl.cpp @@ -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); } diff --git a/filament/src/driver/vulkan/VulkanDriverImpl.h b/filament/src/driver/vulkan/VulkanDriverImpl.h index 3225ec9a8f..51b8687e54 100644 --- a/filament/src/driver/vulkan/VulkanDriverImpl.h +++ b/filament/src/driver/vulkan/VulkanDriverImpl.h @@ -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& candidates, VkImageTiling tiling, VkFormatFeatureFlags features); diff --git a/filament/src/driver/vulkan/VulkanHandles.cpp b/filament/src/driver/vulkan/VulkanHandles.cpp index 550fd1babb..d45bf38a76 100644 --- a/filament/src/driver/vulkan/VulkanHandles.cpp +++ b/filament/src/driver/vulkan/VulkanHandles.cpp @@ -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); }); };