blob: 11ee7f0b7db72a0045bc78471b82e0aac1d05ba3 [file] [log] [blame]
// 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