| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "impeller/renderer/backend/vulkan/fence_waiter_vk.h" |
| |
| #include <algorithm> |
| #include <chrono> |
| #include <utility> |
| |
| #include "flutter/fml/cpu_affinity.h" |
| #include "flutter/fml/thread.h" |
| #include "flutter/fml/trace_event.h" |
| #include "impeller/base/validation.h" |
| |
| namespace impeller { |
| |
| class WaitSetEntry { |
| public: |
| static std::shared_ptr<WaitSetEntry> Create(vk::UniqueFence p_fence, |
| const fml::closure& p_callback) { |
| return std::shared_ptr<WaitSetEntry>( |
| new WaitSetEntry(std::move(p_fence), p_callback)); |
| } |
| |
| void UpdateSignalledStatus(const vk::Device& device) { |
| if (is_signalled_) { |
| return; |
| } |
| is_signalled_ = device.getFenceStatus(fence_.get()) == vk::Result::eSuccess; |
| } |
| |
| const vk::Fence& GetFence() const { return fence_.get(); } |
| |
| bool IsSignalled() const { return is_signalled_; } |
| |
| private: |
| vk::UniqueFence fence_; |
| fml::ScopedCleanupClosure callback_; |
| bool is_signalled_ = false; |
| |
| WaitSetEntry(vk::UniqueFence p_fence, const fml::closure& p_callback) |
| : fence_(std::move(p_fence)), |
| callback_(fml::ScopedCleanupClosure{p_callback}) {} |
| |
| WaitSetEntry(const WaitSetEntry&) = delete; |
| |
| WaitSetEntry(WaitSetEntry&&) = delete; |
| |
| WaitSetEntry& operator=(const WaitSetEntry&) = delete; |
| |
| WaitSetEntry& operator=(WaitSetEntry&&) = delete; |
| }; |
| |
| FenceWaiterVK::FenceWaiterVK(std::weak_ptr<DeviceHolder> device_holder) |
| : device_holder_(std::move(device_holder)) { |
| waiter_thread_ = std::make_unique<std::thread>([&]() { Main(); }); |
| } |
| |
| FenceWaiterVK::~FenceWaiterVK() { |
| Terminate(); |
| waiter_thread_->join(); |
| } |
| |
| bool FenceWaiterVK::AddFence(vk::UniqueFence fence, |
| const fml::closure& callback) { |
| if (!fence || !callback) { |
| return false; |
| } |
| { |
| // Maintain the invariant that terminate_ is accessed only under the lock. |
| std::scoped_lock lock(wait_set_mutex_); |
| if (terminate_) { |
| return false; |
| } |
| wait_set_.emplace_back(WaitSetEntry::Create(std::move(fence), callback)); |
| } |
| wait_set_cv_.notify_one(); |
| return true; |
| } |
| |
| static std::vector<vk::Fence> GetFencesForWaitSet(const WaitSet& set) { |
| std::vector<vk::Fence> fences; |
| for (const auto& entry : set) { |
| if (!entry->IsSignalled()) { |
| fences.emplace_back(entry->GetFence()); |
| } |
| } |
| return fences; |
| } |
| |
| void FenceWaiterVK::Main() { |
| fml::Thread::SetCurrentThreadName( |
| fml::Thread::ThreadConfig{"io.flutter.impeller.fence_waiter"}); |
| // Since this thread mostly waits on fences, it doesn't need to be fast. |
| fml::RequestAffinity(fml::CpuAffinity::kEfficiency); |
| |
| while (true) { |
| // We'll read the terminate_ flag within the lock below. |
| bool terminate = false; |
| |
| { |
| std::unique_lock lock(wait_set_mutex_); |
| |
| // If there are no fences to wait on, wait on the condition variable. |
| wait_set_cv_.wait(lock, |
| [&]() { return !wait_set_.empty() || terminate_; }); |
| |
| // Still under the lock, check if the waiter has been terminated. |
| terminate = terminate_; |
| } |
| |
| if (terminate) { |
| WaitUntilEmpty(); |
| break; |
| } |
| |
| if (!Wait()) { |
| break; |
| } |
| } |
| } |
| |
| void FenceWaiterVK::WaitUntilEmpty() { |
| // Note, there is no lock because once terminate_ is set to true, no other |
| // fence can be added to the wait set. Just in case, here's a FML_DCHECK: |
| FML_DCHECK(terminate_) << "Fence waiter must be terminated."; |
| while (!wait_set_.empty() && Wait()) { |
| // Intentionally empty. |
| } |
| } |
| |
| bool FenceWaiterVK::Wait() { |
| // Snapshot the wait set and wait on the fences. |
| WaitSet wait_set; |
| { |
| std::scoped_lock lock(wait_set_mutex_); |
| wait_set = wait_set_; |
| } |
| |
| using namespace std::literals::chrono_literals; |
| |
| // Check if the context had died in the meantime. |
| auto device_holder = device_holder_.lock(); |
| if (!device_holder) { |
| return false; |
| } |
| |
| const auto& device = device_holder->GetDevice(); |
| // Wait for one or more fences to be signaled. Any additional fences added |
| // to the waiter will be serviced in the next pass. If a fence that is going |
| // to be signaled at an abnormally long deadline is the only one in the set, |
| // a timeout will bail out the wait. |
| auto fences = GetFencesForWaitSet(wait_set); |
| if (fences.empty()) { |
| return true; |
| } |
| |
| auto result = device.waitForFences( |
| /*fenceCount=*/fences.size(), |
| /*pFences=*/fences.data(), |
| /*waitAll=*/false, |
| /*timeout=*/std::chrono::nanoseconds{100ms}.count()); |
| if (!(result == vk::Result::eSuccess || result == vk::Result::eTimeout)) { |
| VALIDATION_LOG << "Fence waiter encountered an unexpected error. Tearing " |
| "down the waiter thread."; |
| return false; |
| } |
| |
| // One or more fences have been signaled. Find out which ones and update |
| // their signaled statuses. |
| { |
| TRACE_EVENT0("impeller", "CheckFenceStatus"); |
| for (auto& entry : wait_set) { |
| entry->UpdateSignalledStatus(device); |
| } |
| wait_set.clear(); |
| } |
| |
| // Quickly acquire the wait set lock and erase signaled entries. Make sure |
| // the mutex is unlocked before calling the destructors of the erased |
| // entries. These might touch allocators. |
| WaitSet erased_entries; |
| { |
| static constexpr auto is_signalled = [](const auto& entry) { |
| return entry->IsSignalled(); |
| }; |
| std::scoped_lock lock(wait_set_mutex_); |
| |
| // TODO(matanlurey): Iterate the list 1x by copying is_signaled into erased. |
| std::copy_if(wait_set_.begin(), wait_set_.end(), |
| std::back_inserter(erased_entries), is_signalled); |
| wait_set_.erase( |
| std::remove_if(wait_set_.begin(), wait_set_.end(), is_signalled), |
| wait_set_.end()); |
| } |
| |
| { |
| TRACE_EVENT0("impeller", "ClearSignaledFences"); |
| // Erase the erased entries which will invoke callbacks. |
| erased_entries.clear(); // Bit redundant because of scope but hey. |
| } |
| |
| return true; |
| } |
| |
| void FenceWaiterVK::Terminate() { |
| { |
| std::scoped_lock lock(wait_set_mutex_); |
| terminate_ = true; |
| } |
| wait_set_cv_.notify_one(); |
| } |
| |
| } // namespace impeller |