diff --git a/filament/backend/src/vulkan/VulkanCommands.cpp b/filament/backend/src/vulkan/VulkanCommands.cpp index ee8ffe24971..0dc16a578c2 100644 --- a/filament/backend/src/vulkan/VulkanCommands.cpp +++ b/filament/backend/src/vulkan/VulkanCommands.cpp @@ -33,453 +33,504 @@ using namespace utils; namespace filament::backend { + namespace { + #if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS) -using Timestamp = VulkanGroupMarkers::Timestamp; + using Timestamp = VulkanGroupMarkers::Timestamp; #endif -VulkanCmdFence::VulkanCmdFence(VkFence ifence) - : fence(ifence) { - // Internally we use the VK_INCOMPLETE status to mean "not yet submitted". When this fence gets - // submitted, its status changes to VK_NOT_READY. Finally, when the GPU actually finishes - // executing the command buffer, the status changes to VK_SUCCESS. - status.store(VK_INCOMPLETE); -} - -VulkanCommandBuffer::VulkanCommandBuffer(VulkanResourceAllocator* allocator, VkDevice device, - VkCommandPool pool) - : mResourceManager(allocator), - mPipeline(VK_NULL_HANDLE) { - // Create the low-level command buffer. - const VkCommandBufferAllocateInfo allocateInfo{ - .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, - .commandPool = pool, - .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, - .commandBufferCount = 1, - }; - - // The buffer allocated here will be implicitly reset when vkBeginCommandBuffer is called. - // We don't need to deallocate since destroying the pool will free all of the buffers. - vkAllocateCommandBuffers(device, &allocateInfo, &mBuffer); -} - -CommandBufferObserver::~CommandBufferObserver() {} - -static VkCommandPool createPool(VkDevice device, uint32_t queueFamilyIndex) { - VkCommandPoolCreateInfo createInfo = { - .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, - .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT - | VK_COMMAND_POOL_CREATE_TRANSIENT_BIT, - .queueFamilyIndex = queueFamilyIndex, - }; - VkCommandPool pool; - vkCreateCommandPool(device, &createInfo, VKALLOC, &pool); - return pool; -} + VkCommandBuffer createCommandBuffer(VkDevice device, VkCommandPool pool) { + VkCommandBuffer cmdbuffer; + // Create the low-level command buffer. + VkCommandBufferAllocateInfo const allocateInfo{ + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .commandPool = pool, + .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, + .commandBufferCount = 1, + }; + // The buffer allocated here will be implicitly reset when vkBeginCommandBuffer is called. + // We don't need to deallocate since destroying the pool will free all of the buffers. + vkAllocateCommandBuffers(device, &allocateInfo, &cmdbuffer); + return cmdbuffer; + } + + } // anonymous namespace #if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS) -void VulkanGroupMarkers::push(std::string const& marker, Timestamp start) noexcept { - mMarkers.push_back(marker); + void VulkanGroupMarkers::push(std::string const& marker, Timestamp start) noexcept { + mMarkers.push_back(marker); #if FVK_ENABLED(FVK_DEBUG_PRINT_GROUP_MARKERS) - mTimestamps.push_back(start.time_since_epoch().count() > 0.0 - ? start - : std::chrono::high_resolution_clock::now()); + mTimestamps.push_back(start.time_since_epoch().count() > 0.0 + ? start + : std::chrono::high_resolution_clock::now()); #endif -} + } -std::pair VulkanGroupMarkers::pop() noexcept { - auto const marker = mMarkers.back(); - mMarkers.pop_back(); + std::pair VulkanGroupMarkers::pop() noexcept { + auto const marker = mMarkers.back(); + mMarkers.pop_back(); #if FVK_ENABLED(FVK_DEBUG_PRINT_GROUP_MARKERS) - auto const timestamp = mTimestamps.back(); - mTimestamps.pop_back(); - return std::make_pair(marker, timestamp); + auto const timestamp = mTimestamps.back(); + mTimestamps.pop_back(); + return std::make_pair(marker, timestamp); #else - return std::make_pair(marker, Timestamp{}); + return std::make_pair(marker, Timestamp{}); #endif -} + } -std::pair VulkanGroupMarkers::pop_bottom() noexcept { - auto const marker = mMarkers.front(); - mMarkers.pop_front(); + std::pair VulkanGroupMarkers::pop_bottom() noexcept { + auto const marker = mMarkers.front(); + mMarkers.pop_front(); #if FVK_ENABLED(FVK_DEBUG_PRINT_GROUP_MARKERS) - auto const timestamp = mTimestamps.front(); - mTimestamps.pop_front(); - return std::make_pair(marker, timestamp); + auto const timestamp = mTimestamps.front(); + mTimestamps.pop_front(); + return std::make_pair(marker, timestamp); #else - return std::make_pair(marker, Timestamp{}); + return std::make_pair(marker, Timestamp{}); #endif -} + } -std::pair VulkanGroupMarkers::top() const { - assert_invariant(!empty()); - auto const marker = mMarkers.back(); + std::pair VulkanGroupMarkers::top() const { + assert_invariant(!empty()); + auto const marker = mMarkers.back(); #if FVK_ENABLED(FVK_DEBUG_PRINT_GROUP_MARKERS) - auto const topTimestamp = mTimestamps.front(); - return std::make_pair(marker, topTimestamp); + auto const topTimestamp = mTimestamps.front(); + return std::make_pair(marker, topTimestamp); #else - return std::make_pair(marker, Timestamp{}); + return std::make_pair(marker, Timestamp{}); #endif -} + } -bool VulkanGroupMarkers::empty() const noexcept { - return mMarkers.empty(); -} + bool VulkanGroupMarkers::empty() const noexcept { + return mMarkers.empty(); + } #endif // FVK_DEBUG_GROUP_MARKERS -VulkanCommands::VulkanCommands(VkDevice device, VkQueue queue, uint32_t queueFamilyIndex, - VulkanContext* context, VulkanResourceAllocator* allocator) - : mDevice(device), - mQueue(queue), - mPool(createPool(mDevice, queueFamilyIndex)), - mContext(context), - mStorage(CAPACITY) { - VkSemaphoreCreateInfo sci{.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO}; - for (auto& semaphore: mSubmissionSignals) { - vkCreateSemaphore(mDevice, &sci, nullptr, &semaphore); + VulkanCommandBuffer::VulkanCommandBuffer(VulkanContext* context, VulkanResourceAllocator* allocator, + VkDevice device, VkQueue queue, VkCommandPool pool, bool isProtected) + : mContext(context), + mMarkerCount(0), + isProtected(isProtected), + mDevice(device), + mQueue(queue), + mResourceManager(allocator), + mBuffer(createCommandBuffer(device, pool)), + mFenceStatus(std::make_shared(VK_INCOMPLETE)) { + VkSemaphoreCreateInfo sci{ .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO }; + vkCreateSemaphore(mDevice, &sci, VKALLOC, &mSubmission); + + VkFenceCreateInfo fenceCreateInfo{ .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO }; + vkCreateFence(device, &fenceCreateInfo, VKALLOC, &mFence); + mIsProtected = isProtected; } - VkFenceCreateInfo fenceCreateInfo{.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO}; - for (auto& fence: mFences) { - vkCreateFence(device, &fenceCreateInfo, VKALLOC, &fence); + VulkanCommandBuffer::~VulkanCommandBuffer() { + vkDestroySemaphore(mDevice, mSubmission, VKALLOC); + vkDestroyFence(mDevice, mFence, VKALLOC); } - for (size_t i = 0; i < CAPACITY; ++i) { - mStorage[i] = std::make_unique(allocator, mDevice, mPool); + void VulkanCommandBuffer::reset() noexcept { + mMarkerCount = 0; + mResourceManager.clear(); + mWaitSemaphores.clear(); + + // Internally we use the VK_INCOMPLETE status to mean "not yet submitted". When this fence + // gets, gets submitted, its status changes to VK_NOT_READY. Finally, when the GPU actually + // finishes executing the command buffer, the status changes to VK_SUCCESS. + mFenceStatus = std::make_shared(VK_INCOMPLETE); + vkResetFences(mDevice, 1, &mFence); } -#if !FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS) - (void) mContext; -#endif -} + void VulkanCommandBuffer::pushMarker(char const* marker) noexcept { + if (mContext->isDebugUtilsSupported()) { + VkDebugUtilsLabelEXT labelInfo = { + .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT, + .pLabelName = marker, + .color = {0, 1, 0, 1}, + }; + vkCmdBeginDebugUtilsLabelEXT(mBuffer, &labelInfo); + } + else if (mContext->isDebugMarkersSupported()) { + VkDebugMarkerMarkerInfoEXT markerInfo = { + .sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT, + .pMarkerName = marker, + .color = {0.0f, 1.0f, 0.0f, 1.0f}, + }; + vkCmdDebugMarkerBeginEXT(mBuffer, &markerInfo); + } + mMarkerCount++; + } -void VulkanCommands::terminate() { - wait(); - gc(); - vkDestroyCommandPool(mDevice, mPool, VKALLOC); - for (VkSemaphore sema: mSubmissionSignals) { - vkDestroySemaphore(mDevice, sema, VKALLOC); + void VulkanCommandBuffer::popMarker() noexcept { + assert_invariant(mMarkerCount > 0); + if (mContext->isDebugUtilsSupported()) { + vkCmdEndDebugUtilsLabelEXT(mBuffer); + } + else if (mContext->isDebugMarkersSupported()) { + vkCmdDebugMarkerEndEXT(mBuffer); + } + mMarkerCount--; } - for (VkFence fence: mFences) { - vkDestroyFence(mDevice, fence, VKALLOC); + + void VulkanCommandBuffer::insertEvent(char const* marker) noexcept { + if (mContext->isDebugUtilsSupported()) { + VkDebugUtilsLabelEXT labelInfo = { + .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT, + .pLabelName = marker, + .color = {1, 1, 0, 1}, + }; + vkCmdInsertDebugUtilsLabelEXT(mBuffer, &labelInfo); + } + else if (mContext->isDebugMarkersSupported()) { + VkDebugMarkerMarkerInfoEXT markerInfo = { + .sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT, + .pMarkerName = marker, + .color = {0.0f, 1.0f, 0.0f, 1.0f}, + }; + vkCmdDebugMarkerInsertEXT(mBuffer, &markerInfo); + } } -} -VulkanCommandBuffer& VulkanCommands::get() { - if (mCurrentCommandBufferIndex >= 0) { - return *mStorage[mCurrentCommandBufferIndex].get(); + void VulkanCommandBuffer::begin() noexcept { + FVK_LOGE << "[" << (mIsProtected ? "protected" : "regular") << "]=" << buffer() << " begin" + << utils::io::endl; + // Begin writing into the command buffer. + VkCommandBufferBeginInfo const binfo{ + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, + }; + vkBeginCommandBuffer(mBuffer, &binfo); } - // If we ran out of available command buffers, stall until one finishes. This is very rare. - // It occurs only when Filament invokes commit() or endFrame() a large number of times without - // presenting the swap chain or waiting on a fence. - while (mAvailableBufferCount == 0) { + VkSemaphore VulkanCommandBuffer::submit() { + while (mMarkerCount > 0) { + popMarker(); + } + + vkEndCommandBuffer(mBuffer); + + FVK_LOGE << "[" << (mIsProtected ? "protected" : "regular") << "]=" << buffer() << " end" + << utils::io::endl; + + VkPipelineStageFlags const waitDestStageMasks[2] = { + VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + }; + + VkSubmitInfo submitInfo{ + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .waitSemaphoreCount = mWaitSemaphores.size(), + .pWaitSemaphores = mWaitSemaphores.data(), + .pWaitDstStageMask = waitDestStageMasks, + .commandBufferCount = 1u, + .pCommandBuffers = &mBuffer, + .signalSemaphoreCount = 1u, + .pSignalSemaphores = &mSubmission, + }; + // add submit protection if needed + VkProtectedSubmitInfo protectedSubmitInfo{ + .sType = VK_STRUCTURE_TYPE_PROTECTED_SUBMIT_INFO, + .protectedSubmit = VK_TRUE, + }; + + if (isProtected) { + submitInfo.pNext = &protectedSubmitInfo; + } + #if FVK_ENABLED(FVK_DEBUG_COMMAND_BUFFER) - FVK_LOGI << "VulkanCommands has stalled. " - << "If this occurs frequently, consider increasing VK_MAX_COMMAND_BUFFERS." - << io::endl; + FVK_LOGI << "Submitting cmdbuffer=" << mBuffer + << " wait=("; + for (size_t s = 0, count = mWaitSemaphores.size(); s < count; ++s) { + FVK_LOGI << mWaitSemaphores[s] << " "; + } + FVK_LOGI << ") " + << " signal=" << mSubmission + << " fence=" << mFence << utils::io::endl; #endif - wait(); - gc(); + + mFenceStatus->setStatus(VK_NOT_READY); + UTILS_UNUSED_IN_RELEASE VkResult result = vkQueueSubmit(mQueue, 1, &submitInfo, mFence); + +#if FVK_ENABLED(FVK_DEBUG_COMMAND_BUFFER) + if (result != VK_SUCCESS) { + FVK_LOGD << "Failed command buffer submission result: " << result << utils::io::endl; + } +#endif + assert_invariant(result == VK_SUCCESS); + mWaitSemaphores.clear(); + return mSubmission; } - VulkanCommandBuffer* currentbuf = nullptr; - // Find an available slot. - for (size_t i = 0; i < CAPACITY; ++i) { - auto wrapper = mStorage[i].get(); - if (wrapper->buffer() == VK_NULL_HANDLE) { - mCurrentCommandBufferIndex = static_cast(i); - currentbuf = wrapper; - break; + CommandBufferPool::CommandBufferPool(VulkanContext* context, VulkanResourceAllocator* allocator, + VkDevice device, VkQueue queue, uint8_t queueFamilyIndex, bool isProtected) + : mDevice(device), + mRecording(INVALID) { + VkCommandPoolCreateInfo createInfo = { + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT | + VK_COMMAND_POOL_CREATE_TRANSIENT_BIT | + (isProtected ? VK_COMMAND_POOL_CREATE_PROTECTED_BIT : 0u), + .queueFamilyIndex = queueFamilyIndex, + }; + vkCreateCommandPool(device, &createInfo, VKALLOC, &mPool); + + for (size_t i = 0; i < CAPACITY; ++i) { + mBuffers.emplace_back(std::make_unique(context, allocator, device, + queue, mPool, isProtected)); } } - assert_invariant(currentbuf); - mAvailableBufferCount--; + CommandBufferPool::~CommandBufferPool() { + wait(); + gc(); + vkDestroyCommandPool(mDevice, mPool, VKALLOC); + } - // Note that the fence wrapper uses shared_ptr because a DriverAPI fence can also have ownership - // over it. The destruction of the low-level fence occurs either in VulkanCommands::gc(), or in - // VulkanDriver::destroyFence(), both of which are safe spots. - currentbuf->fence = std::make_shared(mFences[mCurrentCommandBufferIndex]); + VulkanCommandBuffer& CommandBufferPool::getRecording() { + if (isRecording()) { + return *mBuffers[mRecording]; + } - // Begin writing into the command buffer. - const VkCommandBufferBeginInfo binfo{ - .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, - .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, - }; - vkBeginCommandBuffer(currentbuf->buffer(), &binfo); + auto const findNext = [this]() { + for (int8_t i = 0; i < CAPACITY; ++i) { + if (!mSubmitted[i]) { + return i; + } + } + return INVALID; + }; + + while ((mRecording = findNext()) == INVALID) { + wait(); + gc(); + } - // Notify the observer that a new command buffer has been activated. - if (mObserver) { - mObserver->onCommandBuffer(*currentbuf); + auto& recording = *mBuffers[mRecording]; + recording.begin(); + return recording; } -#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS) - // We push the current markers onto a temporary stack. This must be placed after currentbuf is - // set to the new command buffer since pushGroupMarker also calls get(). - while (mCarriedOverMarkers && !mCarriedOverMarkers->empty()) { - auto [marker, time] = mCarriedOverMarkers->pop(); - pushGroupMarker(marker.c_str(), time); + void CommandBufferPool::gc() { + ActiveBuffers reclaimed; + mSubmitted.forEachSetBit([this, &reclaimed](size_t index) { + auto& buffer = mBuffers[index]; + if (buffer->getStatus() == VK_SUCCESS) { + reclaimed.set(index, true); + buffer->reset(); + } + }); + mSubmitted &= ~reclaimed; } -#endif - return *currentbuf; -} -bool VulkanCommands::flush() { - // It's perfectly fine to call flush when no commands have been written. - if (mCurrentCommandBufferIndex < 0) { - return false; + void CommandBufferPool::update() { + mSubmitted.forEachSetBit([this](size_t index) { + auto& buffer = mBuffers[index]; + VkResult status = vkGetFenceStatus(mDevice, buffer->getVkFence()); + if (status == VK_SUCCESS) { + buffer->setComplete(); + } + }); } - // Before actually submitting, we need to pop any leftover group markers. - // Note that this needs to occur before vkEndCommandBuffer. -#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS) - while (mGroupMarkers && !mGroupMarkers->empty()) { - if (!mCarriedOverMarkers) { - mCarriedOverMarkers = std::make_unique(); + VkSemaphore CommandBufferPool::flush() { + // We're not recording right now. + if (!isRecording()) { + return VK_NULL_HANDLE; } - auto const [marker, time] = mGroupMarkers->top(); - mCarriedOverMarkers->push(marker, time); - // We still need to call through to vkCmdEndDebugUtilsLabelEXT. - popGroupMarker(); + auto submitSemaphore = mBuffers[mRecording]->submit(); + mSubmitted.set(mRecording, true); + mRecording = INVALID; + return submitSemaphore; } -#endif - - int8_t const index = mCurrentCommandBufferIndex; - VulkanCommandBuffer const* currentbuf = mStorage[index].get(); - VkSemaphore const renderingFinished = mSubmissionSignals[index]; - vkEndCommandBuffer(currentbuf->buffer()); + void CommandBufferPool::wait() { + uint8_t count = 0; + VkFence fences[CAPACITY]; + mSubmitted.forEachSetBit([this, &count, &fences](size_t index) { + fences[count++] = mBuffers[index]->getVkFence(); + }); + vkWaitForFences(mDevice, count, fences, VK_TRUE, UINT64_MAX); + update(); + } - // If the injected semaphore is an "image available" semaphore that has not yet been signaled, - // it is sometimes fine to start executing commands anyway, as along as we stall the GPU at the - // VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT stage. However we need to assume the worst - // here and use VK_PIPELINE_STAGE_ALL_COMMANDS_BIT. This is a more aggressive stall, but it is - // the only safe option because the previously submitted command buffer might have set up some - // state that the new command buffer depends on. - VkPipelineStageFlags waitDestStageMasks[2] = { - VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, - VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, - }; + void CommandBufferPool::waitFor(VkSemaphore previousAction) { + if (!isRecording()) { + return; + } + auto& recording = mBuffers[mRecording]; + recording->insertWait(previousAction); + } - VkSemaphore signals[2] = { - VK_NULL_HANDLE, - VK_NULL_HANDLE, - }; - uint32_t waitSemaphoreCount = 0; - if (mSubmissionSignal) { - signals[waitSemaphoreCount++] = mSubmissionSignal; + void CommandBufferPool::pushMarker(char const* marker) { + getRecording().pushMarker(marker); } - if (mInjectedSignal) { - signals[waitSemaphoreCount++] = mInjectedSignal; + + void CommandBufferPool::popMarker() { + getRecording().popMarker(); } - VkCommandBuffer const cmdbuffer = currentbuf->buffer(); - VkSubmitInfo submitInfo{ - .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, - .waitSemaphoreCount = waitSemaphoreCount, - .pWaitSemaphores = waitSemaphoreCount > 0 ? signals : nullptr, - .pWaitDstStageMask = waitDestStageMasks, - .commandBufferCount = 1, - .pCommandBuffers = &cmdbuffer, - .signalSemaphoreCount = 1u, - .pSignalSemaphores = &renderingFinished, - }; -#if FVK_ENABLED(FVK_DEBUG_COMMAND_BUFFER) - FVK_LOGI << "Submitting cmdbuffer=" << cmdbuffer - << " wait=(" << signals[0] << ", " << signals[1] << ") " - << " signal=" << renderingFinished - << " fence=" << currentbuf->fence->fence - << utils::io::endl; -#endif + void CommandBufferPool::insertEvent(char const* marker) { + getRecording().insertEvent(marker); + } - auto& cmdfence = currentbuf->fence; - UTILS_UNUSED_IN_RELEASE VkResult result = VK_SUCCESS; - { - auto scope = cmdfence->setValue(VK_NOT_READY); - result = vkQueueSubmit(mQueue, 1, &submitInfo, cmdfence->getFence()); + VulkanCommands::VulkanCommands(VkDevice device, VkQueue queue, uint32_t queueFamilyIndex, + VkQueue protectedQueue, uint32_t protectedQueueFamilyIndex, VulkanContext* context, + VulkanResourceAllocator* allocator) + : mDevice(device), + mProtectedQueue(protectedQueue), + mProtectedQueueFamilyIndex(protectedQueueFamilyIndex), + mAllocator(allocator), + mContext(context), + mPool(std::make_unique(context, allocator, device, queue, queueFamilyIndex, + false)) { } -#if FVK_ENABLED(FVK_DEBUG_COMMAND_BUFFER) - if (result != VK_SUCCESS) { - FVK_LOGD << "Failed command buffer submission result: " << result << utils::io::endl; + void VulkanCommands::terminate() { + mPool.reset(); + mProtectedPool.reset(); } -#endif - assert_invariant(result == VK_SUCCESS); - mSubmissionSignal = renderingFinished; - mInjectedSignal = VK_NULL_HANDLE; - mCurrentCommandBufferIndex = -1; - return true; -} + VulkanCommandBuffer& VulkanCommands::get() { + auto& ret = mPool->getRecording(); + return ret; + } -VkSemaphore VulkanCommands::acquireFinishedSignal() { - VkSemaphore semaphore = mSubmissionSignal; - mSubmissionSignal = VK_NULL_HANDLE; -#if FVK_ENABLED(FVK_DEBUG_COMMAND_BUFFER) - FVK_LOGI << "Acquiring " << semaphore << " (e.g. for vkQueuePresentKHR)" << io::endl; -#endif - return semaphore; -} + VulkanCommandBuffer& VulkanCommands::getProtected() { + assert_invariant(mProtectedQueue != VK_NULL_HANDLE); -void VulkanCommands::injectDependency(VkSemaphore next) { - assert_invariant(mInjectedSignal == VK_NULL_HANDLE); - mInjectedSignal = next; -#if FVK_ENABLED(FVK_DEBUG_COMMAND_BUFFER) - FVK_LOGI << "Injecting " << next << " (e.g. due to vkAcquireNextImageKHR)" << io::endl; -#endif -} - -void VulkanCommands::wait() { - VkFence fences[CAPACITY]; - size_t count = 0; - for (size_t i = 0; i < CAPACITY; i++) { - auto wrapper = mStorage[i].get(); - if (wrapper->buffer() != VK_NULL_HANDLE - && mCurrentCommandBufferIndex != static_cast(i)) { - fences[count++] = wrapper->fence->getFence(); + if (!mProtectedPool) { + mProtectedPool = std::make_unique(mContext, mAllocator, mDevice, + mProtectedQueue, mProtectedQueueFamilyIndex, true); } + auto& ret = mProtectedPool->getRecording(); + return ret; } - if (count > 0) { - vkWaitForFences(mDevice, count, fences, VK_TRUE, UINT64_MAX); - updateFences(); - } -} -void VulkanCommands::gc() { - FVK_SYSTRACE_CONTEXT(); - FVK_SYSTRACE_START("commands::gc"); + bool VulkanCommands::flush() { + VkSemaphore dependency = mInjectedDependency; + VkSemaphore lastSubmit = mLastSubmit; + bool hasFlushed = false; + + // Note that we've ordered it so that the non-protected commands are followed by the protected + // commands. This assumes that the protected commands will be that one doing the rendering into + // the protected memory (i.e. protected render target). + for (auto pool : { mPool.get(), mProtectedPool.get() }) { + if (!pool || !pool->isRecording()) { + continue; + } + if (dependency != VK_NULL_HANDLE) { + pool->waitFor(dependency); + } + if (lastSubmit != VK_NULL_HANDLE) { + pool->waitFor(lastSubmit); + lastSubmit = VK_NULL_HANDLE; + } + dependency = pool->flush(); + hasFlushed = true; + } + + if (hasFlushed) { + mInjectedDependency = VK_NULL_HANDLE; + mLastSubmit = dependency; + } - VkFence fences[CAPACITY]; - size_t count = 0; + return true; + } + + void VulkanCommands::wait() { + FVK_SYSTRACE_CONTEXT(); + FVK_SYSTRACE_START("commands::wait"); - for (size_t i = 0; i < CAPACITY; i++) { - auto wrapper = mStorage[i].get(); - if (wrapper->buffer() == VK_NULL_HANDLE) { - continue; + mPool->wait(); + if (mProtectedPool) { + mProtectedPool->wait(); } - auto const vkfence = wrapper->fence->getFence(); - VkResult const result = vkGetFenceStatus(mDevice, vkfence); - if (result != VK_SUCCESS) { - continue; + FVK_SYSTRACE_END(); + } + + void VulkanCommands::gc() { + FVK_SYSTRACE_CONTEXT(); + FVK_SYSTRACE_START("commands::gc"); + + mPool->gc(); + if (mProtectedPool) { + mProtectedPool->gc(); } - fences[count++] = vkfence; - wrapper->fence->setValue(VK_SUCCESS); - wrapper->reset(); - mAvailableBufferCount++; - } - - if (count > 0) { - vkResetFences(mDevice, count, fences); - } - FVK_SYSTRACE_END(); -} - -void VulkanCommands::updateFences() { - for (size_t i = 0; i < CAPACITY; i++) { - auto wrapper = mStorage[i].get(); - if (wrapper->buffer() != VK_NULL_HANDLE) { - VulkanCmdFence* fence = wrapper->fence.get(); - if (fence) { - VkResult status = vkGetFenceStatus(mDevice, fence->getFence()); - // This is either VK_SUCCESS, VK_NOT_READY, or VK_ERROR_DEVICE_LOST. - fence->setValue(status); - } + FVK_SYSTRACE_END(); + } + + void VulkanCommands::updateFences() { + mPool->update(); + if (mProtectedPool) { + mProtectedPool->update(); } } -} #if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS) -void VulkanCommands::pushGroupMarker(char const* str, VulkanGroupMarkers::Timestamp timestamp) { + void VulkanCommands::pushGroupMarker(char const* str, VulkanGroupMarkers::Timestamp timestamp) { #if FVK_ENABLED(FVK_DEBUG_PRINT_GROUP_MARKERS) - // If the timestamp is not 0, then we are carrying over a marker across buffer submits. - // If it is 0, then this is a normal marker push and we should just print debug line as usual. - if (timestamp.time_since_epoch().count() == 0.0) { - FVK_LOGD << "----> " << str << utils::io::endl; - } + // If the timestamp is not 0, then we are carrying over a marker across buffer submits. + // If it is 0, then this is a normal marker push and we should just print debug line as usual. + if (timestamp.time_since_epoch().count() == 0.0) { + FVK_LOGD << "----> " << str << utils::io::endl; + } #endif + if (!mGroupMarkers) { + mGroupMarkers = std::make_unique(); + } + mGroupMarkers->push(str, timestamp); - // TODO: Add group marker color to the Driver API - VkCommandBuffer const cmdbuffer = get().buffer(); - - if (!mGroupMarkers) { - mGroupMarkers = std::make_unique(); - } - mGroupMarkers->push(str, timestamp); - - if (mContext->isDebugUtilsSupported()) { - VkDebugUtilsLabelEXT labelInfo = { - .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT, - .pLabelName = str, - .color = {0, 1, 0, 1}, - }; - vkCmdBeginDebugUtilsLabelEXT(cmdbuffer, &labelInfo); - } else if (mContext->isDebugMarkersSupported()) { - VkDebugMarkerMarkerInfoEXT markerInfo = { - .sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT, - .pMarkerName = str, - .color = {0.0f, 1.0f, 0.0f, 1.0f}, - }; - vkCmdDebugMarkerBeginEXT(cmdbuffer, &markerInfo); + mPool->pushMarker(str); + if (mProtectedPool) { + mProtectedPool->pushMarker(str); + } } -} -void VulkanCommands::popGroupMarker() { - assert_invariant(mGroupMarkers); + void VulkanCommands::popGroupMarker() { + assert_invariant(mGroupMarkers); - if (!mGroupMarkers->empty()) { - VkCommandBuffer const cmdbuffer = get().buffer(); + if (!mGroupMarkers->empty()) { + VkCommandBuffer const cmdbuffer = get().buffer(); #if FVK_ENABLED(FVK_DEBUG_PRINT_GROUP_MARKERS) - auto const [marker, startTime] = mGroupMarkers->pop(); - auto const endTime = std::chrono::high_resolution_clock::now(); - std::chrono::duration diff = endTime - startTime; - FVK_LOGD << "<---- " << marker << " elapsed: " << (diff.count() * 1000) << " ms" - << utils::io::endl; + auto const [marker, startTime] = mGroupMarkers->pop(); + auto const endTime = std::chrono::high_resolution_clock::now(); + std::chrono::duration diff = endTime - startTime; + FVK_LOGD << "<---- " << marker << " elapsed: " << (diff.count() * 1000) << " ms" + << utils::io::endl; #else - mGroupMarkers->pop(); + mGroupMarkers->pop(); #endif - if (mContext->isDebugUtilsSupported()) { - vkCmdEndDebugUtilsLabelEXT(cmdbuffer); - } else if (mContext->isDebugMarkersSupported()) { - vkCmdDebugMarkerEndEXT(cmdbuffer); + mPool->popMarker(); + if (mProtectedPool) { + mProtectedPool->popMarker(); + } + } + else if (mCarriedOverMarkers && !mCarriedOverMarkers->empty()) { + // It could be that pop is called between flush() and get() (new command buffer), in which + // case the marker is in "carried over" state, we'd just remove that. Since the + // mCarriedOverMarkers is in the opposite order, we pop the bottom instead of the top. + mCarriedOverMarkers->pop_bottom(); } - } else if (mCarriedOverMarkers && !mCarriedOverMarkers->empty()) { - // It could be that pop is called between flush() and get() (new command buffer), in which - // case the marker is in "carried over" state, we'd just remove that. Since the - // mCarriedOverMarkers is in the opposite order, we pop the bottom instead of the top. - mCarriedOverMarkers->pop_bottom(); - } -} - -void VulkanCommands::insertEventMarker(char const* string, uint32_t len) { - VkCommandBuffer const cmdbuffer = get().buffer(); - if (mContext->isDebugUtilsSupported()) { - VkDebugUtilsLabelEXT labelInfo = { - .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT, - .pLabelName = string, - .color = {1, 1, 0, 1}, - }; - vkCmdInsertDebugUtilsLabelEXT(cmdbuffer, &labelInfo); - } else if (mContext->isDebugMarkersSupported()) { - VkDebugMarkerMarkerInfoEXT markerInfo = { - .sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT, - .pMarkerName = string, - .color = {0.0f, 1.0f, 0.0f, 1.0f}, - }; - vkCmdDebugMarkerInsertEXT(cmdbuffer, &markerInfo); } -} -std::string VulkanCommands::getTopGroupMarker() const { - if (!mGroupMarkers || mGroupMarkers->empty()) { - return ""; + void VulkanCommands::insertEventMarker(char const* str, uint32_t len) { + mPool->insertEvent(str); + if (mProtectedPool) { + mProtectedPool->insertEvent(str); + } + } + + std::string VulkanCommands::getTopGroupMarker() const { + if (!mGroupMarkers || mGroupMarkers->empty()) { + return ""; + } + return std::get<0>(mGroupMarkers->top()); } - return std::get<0>(mGroupMarkers->top()); -} #endif // FVK_DEBUG_GROUP_MARKERS } // namespace filament::backend diff --git a/filament/backend/src/vulkan/VulkanCommands.h b/filament/backend/src/vulkan/VulkanCommands.h index a946fc4314a..6899ed03b72 100644 --- a/filament/backend/src/vulkan/VulkanCommands.h +++ b/filament/backend/src/vulkan/VulkanCommands.h @@ -23,6 +23,7 @@ #include "VulkanConstants.h" #include "VulkanResources.h" +#include "VulkanUtility.h" #include #include @@ -37,218 +38,248 @@ namespace filament::backend { -struct VulkanContext; + struct VulkanContext; #if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS) -class VulkanGroupMarkers { -public: - using Timestamp = std::chrono::time_point; - - void push(std::string const& marker, Timestamp start = {}) noexcept; - std::pair pop() noexcept; - std::pair pop_bottom() noexcept; - std::pair top() const; - bool empty() const noexcept; - -private: - std::list mMarkers; + class VulkanGroupMarkers { + public: + using Timestamp = std::chrono::time_point; + + void push(std::string const& marker, Timestamp start = {}) noexcept; + std::pair pop() noexcept; + std::pair pop_bottom() noexcept; + std::pair top() const; + bool empty() const noexcept; + + private: + std::list mMarkers; #if FVK_ENABLED(FVK_DEBUG_PRINT_GROUP_MARKERS) - std::list mTimestamps; + std::list mTimestamps; #endif -}; + }; #endif // FVK_DEBUG_GROUP_MARKERS -// Wrapper to enable use of shared_ptr for implementing shared ownership of low-level Vulkan fences. -struct VulkanCmdFence { - struct SetValueScope { - public: - ~SetValueScope() { - mHolder->mutex.unlock(); - mHolder->condition.notify_all(); + // Wrapper to enable use of shared_ptr for implementing shared ownership of low-level Vulkan fences. + struct VulkanCmdFence { + VulkanCmdFence(VkResult initialStatus) { + // Internally we use the VK_INCOMPLETE status to mean "not yet submitted". When this fence + // gets submitted, its status changes to VK_NOT_READY. Finally, when the GPU actually + // finishes executing the command buffer, the status changes to VK_SUCCESS. + status.store(initialStatus); } + ~VulkanCmdFence() = default; + + void setStatus(VkResult value) { status.store(value); } + + VkResult getStatus() { return status.load(std::memory_order_acquire); } + private: - SetValueScope(VulkanCmdFence* fenceHolder, VkResult result) : - mHolder(fenceHolder) { - mHolder->mutex.lock(); - mHolder->status.store(result); - } - VulkanCmdFence* mHolder; - friend struct VulkanCmdFence; + std::atomic status; }; - VulkanCmdFence(VkFence ifence); - ~VulkanCmdFence() = default; - - SetValueScope setValue(VkResult value) { - return {this, value}; - } - - VkFence& getFence() { - return fence; - } - - VkResult getStatus() { - std::unique_lock lock(mutex); - return status.load(std::memory_order_acquire); - } - -private: - VkFence fence; - utils::Condition condition; - utils::Mutex mutex; - std::atomic status; -}; - -// The submission fence has shared ownership semantics because it is potentially wrapped by a -// DriverApi fence object and should not be destroyed until both the DriverApi object is freed and -// we're done waiting on the most recent submission of the given command buffer. -struct VulkanCommandBuffer { - VulkanCommandBuffer(VulkanResourceAllocator* allocator, VkDevice device, VkCommandPool pool); - - VulkanCommandBuffer(VulkanCommandBuffer const&) = delete; - VulkanCommandBuffer& operator=(VulkanCommandBuffer const&) = delete; - - inline void acquire(VulkanResource* resource) { - mResourceManager.acquire(resource); - } - - inline void acquire(VulkanAcquireOnlyResourceManager* srcResources) { - mResourceManager.acquireAll(srcResources); - } - - inline void reset() { - fence.reset(); - mResourceManager.clear(); - mPipeline = VK_NULL_HANDLE; - } - - inline void setPipeline(VkPipeline pipeline) { - mPipeline = pipeline; - } - - inline VkPipeline pipeline() const { - return mPipeline; - } - - inline VkCommandBuffer buffer() const { - if (fence) { + // The submission fence has shared ownership semantics because it is potentially wrapped by a + // DriverApi fence object and should not be destroyed until both the DriverApi object is freed and + // we're done waiting on the most recent submission of the given command buffer. + struct VulkanCommandBuffer { + VulkanCommandBuffer(VulkanContext* mContext, VulkanResourceAllocator* allocator, + VkDevice device, VkQueue queue, VkCommandPool pool, bool isProtected); + + VulkanCommandBuffer(VulkanCommandBuffer const&) = delete; + VulkanCommandBuffer& operator=(VulkanCommandBuffer const&) = delete; + + ~VulkanCommandBuffer(); + + inline void acquire(VulkanResource* resource) { + mResourceManager.acquire(resource); + } + + inline void acquire(VulkanAcquireOnlyResourceManager* srcResources) { + mResourceManager.acquireAll(srcResources); + } + void reset() noexcept; + + inline void insertWait(VkSemaphore sem) { + mWaitSemaphores.insert(sem); + } + + void pushMarker(char const* marker) noexcept; + void popMarker() noexcept; + void insertEvent(char const* marker) noexcept; + + void begin() noexcept; + VkSemaphore submit(); + + inline void setComplete() { + mFenceStatus->setStatus(VK_SUCCESS); + } + + VkResult getStatus() { + return mFenceStatus->getStatus(); + } + + std::shared_ptr getFenceStatus() const { + return mFenceStatus; + } + + VkFence getVkFence() const { + return mFence; + } + + VkCommandBuffer buffer() const { + FVK_LOGE << "-- getting " << mBuffer << " -- "; return mBuffer; } - return VK_NULL_HANDLE; - } - - std::shared_ptr fence; - -private: - VulkanAcquireOnlyResourceManager mResourceManager; - VkCommandBuffer mBuffer; - VkPipeline mPipeline; -}; - -// Allows classes to be notified after a new command buffer has been activated. -class CommandBufferObserver { -public: - virtual void onCommandBuffer(const VulkanCommandBuffer& cmdbuffer) = 0; - virtual ~CommandBufferObserver(); -}; - -// Manages a set of command buffers and semaphores, exposing an API that is significantly simpler -// than the raw Vulkan API. -// -// The manager's API primarily consists of get() and flush(). The "get" method acquires a fresh -// command buffer in the recording state, while the "flush" method releases a command buffer and -// changes its state from recording to executing. Some of the operational details are listed below. -// -// - Manages a dependency chain of submitted command buffers using VkSemaphore. -// - This creates a guarantee of in-order execution. -// - Semaphores are recycled to prevent create / destroy churn. -// -// - Notifies listeners when recording begins in a new VkCommandBuffer. -// - Used by PipelineCache so that it knows when to clear out its shadow state. -// -// - Allows 1 user to inject a "dependency" semaphore that stalls the next flush. -// - This is used for asynchronous acquisition of a swap chain image, since the GPU -// might require a valid swap chain image when it starts executing the command buffer. -// -// - Allows 1 user to listen to the most recent flush event using a "finished" VkSemaphore. -// - This is used to trigger presentation of the swap chain image. -// -// - Allows off-thread queries of command buffer status. -// - Exposes an "updateFences" method that transfers current fence status into atomics. -// - Users can examine these atomic variables (see VulkanCmdFence) to determine status. -// - We do this because vkGetFenceStatus must be called from the rendering thread. -// -class VulkanCommands { -public: - VulkanCommands(VkDevice device, VkQueue queue, uint32_t queueFamilyIndex, - VulkanContext* context, VulkanResourceAllocator* allocator); - - void terminate(); - - // Creates a "current" command buffer if none exists, otherwise returns the current one. - VulkanCommandBuffer& get(); - - // Submits the current command buffer if it exists, then sets "current" to null. - // If there are no outstanding commands then nothing happens and this returns false. - bool flush(); - - // Returns the "rendering finished" semaphore for the most recent flush and removes - // it from the existing dependency chain. This is especially useful for setting up - // vkQueuePresentKHR. - VkSemaphore acquireFinishedSignal(); - - // Takes a semaphore that signals when the next flush can occur. Only one injected - // semaphore is allowed per flush. Useful after calling vkAcquireNextImageKHR. - void injectDependency(VkSemaphore next); - - // Destroys all command buffers that are no longer in use. - void gc(); - - // Waits for all outstanding command buffers to finish. - void wait(); - - // Updates the atomic "status" variable in every extant fence. - void updateFences(); - - // Sets an observer who is notified every time a new command buffer has been made "current". - // The observer's event handler can only be called during get(). - void setObserver(CommandBufferObserver* observer) { mObserver = observer; } -#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS) - void pushGroupMarker(char const* str, VulkanGroupMarkers::Timestamp timestamp = {}); + // debug only + bool mIsProtected; + + private: + VulkanContext* mContext; + uint8_t mMarkerCount; + bool const isProtected; + VkDevice mDevice; + VkQueue mQueue; + VulkanAcquireOnlyResourceManager mResourceManager; + CappedArray mWaitSemaphores; + VkCommandBuffer mBuffer; + VkSemaphore mSubmission; + VkFence mFence; + std::shared_ptr mFenceStatus; + + }; + + struct CommandBufferPool { + using ActiveBuffers = utils::bitset32; + static constexpr int8_t INVALID = -1; + + CommandBufferPool(VulkanContext* context, VulkanResourceAllocator* allocator, VkDevice device, + VkQueue queue, uint8_t queueFamilyIndex, bool isProtected); + ~CommandBufferPool(); + + VulkanCommandBuffer& getRecording(); + + void gc(); + void update(); + VkSemaphore flush(); + void wait(); + + void waitFor(VkSemaphore previousAction); + + void pushMarker(char const* marker); + void popMarker(); + void insertEvent(char const* marker); + + inline bool isRecording() const { return mRecording != INVALID; } + + private: + static constexpr int CAPACITY = FVK_MAX_COMMAND_BUFFERS; + using BufferList = utils::FixedCapacityVector>; + VkDevice mDevice; + VkCommandPool mPool; + ActiveBuffers mSubmitted; + std::vector> mBuffers; + int8_t mRecording; + }; + + // Manages a set of command buffers and semaphores, exposing an API that is significantly simpler + // than the raw Vulkan API. + // + // The manager's API primarily consists of get() and flush(). The "get" method acquires a fresh + // command buffer in the recording state, while the "flush" method releases a command buffer and + // changes its state from recording to executing. Some of the operational details are listed below. + // + // - Manages a dependency chain of submitted command buffers using VkSemaphore. + // - This creates a guarantee of in-order execution. + // - Semaphores are recycled to prevent create / destroy churn. + // + // - Allows 1 user to inject a "dependency" semaphore that stalls the next flush. + // - This is used for asynchronous acquisition of a swap chain image, since the GPU + // might require a valid swap chain image when it starts executing the command buffer. + // + // - Allows 1 user to listen to the most recent flush event using a "finished" VkSemaphore. + // - This is used to trigger presentation of the swap chain image. + // + // - Allows off-thread queries of command buffer status. + // - Exposes an "updateFences" method that transfers current fence status into atomics. + // - Users can examine these atomic variables (see VulkanCmdFence) to determine status. + // - We do this because vkGetFenceStatus must be called from the rendering thread. + // + class VulkanCommands { + public: + VulkanCommands(VkDevice device, VkQueue queue, uint32_t queueFamilyIndex, + VkQueue protectedQueue, uint32_t protectedQueueFamilyIndex, VulkanContext* context, + VulkanResourceAllocator* allocator); + + void terminate(); + + // Creates a "current" command buffer if none exists, otherwise returns the current one. + VulkanCommandBuffer& get(); + + // Creates a "current" protected capable command buffer if none exists, otherwise + // returns the current one. + VulkanCommandBuffer& getProtected(); + + // Submits the current command buffer if it exists, then sets "current" to null. + // If there are no outstanding commands then nothing happens and this returns false. + bool flush(); + + // Returns the "rendering finished" semaphore for the most recent flush and removes + // it from the existing dependency chain. This is especially useful for setting up + // vkQueuePresentKHR. + VkSemaphore acquireFinishedSignal() { + VkSemaphore ret = mLastSubmit; + mLastSubmit = VK_NULL_HANDLE; + return ret; + } + + // Takes a semaphore that signals when the next flush can occur. Only one injected + // semaphore is allowed per flush. Useful after calling vkAcquireNextImageKHR. + void injectDependency(VkSemaphore next) { + mInjectedDependency = next; + } + + // Destroys all command buffers that are no longer in use. + void gc(); - void popGroupMarker(); + // Waits for all outstanding command buffers to finish. + void wait(); - void insertEventMarker(char const* string, uint32_t len); + // Updates the atomic "status" variable in every extant fence. + void updateFences(); - std::string getTopGroupMarker() const; +#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS) + void pushGroupMarker(char const* str, VulkanGroupMarkers::Timestamp timestamp = {}); + void popGroupMarker(); + void insertEventMarker(char const* string, uint32_t len); + std::string getTopGroupMarker() const; #endif -private: - static constexpr int CAPACITY = FVK_MAX_COMMAND_BUFFERS; - VkDevice const mDevice; - VkQueue const mQueue; - VkCommandPool const mPool; - VulkanContext const* mContext; - - // int8 only goes up to 127, therefore capacity must be less than that. - static_assert(CAPACITY < 128); - int8_t mCurrentCommandBufferIndex = -1; - VkSemaphore mSubmissionSignal = {}; - VkSemaphore mInjectedSignal = {}; - utils::FixedCapacityVector> mStorage; - VkFence mFences[CAPACITY] = {}; - VkSemaphore mSubmissionSignals[CAPACITY] = {}; - uint8_t mAvailableBufferCount = CAPACITY; - CommandBufferObserver* mObserver = nullptr; + private: + + static constexpr int CAPACITY = FVK_MAX_COMMAND_BUFFERS; + VkDevice const mDevice; + VkQueue const mProtectedQueue; + // For defered initialization if/when we need protected content + uint32_t const mProtectedQueueFamilyIndex; + VulkanResourceAllocator* mAllocator; + VulkanContext* mContext; + + // int8 only goes up to 127, therefore capacity must be less than that. + static_assert(CAPACITY < 128); + + std::unique_ptr mPool; + std::unique_ptr mProtectedPool; + + VkSemaphore mInjectedDependency = VK_NULL_HANDLE; + VkSemaphore mLastSubmit = VK_NULL_HANDLE; #if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS) - std::unique_ptr mGroupMarkers; - std::unique_ptr mCarriedOverMarkers; + std::unique_ptr mGroupMarkers; + std::unique_ptr mCarriedOverMarkers; #endif -}; + }; } // namespace filament::backend diff --git a/filament/backend/src/vulkan/VulkanContext.cpp b/filament/backend/src/vulkan/VulkanContext.cpp index fc634eaebfe..36cbbab5937 100644 --- a/filament/backend/src/vulkan/VulkanContext.cpp +++ b/filament/backend/src/vulkan/VulkanContext.cpp @@ -121,7 +121,7 @@ void VulkanTimestamps::beginQuery(VulkanCommandBuffer const* commands, vkCmdWriteTimestamp(cmdbuffer, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, mPool, index); // We stash this because getResult might come before the query is actually processed. - query->setFence(commands->fence); + query->setFence(commands->getFenceStatus()); } void VulkanTimestamps::endQuery(VulkanCommandBuffer const* commands, diff --git a/filament/backend/src/vulkan/VulkanContext.h b/filament/backend/src/vulkan/VulkanContext.h index 68c44dfdcee..11eafaf9c67 100644 --- a/filament/backend/src/vulkan/VulkanContext.h +++ b/filament/backend/src/vulkan/VulkanContext.h @@ -82,6 +82,7 @@ class VulkanTimestamps { }; struct VulkanRenderPass { + VulkanCommandBuffer* cmdBuffer; VulkanRenderTarget* renderTarget; VkRenderPass renderPass; RenderPassParams params; diff --git a/filament/backend/src/vulkan/VulkanDriver.cpp b/filament/backend/src/vulkan/VulkanDriver.cpp index 567a34bc53b..cd953a4bf8f 100644 --- a/filament/backend/src/vulkan/VulkanDriver.cpp +++ b/filament/backend/src/vulkan/VulkanDriver.cpp @@ -198,8 +198,11 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& contex mResourceAllocator(driverConfig.handleArenaSize, driverConfig.disableHandleUseAfterFreeCheck), mResourceManager(&mResourceAllocator), mThreadSafeResourceManager(&mResourceAllocator), - mCommands(mPlatform->getDevice(), mPlatform->getGraphicsQueue(), - mPlatform->getGraphicsQueueFamilyIndex(), &mContext, &mResourceAllocator), + mCommands(mPlatform->getDevice(), mPlatform->getGraphicsQueue(), + mPlatform->getGraphicsQueueFamilyIndex(), + mPlatform->getProtectedGraphicsQueue(), + mPlatform->getProtectedGraphicsQueueFamilyIndex(), + &mContext, &mResourceAllocator), mPipelineLayoutCache(mPlatform->getDevice(), &mResourceAllocator), mPipelineCache(mPlatform->getDevice(), mAllocator), mStagePool(mAllocator, &mCommands), @@ -690,8 +693,14 @@ void VulkanDriver::destroyRenderTarget(Handle rth) { } void VulkanDriver::createFenceR(Handle fh, int) { - VulkanCommandBuffer const& commandBuffer = mCommands.get(); - mResourceAllocator.construct(fh, commandBuffer.fence); + VulkanCommandBuffer* cmdbuf = nullptr; + if (mCurrentRenderPass.cmdBuffer) { + cmdbuf = mCurrentRenderPass.cmdBuffer; + } + else { + cmdbuf = &mCommands.get(); + } + mResourceAllocator.construct(fh, cmdbuf->getFenceStatus()); } void VulkanDriver::createSwapChainR(Handle sch, void* nativeWindow, uint64_t flags) { @@ -703,6 +712,13 @@ void VulkanDriver::createSwapChainR(Handle sch, void* nativeWindow, auto swapChain = mResourceAllocator.construct(sch, mPlatform, mContext, mAllocator, &mCommands, &mResourceAllocator, mStagePool, nativeWindow, flags); mResourceManager.acquire(swapChain); + + if (flags & backend::SWAP_CHAIN_CONFIG_PROTECTED_CONTENT) { + if (!isProtectedContentSupported()) { + utils::slog.w << "protected swapchain requested, but Vulkan does not support it" + << utils::io::endl; + } + } } void VulkanDriver::createSwapChainHeadlessR(Handle sch, uint32_t width, @@ -1257,6 +1273,7 @@ void VulkanDriver::beginRenderPass(Handle rth, const RenderPassP sc->markFirstRenderPass(); } } + mIsRenderPassProtected = rt->isProtected(); #if FVK_ENABLED(FVK_DEBUG_TEXTURE) if (rt->hasDepth()) { @@ -1269,7 +1286,7 @@ void VulkanDriver::beginRenderPass(Handle rth, const RenderPassP // If that's the case, we need to change the layout of the texture to DEPTH_SAMPLER, which is a // more general layout. Otherwise, we prefer the DEPTH_ATTACHMENT layout, which is optimal for // the non-sampling case. - VulkanCommandBuffer& commands = mCommands.get(); + VulkanCommandBuffer& commands = getCommandBuffer(); VkCommandBuffer const cmdbuffer = commands.buffer(); // Scissor is reset with each render pass @@ -1390,7 +1407,7 @@ void VulkanDriver::endRenderPass(int) { FVK_SYSTRACE_CONTEXT(); FVK_SYSTRACE_START("endRenderPass"); - VulkanCommandBuffer& commands = mCommands.get(); + VulkanCommandBuffer& commands = getCommandBuffer(); VkCommandBuffer cmdbuffer = commands.buffer(); vkCmdEndRenderPass(cmdbuffer); @@ -1415,7 +1432,7 @@ void VulkanDriver::nextSubpass(int) { assert_invariant(renderTarget); assert_invariant(mCurrentRenderPass.params.subpassMask); - vkCmdNextSubpass(mCommands.get().buffer(), VK_SUBPASS_CONTENTS_INLINE); + vkCmdNextSubpass(getCommandBuffer().buffer(), VK_SUBPASS_CONTENTS_INLINE); mPipelineCache.bindRenderPass(mCurrentRenderPass.renderPass, ++mCurrentRenderPass.currentSubpass); @@ -1464,8 +1481,8 @@ void VulkanDriver::setPushConstant(backend::ShaderStage stage, uint8_t index, backend::PushConstantVariant value) { assert_invariant(mBoundPipeline.program && "Expect a program when writing to push constants"); VulkanCommands* commands = &mCommands; - mBoundPipeline.program->writePushConstant(commands, mBoundPipeline.pipelineLayout, stage, index, - value); + mBoundPipeline.program->writePushConstant(commands, (mIsContentProtected || mIsRenderPassProtected), + mBoundPipeline.pipelineLayout, stage, index, value); } void VulkanDriver::insertEventMarker(char const* string) { @@ -1655,7 +1672,7 @@ void VulkanDriver::bindPipeline(PipelineState const& pipelineState) { FVK_SYSTRACE_CONTEXT(); FVK_SYSTRACE_START("draw"); - VulkanCommandBuffer* commands = &mCommands.get(); + VulkanCommandBuffer* commands = &getCommandBuffer(); const VulkanVertexBufferInfo& vbi = *mResourceAllocator.handle_cast(pipelineState.vertexBufferInfo); @@ -1737,7 +1754,7 @@ void VulkanDriver::bindRenderPrimitive(Handle rph) { FVK_SYSTRACE_CONTEXT(); FVK_SYSTRACE_START("bindRenderPrimitive"); - VulkanCommandBuffer* commands = &mCommands.get(); + VulkanCommandBuffer* commands = &getCommandBuffer(); VkCommandBuffer cmdbuffer = commands->buffer(); const VulkanRenderPrimitive& prim = *mResourceAllocator.handle_cast(rph); commands->acquire(prim.indexBuffer); @@ -1778,7 +1795,7 @@ void VulkanDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t ins FVK_SYSTRACE_CONTEXT(); FVK_SYSTRACE_START("draw2"); - VulkanCommandBuffer& commands = mCommands.get(); + VulkanCommandBuffer& commands = getCommandBuffer(); VkCommandBuffer cmdbuffer = commands.buffer(); mDescriptorSetManager.commit(&commands, mBoundPipeline.pipelineLayout, @@ -1809,7 +1826,7 @@ void VulkanDriver::dispatchCompute(Handle program, math::uint3 workGr } void VulkanDriver::scissor(Viewport scissorBox) { - VulkanCommandBuffer& commands = mCommands.get(); + VulkanCommandBuffer& commands = getCommandBuffer(); VkCommandBuffer cmdbuffer = commands.buffer(); // TODO: it's a common case that scissor() is called with (0, 0, maxint, maxint) diff --git a/filament/backend/src/vulkan/VulkanDriver.h b/filament/backend/src/vulkan/VulkanDriver.h index 7dd95209342..da58a91eae9 100644 --- a/filament/backend/src/vulkan/VulkanDriver.h +++ b/filament/backend/src/vulkan/VulkanDriver.h @@ -111,6 +111,15 @@ class VulkanDriver final : public DriverBase { private: void collectGarbage(); + inline VulkanCommandBuffer& getCommandBuffer() { + // Check if protection encoding is needed: + if (mIsRenderPassProtected || mIsContentProtected) + return mCommands.getProtected(); + else + return mCommands.get(); + } + bool mIsRenderPassProtected = false; + bool mIsContentProtected = false; VulkanPlatform* mPlatform = nullptr; std::unique_ptr mTimestamps; diff --git a/filament/backend/src/vulkan/VulkanHandles.cpp b/filament/backend/src/vulkan/VulkanHandles.cpp index 43c05c6904c..f8dd7b44002 100644 --- a/filament/backend/src/vulkan/VulkanHandles.cpp +++ b/filament/backend/src/vulkan/VulkanHandles.cpp @@ -162,9 +162,14 @@ PushConstantDescription::PushConstantDescription(backend::Program const& program } } -void PushConstantDescription::write(VulkanCommands* commands, VkPipelineLayout layout, - backend::ShaderStage stage, uint8_t index, backend::PushConstantVariant const& value) { - VulkanCommandBuffer* cmdbuf = &(commands->get()); +void PushConstantDescription::write(VulkanCommands* commands, bool isProtected, + VkPipelineLayout layout, backend::ShaderStage stage, uint8_t index, + backend::PushConstantVariant const& value) { + VulkanCommandBuffer* cmdbuf; + if (isProtected) + cmdbuf = &(commands->getProtected()); + else + cmdbuf = &(commands->get()); uint32_t binaryValue = 0; UTILS_UNUSED_IN_RELEASE auto const& types = mTypes[(uint8_t) stage]; if (std::holds_alternative(value)) { @@ -338,6 +343,8 @@ VulkanRenderTarget::VulkanRenderTarget(VkDevice device, VkPhysicalDevice physica continue; } + mProtected |= texture->getIsProtected(); + attachments.push_back(attachment); mInfo->colors.set(index); @@ -384,6 +391,10 @@ VulkanRenderTarget::VulkanRenderTarget(VkDevice device, VkPhysicalDevice physica if (depth.texture) { auto depthTexture = depth.texture; + + // largely unecessary but for completeness + mProtected |= depthTexture->getIsProtected(); + mInfo->depthIndex = (uint8_t) attachments.size(); attachments.push_back(depth); mResources.acquire(depthTexture); diff --git a/filament/backend/src/vulkan/VulkanHandles.h b/filament/backend/src/vulkan/VulkanHandles.h index 60d3702a728..d838a09fc03 100644 --- a/filament/backend/src/vulkan/VulkanHandles.h +++ b/filament/backend/src/vulkan/VulkanHandles.h @@ -185,8 +185,9 @@ struct PushConstantDescription { uint32_t getVkRangeCount() const noexcept { return mRangeCount; } - void write(VulkanCommands* commands, VkPipelineLayout layout, backend::ShaderStage stage, - uint8_t index, backend::PushConstantVariant const& value); + void write(VulkanCommands* commands, bool isProtected, VkPipelineLayout layout, + backend::ShaderStage stage, uint8_t index, + backend::PushConstantVariant const& value); private: static constexpr uint32_t ENTRY_SIZE = sizeof(uint32_t); @@ -218,9 +219,11 @@ struct VulkanProgram : public HwProgram, VulkanResource { return mInfo->pushConstantDescription.getVkRanges(); } - inline void writePushConstant(VulkanCommands* commands, VkPipelineLayout layout, - backend::ShaderStage stage, uint8_t index, backend::PushConstantVariant const& value) { - mInfo->pushConstantDescription.write(commands, layout, stage, index, value); + inline void writePushConstant(VulkanCommands* commands, bool isProtected, + VkPipelineLayout layout, backend::ShaderStage stage, uint8_t index, + backend::PushConstantVariant const& value) { + mInfo->pushConstantDescription.write(commands, isProtected, layout, + stage, index, value); } #if FVK_ENABLED_DEBUG_SAMPLER_NAME diff --git a/filament/backend/src/vulkan/VulkanPipelineCache.cpp b/filament/backend/src/vulkan/VulkanPipelineCache.cpp index 4c440a00857..dd730fdab73 100644 --- a/filament/backend/src/vulkan/VulkanPipelineCache.cpp +++ b/filament/backend/src/vulkan/VulkanPipelineCache.cpp @@ -66,17 +66,12 @@ void VulkanPipelineCache::bindPipeline(VulkanCommandBuffer* commands) { VkCommandBuffer const cmdbuffer = commands->buffer(); PipelineCacheEntry* cacheEntry = getOrCreatePipeline(); - // Check if the required pipeline is already bound. - if (cacheEntry->handle == commands->pipeline()) { - return; - } // If an error occurred, allow higher levels to handle it gracefully. assert_invariant(cacheEntry != nullptr && "Failed to create/find pipeline"); mBoundPipeline = mPipelineRequirements; vkCmdBindPipeline(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, cacheEntry->handle); - commands->setPipeline(cacheEntry->handle); } VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() noexcept { diff --git a/filament/backend/src/vulkan/platform/VulkanPlatformSwapChainImpl.cpp b/filament/backend/src/vulkan/platform/VulkanPlatformSwapChainImpl.cpp index 3037f64af74..46a19226b06 100644 --- a/filament/backend/src/vulkan/platform/VulkanPlatformSwapChainImpl.cpp +++ b/filament/backend/src/vulkan/platform/VulkanPlatformSwapChainImpl.cpp @@ -29,7 +29,7 @@ namespace filament::backend { namespace { std::tuple createImageAndMemory(VulkanContext const& context, - VkDevice device, VkExtent2D extent, VkFormat format) { + VkDevice device, VkExtent2D extent, VkFormat format, bool isProtected) { bool const isDepth = isVkDepthFormat(format); // Filament expects blit() to work with any texture, so we almost always set these usage flags // (see copyFrame() and readPixels()). @@ -38,6 +38,7 @@ std::tuple createImageAndMemory(VulkanContext const& co VkImageCreateInfo imageInfo { .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .flags = isProtected ? VK_IMAGE_CREATE_PROTECTED_BIT : VkImageCreateFlags(0), .imageType = VK_IMAGE_TYPE_2D, .format = format, .extent = {extent.width, extent.height, 1}, @@ -59,7 +60,8 @@ std::tuple createImageAndMemory(VulkanContext const& co vkGetImageMemoryRequirements(device, image, &memReqs); uint32_t memoryTypeIndex - = context.selectMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + = context.selectMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | + (isProtected ? VK_MEMORY_PROPERTY_PROTECTED_BIT : 0U)); FILAMENT_CHECK_POSTCONDITION(memoryTypeIndex < VK_MAX_MEMORY_TYPES) << "VulkanPlatformSwapChainImpl: unable to find a memory type that meets requirements."; @@ -110,8 +112,8 @@ void VulkanPlatformSwapChainImpl::destroy() { mSwapChainBundle.colors.clear(); } -VkImage VulkanPlatformSwapChainImpl::createImage(VkExtent2D extent, VkFormat format) { - auto [image, memory] = createImageAndMemory(mContext, mDevice, extent, format); +VkImage VulkanPlatformSwapChainImpl::createImage(VkExtent2D extent, VkFormat format, bool isProtected) { + auto [image, memory] = createImageAndMemory(mContext, mDevice, extent, format, isProtected); mMemory.insert({image, memory}); return image; } @@ -248,7 +250,7 @@ VkResult VulkanPlatformSurfaceSwapChain::create() { mSwapChainBundle.colorFormat = surfaceFormat.format; mSwapChainBundle.depthFormat = selectDepthFormat(mContext.getAttachmentDepthStencilFormats(), mHasStencil); - mSwapChainBundle.depth = createImage(mSwapChainBundle.extent, mSwapChainBundle.depthFormat); + mSwapChainBundle.depth = createImage(mSwapChainBundle.extent, mSwapChainBundle.depthFormat, mIsProtected); mSwapChainBundle.isProtected = mIsProtected; FVK_LOGI << "vkCreateSwapchain" @@ -356,13 +358,14 @@ VulkanPlatformHeadlessSwapChain::VulkanPlatformHeadlessSwapChain(VulkanContext c images.reserve(HEADLESS_SWAPCHAIN_SIZE); images.resize(HEADLESS_SWAPCHAIN_SIZE); for (size_t i = 0; i < HEADLESS_SWAPCHAIN_SIZE; ++i) { - images[i] = createImage(extent, mSwapChainBundle.colorFormat); + assert_invariant(mSwapChainBundle.isProtected == false); + images[i] = createImage(extent, mSwapChainBundle.colorFormat, false); } bool const hasStencil = (flags & backend::SWAP_CHAIN_HAS_STENCIL_BUFFER) != 0; mSwapChainBundle.depthFormat = selectDepthFormat(mContext.getAttachmentDepthStencilFormats(), hasStencil); - mSwapChainBundle.depth = createImage(extent, mSwapChainBundle.depthFormat); + mSwapChainBundle.depth = createImage(extent, mSwapChainBundle.depthFormat, mSwapChainBundle.isProtected); } VulkanPlatformHeadlessSwapChain::~VulkanPlatformHeadlessSwapChain() { diff --git a/filament/backend/src/vulkan/platform/VulkanPlatformSwapChainImpl.h b/filament/backend/src/vulkan/platform/VulkanPlatformSwapChainImpl.h index 2a715a210c2..ff1179d4ec3 100644 --- a/filament/backend/src/vulkan/platform/VulkanPlatformSwapChainImpl.h +++ b/filament/backend/src/vulkan/platform/VulkanPlatformSwapChainImpl.h @@ -73,7 +73,7 @@ struct VulkanPlatformSwapChainImpl : public Platform::SwapChain { // Non-virtual override-able method void destroy(); - VkImage createImage(VkExtent2D extent, VkFormat format); + VkImage createImage(VkExtent2D extent, VkFormat format, bool isProtected); VulkanContext const& mContext; VkDevice mDevice;