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