diff options
author | Charlie Lao <cclao@google.com> | 2024-05-10 19:48:34 -0700 |
---|---|---|
committer | Angle LUCI CQ <angle-scoped@luci-project-accounts.iam.gserviceaccount.com> | 2024-05-15 18:36:08 +0000 |
commit | 5332ab8c6260724bb0729c593c6f5a9772c87985 (patch) | |
tree | 10228f6bf943a54f261bcc7427c02fae72b6f2ce | |
parent | 0ac0603e5f76422279086e0abe22db5d1b866681 (diff) | |
download | angle-5332ab8c6260724bb0729c593c6f5a9772c87985.tar.gz |
Vulkan: Add RefCountedEventRecycler to vk::Renderer
This CL adds event recycler in vk::Renderer to avoid the constant create
and destroy of VkEvents. When RefCountedEvent is destroyed previously,
it now goes into per renderer recycler. When RefCountedEvent is created
previously, it now dips into this recycler and fetch it. Before we issue
VkCmdSetEvent, if this event was from recycler, we also issue
VkCmdResetEvent before VkCmdSetEvebt. When glFinish/EGLSwapBuffer is
called or context gets destroyed, this recycler is purged to keep the
free count under limit.
Bug: b/336844257
Change-Id: I92ec1b183f708112a96c3d06fcfa265024f5aa04
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/5519174
Reviewed-by: Shahbaz Youssefi <syoussefi@chromium.org>
Commit-Queue: Charlie Lao <cclao@google.com>
Reviewed-by: Amirali Abdolrashidi <abdolrashidi@google.com>
-rw-r--r-- | src/libANGLE/renderer/vulkan/ContextVk.cpp | 2 | ||||
-rw-r--r-- | src/libANGLE/renderer/vulkan/vk_helpers.cpp | 34 | ||||
-rw-r--r-- | src/libANGLE/renderer/vulkan/vk_ref_counted_event.cpp | 102 | ||||
-rw-r--r-- | src/libANGLE/renderer/vulkan/vk_ref_counted_event.h | 86 | ||||
-rw-r--r-- | src/libANGLE/renderer/vulkan/vk_renderer.cpp | 2 | ||||
-rw-r--r-- | src/libANGLE/renderer/vulkan/vk_renderer.h | 4 |
6 files changed, 161 insertions, 69 deletions
diff --git a/src/libANGLE/renderer/vulkan/ContextVk.cpp b/src/libANGLE/renderer/vulkan/ContextVk.cpp index 39ca051602..d176779cc8 100644 --- a/src/libANGLE/renderer/vulkan/ContextVk.cpp +++ b/src/libANGLE/renderer/vulkan/ContextVk.cpp @@ -1255,6 +1255,7 @@ void ContextVk::onDestroy(const gl::Context *context) VkDevice device = getDevice(); + mRenderer->getRefCountedEventRecycler()->destroy(getDevice()); mDefaultUniformStorage.release(mRenderer); mEmptyBuffer.release(mRenderer); @@ -7693,6 +7694,7 @@ angle::Result ContextVk::flushImpl(const vk::Semaphore *signalSemaphore, mShareGroupVk->isDueForBufferPoolPrune(mRenderer)) { mShareGroupVk->pruneDefaultBufferPools(mRenderer); + mRenderer->getRefCountedEventRecycler()->destroy(getDevice()); } // Since we just flushed, deferred flush is no longer deferred. diff --git a/src/libANGLE/renderer/vulkan/vk_helpers.cpp b/src/libANGLE/renderer/vulkan/vk_helpers.cpp index 06712331b7..b52ab55536 100644 --- a/src/libANGLE/renderer/vulkan/vk_helpers.cpp +++ b/src/libANGLE/renderer/vulkan/vk_helpers.cpp @@ -1745,8 +1745,12 @@ void CommandBufferHelperCommon::flushSetEventsImpl(Context *context, CommandBuff ASSERT(refCountedEvent.valid()); const ImageMemoryBarrierData &layoutData = kImageMemoryBarrierData[refCountedEvent.getImageLayout()]; - commandBuffer->setEvent(refCountedEvent.getEvent().getHandle(), - GetImageLayoutDstStageMask(context, layoutData)); + VkPipelineStageFlags stageMask = GetImageLayoutDstStageMask(context, layoutData); + if (refCountedEvent.needsReset()) + { + commandBuffer->resetEvent(refCountedEvent.getEvent().getHandle(), stageMask); + } + commandBuffer->setEvent(refCountedEvent.getEvent().getHandle(), stageMask); // We no longer need event, so garbage collect it. mRefCountedEventCollector.emplace_back(std::move(refCountedEvent)); } @@ -6140,8 +6144,8 @@ void ImageHelper::releaseImage(Renderer *renderer) renderer->onMemoryDealloc(mMemoryAllocationType, mAllocationSize, mMemoryTypeIndex, mVmaAllocation.getHandle()); } - mCurrentEvent.release(renderer->getDevice()); - mLastNonShaderReadOnlyEvent.release(renderer->getDevice()); + mCurrentEvent.release(renderer); + mLastNonShaderReadOnlyEvent.release(renderer); renderer->collectGarbage(mUse, &mImage, &mDeviceMemory, &mVmaAllocation); mViewFormats.clear(); mUse.reset(); @@ -6621,8 +6625,8 @@ void ImageHelper::destroy(Renderer *renderer) mVmaAllocation.getHandle()); } - mCurrentEvent.release(device); - mLastNonShaderReadOnlyEvent.release(device); + mCurrentEvent.release(renderer); + mLastNonShaderReadOnlyEvent.release(renderer); mImage.destroy(device); mDeviceMemory.destroy(device); mVmaAllocation.destroy(renderer->getAllocator()); @@ -7117,7 +7121,7 @@ void ImageHelper::barrierImpl(Context *context, { // For now we always use pipelineBarrier for singlebuffer mode. We could use event here in // future. - mCurrentEvent.release(context->getDevice()); + mCurrentEvent.release(context->getRenderer()); const ImageMemoryBarrierData &transition = kImageMemoryBarrierData[mCurrentLayout]; VkMemoryBarrier memoryBarrier = {}; @@ -7174,7 +7178,7 @@ void ImageHelper::barrierImpl(Context *context, } commandBuffer->imageBarrier(srcStageMask, dstStageMask, imageMemoryBarrier); // We use pipelineBarrier here, no needs to wait for events any more. - mCurrentEvent.release(context->getDevice()); + mCurrentEvent.release(context->getRenderer()); } mCurrentLayout = newLayout; @@ -7368,7 +7372,7 @@ void ImageHelper::updateLayoutAndBarrier(Context *context, // Release it. No need to garbage collect since we did not use the event here. ALl // previous use of event should garbage tracked already. - mCurrentEvent.release(context->getDevice()); + mCurrentEvent.release(context->getRenderer()); } mBarrierQueueSerial = queueSerial; } @@ -7483,7 +7487,7 @@ void ImageHelper::updateLayoutAndBarrier(Context *context, mLastNonShaderReadOnlyLayout = ImageLayout::Undefined; if (mLastNonShaderReadOnlyEvent.valid()) { - mLastNonShaderReadOnlyEvent.release(context->getDevice()); + mLastNonShaderReadOnlyEvent.release(context->getRenderer()); } } @@ -7492,7 +7496,7 @@ void ImageHelper::updateLayoutAndBarrier(Context *context, const bool isShaderReadOnly = IsShaderReadOnlyLayout(transitionTo); if (isShaderReadOnly) { - mLastNonShaderReadOnlyEvent.release(context->getDevice()); + mLastNonShaderReadOnlyEvent.release(context->getRenderer()); mLastNonShaderReadOnlyLayout = mCurrentLayout; mCurrentShaderReadStageMask = dstStageMask; } @@ -7511,7 +7515,7 @@ void ImageHelper::updateLayoutAndBarrier(Context *context, { pipelineBarriers->mergeImageBarrier(transitionTo.barrierIndex, srcStageMask, dstStageMask, imageMemoryBarrier); - mCurrentEvent.release(context->getDevice()); + mCurrentEvent.release(context->getRenderer()); } mBarrierQueueSerial = queueSerial; @@ -7532,7 +7536,7 @@ void ImageHelper::setCurrentRefCountedEvent(Context *context, ImageLayoutEventMa ASSERT(context->getRenderer()->getFeatures().useVkEventForImageBarrier.enabled); // If there is already an event, release it first. - mCurrentEvent.release(context->getDevice()); + mCurrentEvent.release(context->getRenderer()); // Create the event if we have not yet so. Otherwise just use the already created event. This // means all images used in the same render pass that has the same layout will be tracked by the @@ -10211,7 +10215,7 @@ angle::Result ImageHelper::copySurfaceImageToBuffer(DisplayVk *displayVk, // We may have a valid event here but we do not have a collector to collect it. Release the // event here to force pipelineBarrier. - mCurrentEvent.release(displayVk->getDevice()); + mCurrentEvent.release(displayVk->getRenderer()); PrimaryCommandBuffer primaryCommandBuffer; ANGLE_TRY(renderer->getCommandBufferOneOff(displayVk, ProtectionType::Unprotected, @@ -10263,7 +10267,7 @@ angle::Result ImageHelper::copyBufferToSurfaceImage(DisplayVk *displayVk, // We may have a valid event here but we do not have a collector to collect it. Release the // event here to force pipelineBarrier. - mCurrentEvent.release(displayVk->getDevice()); + mCurrentEvent.release(displayVk->getRenderer()); PrimaryCommandBuffer commandBuffer; ANGLE_TRY( diff --git a/src/libANGLE/renderer/vulkan/vk_ref_counted_event.cpp b/src/libANGLE/renderer/vulkan/vk_ref_counted_event.cpp index 2390c37ba1..6f93aea8bf 100644 --- a/src/libANGLE/renderer/vulkan/vk_ref_counted_event.cpp +++ b/src/libANGLE/renderer/vulkan/vk_ref_counted_event.cpp @@ -20,52 +20,92 @@ bool RefCountedEvent::init(Context *context, ImageLayout layout) ASSERT(mHandle == nullptr); ASSERT(layout != ImageLayout::Undefined); - mHandle = new AtomicRefCounted<EventAndLayout>; - VkEventCreateInfo createInfo = {}; - createInfo.sType = VK_STRUCTURE_TYPE_EVENT_CREATE_INFO; - // Use device only for performance reasons. - createInfo.flags = context->getFeatures().supportsSynchronization2.enabled - ? VK_EVENT_CREATE_DEVICE_ONLY_BIT_KHR - : 0; - VkResult result = mHandle->get().event.init(context->getDevice(), createInfo); - if (result != VK_SUCCESS) + // First try with recycler. We must issue VkCmdResetEvent before VkCmdSetEvent + if (context->getRenderer()->getRefCountedEventRecycler()->fetch(this)) { - WARN() << "event.init failed. Clean up garbage and retry again"; - // Proactively clean up garbage and retry - context->getRenderer()->cleanupGarbage(); - result = mHandle->get().event.init(context->getDevice(), createInfo); + mHandle->get().needsReset = true; + } + else + { + // If failed to fetch from recycler, then create a new event. + mHandle = new AtomicRefCounted<EventAndLayout>; + VkEventCreateInfo createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_EVENT_CREATE_INFO; + // Use device only for performance reasons. + createInfo.flags = context->getFeatures().supportsSynchronization2.enabled + ? VK_EVENT_CREATE_DEVICE_ONLY_BIT_KHR + : 0; + VkResult result = mHandle->get().event.init(context->getDevice(), createInfo); if (result != VK_SUCCESS) { - // Drivers usually can allocate huge amount of VkEvents, and we should never use that - // many VkEvents under normal situation. If we failed to allocate, there is a high - // chance that we may have a leak somewhere. This macro should help us catch such - // potential bugs in the bots if that happens. - UNREACHABLE(); - // If still fail to create, we just return. An invalid event will trigger - // pipelineBarrier code path - return false; + WARN() << "event.init failed. Clean up garbage and retry again"; + // Proactively clean up garbage and retry + context->getRenderer()->cleanupGarbage(); + result = mHandle->get().event.init(context->getDevice(), createInfo); + if (result != VK_SUCCESS) + { + // Drivers usually can allocate huge amount of VkEvents, and we should never use + // that many VkEvents under normal situation. If we failed to allocate, there is a + // high chance that we may have a leak somewhere. This macro should help us catch + // such potential bugs in the bots if that happens. + UNREACHABLE(); + // If still fail to create, we just return. An invalid event will trigger + // pipelineBarrier code path + return false; + } } + mHandle->get().needsReset = false; } + mHandle->addRef(); mHandle->get().imageLayout = layout; return true; } +void RefCountedEvent::release(Renderer *renderer) +{ + if (mHandle != nullptr) + { + releaseImpl(renderer, renderer->getRefCountedEventRecycler()); + } +} + +template <typename RecyclerT> +void RefCountedEvent::releaseImpl(Renderer *renderer, RecyclerT *recycler) +{ + ASSERT(mHandle != nullptr); + const bool isLastReference = mHandle->getAndReleaseRef() == 1; + if (isLastReference) + { + recycler->recycle(std::move(*this)); + ASSERT(mHandle == nullptr); + } + else + { + mHandle = nullptr; + } +} + +void RefCountedEvent::destroy(VkDevice device) +{ + ASSERT(mHandle != nullptr); + ASSERT(!mHandle->isReferenced()); + mHandle->get().event.destroy(device); + SafeDelete(mHandle); +} + // RefCountedEventsGarbage implementation. bool RefCountedEventsGarbage::destroyIfComplete(Renderer *renderer) { - if (renderer->hasResourceUseFinished(mLifetime)) + if (!renderer->hasResourceUseFinished(mLifetime)) { - for (RefCountedEvent &event : mRefCountedEvents) - { - ASSERT(event.valid()); - event.release(renderer->getDevice()); - ASSERT(!event.valid()); - } - mRefCountedEvents.clear(); - return true; + return false; } - return false; + + RefCountedEventRecycler *recycler = renderer->getRefCountedEventRecycler(); + recycler->releaseOrRecycle(renderer, std::move(mRefCountedEvents)); + + return true; } bool RefCountedEventsGarbage::hasResourceUseSubmitted(Renderer *renderer) const diff --git a/src/libANGLE/renderer/vulkan/vk_ref_counted_event.h b/src/libANGLE/renderer/vulkan/vk_ref_counted_event.h index 10f78c28ec..e657eef0dd 100644 --- a/src/libANGLE/renderer/vulkan/vk_ref_counted_event.h +++ b/src/libANGLE/renderer/vulkan/vk_ref_counted_event.h @@ -15,6 +15,7 @@ #include <queue> #include "common/PackedEnums.h" +#include "common/SimpleMutex.h" #include "common/debug.h" #include "libANGLE/renderer/serial_utils.h" #include "libANGLE/renderer/vulkan/vk_resource.h" @@ -45,6 +46,7 @@ struct EventAndLayout bool valid() const { return event.valid(); } Event event; ImageLayout imageLayout; + bool needsReset; }; // The VkCmdSetEvent is called after VkCmdEndRenderPass and all images that used at the given @@ -98,23 +100,13 @@ class RefCountedEvent final // failed. bool init(Context *context, ImageLayout layout); - // Release one reference count to the underline Event object and destroy if this is the - // very last reference. - void release(VkDevice device) - { - if (mHandle != nullptr) - { - const bool isLastReference = mHandle->getAndReleaseRef() == 1; - if (isLastReference) - { - destroy(device); - } - else - { - mHandle = nullptr; - } - } - } + // Release one reference count to the underline Event object and destroy or recycle the handle + // to renderer's recycler if this is the very last reference. + void release(Renderer *renderer); + + // Destroy the event and mHandle. Caller must ensure there is no outstanding reference to the + // mHandle. + void destroy(VkDevice device); bool valid() const { return mHandle != nullptr; } @@ -132,15 +124,19 @@ class RefCountedEvent final return mHandle->get().imageLayout; } - private: - void destroy(VkDevice device) + bool needsReset() const { - ASSERT(mHandle != nullptr); - ASSERT(!mHandle->isReferenced()); - mHandle->get().event.destroy(device); - SafeDelete(mHandle); + ASSERT(valid()); + return mHandle->get().needsReset; } + private: + // Release one reference count to the underline Event object and destroy or recycle the handle + // to the provided recycler if this is the very last reference. + friend class RefCountedEventRecycler; + template <typename RecyclerT> + void releaseImpl(Renderer *renderer, RecyclerT *recycler); + AtomicRefCounted<EventAndLayout> *mHandle; }; using RefCountedEventCollector = std::vector<RefCountedEvent>; @@ -213,6 +209,50 @@ class RefCountedEventsGarbage final RefCountedEventCollector mRefCountedEvents; }; +// Thread safe event recycler +class RefCountedEventRecycler final +{ + public: + void recycle(RefCountedEvent &&garbageObject) + { + std::lock_guard<angle::SimpleMutex> lock(mMutex); + mFreeStack.recycle(std::move(garbageObject)); + } + + void releaseOrRecycle(Renderer *renderer, RefCountedEventCollector &&eventCollector) + { + // Take lock once and then use event's releaseImpl function to directly recycle into the + // underlying recycling storage. + std::lock_guard<angle::SimpleMutex> lock(mMutex); + for (RefCountedEvent &event : eventCollector) + { + event.releaseImpl(renderer, &mFreeStack); + } + eventCollector.clear(); + } + + bool fetch(RefCountedEvent *outObject) + { + std::lock_guard<angle::SimpleMutex> lock(mMutex); + if (mFreeStack.empty()) + { + return false; + } + mFreeStack.fetch(outObject); + return true; + } + + void destroy(VkDevice device) + { + std::lock_guard<angle::SimpleMutex> lock(mMutex); + mFreeStack.destroy(device); + } + + private: + angle::SimpleMutex mMutex; + Recycler<RefCountedEvent> mFreeStack; +}; + // This wraps data and API for vkCmdWaitEvent call class EventBarrier : angle::NonCopyable { diff --git a/src/libANGLE/renderer/vulkan/vk_renderer.cpp b/src/libANGLE/renderer/vulkan/vk_renderer.cpp index 4c4a49d719..f6486e2fee 100644 --- a/src/libANGLE/renderer/vulkan/vk_renderer.cpp +++ b/src/libANGLE/renderer/vulkan/vk_renderer.cpp @@ -1513,6 +1513,8 @@ void Renderer::onDestroy(vk::Context *context) ASSERT(!hasSharedGarbage()); ASSERT(mOrphanedBufferBlockList.empty()); + mRefCountedEventRecycler.destroy(mDevice); + for (OneOffCommandPool &oneOffCommandPool : mOneOffCommandPoolMap) { oneOffCommandPool.destroy(mDevice); diff --git a/src/libANGLE/renderer/vulkan/vk_renderer.h b/src/libANGLE/renderer/vulkan/vk_renderer.h index 228da259fd..bc168e1748 100644 --- a/src/libANGLE/renderer/vulkan/vk_renderer.h +++ b/src/libANGLE/renderer/vulkan/vk_renderer.h @@ -748,6 +748,8 @@ class Renderer : angle::NonCopyable return mPipelineCacheGraphDumpPath.c_str(); } + vk::RefCountedEventRecycler *getRefCountedEventRecycler() { return &mRefCountedEventRecycler; } + private: angle::Result setupDevice(vk::Context *context, const angle::FeatureOverrides &featureOverrides, @@ -959,6 +961,8 @@ class Renderer : angle::NonCopyable vk::BufferBlockGarbageList mOrphanedBufferBlockList; // Holds RefCountedEvent garbage vk::SharedGarbageList<vk::RefCountedEventsGarbage> mRefCountedEventGarbageList; + // Holds RefCountedEvent that are free and ready to reuse + vk::RefCountedEventRecycler mRefCountedEventRecycler; VkDeviceSize mPendingGarbageSizeLimit; |