Flutter Impeller
fence_waiter_vk.cc
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
6 
7 #include <algorithm>
8 #include <chrono>
9 #include <utility>
10 
11 #include "flutter/fml/cpu_affinity.h"
12 #include "flutter/fml/thread.h"
13 #include "flutter/fml/trace_event.h"
15 
16 namespace impeller {
17 
18 class WaitSetEntry {
19  public:
20  static std::shared_ptr<WaitSetEntry> Create(vk::UniqueFence p_fence,
21  const fml::closure& p_callback) {
22  return std::shared_ptr<WaitSetEntry>(
23  new WaitSetEntry(std::move(p_fence), p_callback));
24  }
25 
26  void UpdateSignalledStatus(const vk::Device& device) {
27  if (is_signalled_) {
28  return;
29  }
30  is_signalled_ = device.getFenceStatus(fence_.get()) == vk::Result::eSuccess;
31  }
32 
33  const vk::Fence& GetFence() const { return fence_.get(); }
34 
35  bool IsSignalled() const { return is_signalled_; }
36 
37  private:
38  vk::UniqueFence fence_;
39  fml::ScopedCleanupClosure callback_;
40  bool is_signalled_ = false;
41 
42  WaitSetEntry(vk::UniqueFence p_fence, const fml::closure& p_callback)
43  : fence_(std::move(p_fence)),
44  callback_(fml::ScopedCleanupClosure{p_callback}) {}
45 
46  WaitSetEntry(const WaitSetEntry&) = delete;
47 
48  WaitSetEntry(WaitSetEntry&&) = delete;
49 
50  WaitSetEntry& operator=(const WaitSetEntry&) = delete;
51 
52  WaitSetEntry& operator=(WaitSetEntry&&) = delete;
53 };
54 
55 FenceWaiterVK::FenceWaiterVK(std::weak_ptr<DeviceHolderVK> device_holder)
56  : device_holder_(std::move(device_holder)) {
57  waiter_thread_ = std::make_unique<std::thread>([&]() { Main(); });
58 }
59 
61  Terminate();
62 }
63 
64 bool FenceWaiterVK::AddFence(vk::UniqueFence fence,
65  const fml::closure& callback) {
66  if (!fence || !callback) {
67  return false;
68  }
69  {
70  // Maintain the invariant that terminate_ is accessed only under the lock.
71  std::scoped_lock lock(wait_set_mutex_);
72  if (terminate_) {
73  return false;
74  }
75  wait_set_.emplace_back(WaitSetEntry::Create(std::move(fence), callback));
76  }
77  wait_set_cv_.notify_one();
78  return true;
79 }
80 
81 static std::vector<vk::Fence> GetFencesForWaitSet(const WaitSet& set) {
82  std::vector<vk::Fence> fences;
83  for (const auto& entry : set) {
84  if (!entry->IsSignalled()) {
85  fences.emplace_back(entry->GetFence());
86  }
87  }
88  return fences;
89 }
90 
91 void FenceWaiterVK::Main() {
92  fml::Thread::SetCurrentThreadName(
93  fml::Thread::ThreadConfig{"IplrVkFenceWait"});
94  // Since this thread mostly waits on fences, it doesn't need to be fast.
95  fml::RequestAffinity(fml::CpuAffinity::kEfficiency);
96 
97  while (true) {
98  // We'll read the terminate_ flag within the lock below.
99  bool terminate = false;
100 
101  {
102  std::unique_lock lock(wait_set_mutex_);
103 
104  // If there are no fences to wait on, wait on the condition variable.
105  wait_set_cv_.wait(lock,
106  [&]() { return !wait_set_.empty() || terminate_; });
107 
108  // Still under the lock, check if the waiter has been terminated.
109  terminate = terminate_;
110  }
111 
112  if (terminate) {
113  WaitUntilEmpty();
114  break;
115  }
116 
117  if (!Wait()) {
118  break;
119  }
120  }
121 }
122 
123 void FenceWaiterVK::WaitUntilEmpty() {
124  // Note, there is no lock because once terminate_ is set to true, no other
125  // fence can be added to the wait set. Just in case, here's a FML_DCHECK:
126  FML_DCHECK(terminate_) << "Fence waiter must be terminated.";
127  while (!wait_set_.empty() && Wait()) {
128  // Intentionally empty.
129  }
130 }
131 
132 bool FenceWaiterVK::Wait() {
133  // Snapshot the wait set and wait on the fences.
134  WaitSet wait_set;
135  {
136  std::scoped_lock lock(wait_set_mutex_);
137  wait_set = wait_set_;
138  }
139 
140  using namespace std::literals::chrono_literals;
141 
142  // Check if the context had died in the meantime.
143  auto device_holder = device_holder_.lock();
144  if (!device_holder) {
145  return false;
146  }
147 
148  const auto& device = device_holder->GetDevice();
149  // Wait for one or more fences to be signaled. Any additional fences added
150  // to the waiter will be serviced in the next pass. If a fence that is going
151  // to be signaled at an abnormally long deadline is the only one in the set,
152  // a timeout will bail out the wait.
153  auto fences = GetFencesForWaitSet(wait_set);
154  if (fences.empty()) {
155  return true;
156  }
157 
158  auto result = device.waitForFences(
159  /*fenceCount=*/fences.size(),
160  /*pFences=*/fences.data(),
161  /*waitAll=*/false,
162  /*timeout=*/std::chrono::nanoseconds{100ms}.count());
163  if (!(result == vk::Result::eSuccess || result == vk::Result::eTimeout)) {
164  VALIDATION_LOG << "Fence waiter encountered an unexpected error. Tearing "
165  "down the waiter thread.";
166  return false;
167  }
168 
169  // One or more fences have been signaled. Find out which ones and update
170  // their signaled statuses.
171  {
172  TRACE_EVENT0("impeller", "CheckFenceStatus");
173  for (auto& entry : wait_set) {
174  entry->UpdateSignalledStatus(device);
175  }
176  wait_set.clear();
177  }
178 
179  // Quickly acquire the wait set lock and erase signaled entries. Make sure
180  // the mutex is unlocked before calling the destructors of the erased
181  // entries. These might touch allocators.
182  WaitSet erased_entries;
183  {
184  static constexpr auto is_signalled = [](const auto& entry) {
185  return entry->IsSignalled();
186  };
187  std::scoped_lock lock(wait_set_mutex_);
188 
189  // TODO(matanlurey): Iterate the list 1x by copying is_signaled into erased.
190  std::copy_if(wait_set_.begin(), wait_set_.end(),
191  std::back_inserter(erased_entries), is_signalled);
192  wait_set_.erase(
193  std::remove_if(wait_set_.begin(), wait_set_.end(), is_signalled),
194  wait_set_.end());
195  }
196 
197  {
198  TRACE_EVENT0("impeller", "ClearSignaledFences");
199  // Erase the erased entries which will invoke callbacks.
200  erased_entries.clear(); // Bit redundant because of scope but hey.
201  }
202 
203  return true;
204 }
205 
207  {
208  std::scoped_lock lock(wait_set_mutex_);
209  if (terminate_) {
210  return;
211  }
212  terminate_ = true;
213  }
214  wait_set_cv_.notify_one();
215  waiter_thread_->join();
216 }
217 
218 } // namespace impeller
bool AddFence(vk::UniqueFence fence, const fml::closure &callback)
const vk::Fence & GetFence() const
static std::shared_ptr< WaitSetEntry > Create(vk::UniqueFence p_fence, const fml::closure &p_callback)
void UpdateSignalledStatus(const vk::Device &device)
bool IsSignalled() const
bool Main(const fml::CommandLine &command_line)
static std::vector< vk::Fence > GetFencesForWaitSet(const WaitSet &set)
std::vector< std::shared_ptr< WaitSetEntry > > WaitSet
Definition: comparable.h:95
#define VALIDATION_LOG
Definition: validation.h:91