Flutter Engine Uber Docs
Docs for the entire Flutter Engine repo.
 
Loading...
Searching...
No Matches
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
12#include "flutter/fml/thread.h"
15
16namespace impeller {
17
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_;
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
54FenceWaiterVK::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
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_) {
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
88static 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
98void FenceWaiterVK::Main() {
100 fml::Thread::ThreadConfig{"IplrVkFenceWait"});
101 // Since this thread mostly waits on fences, it doesn't need to be fast.
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
130void 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
139bool 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
Wraps a closure that is invoked in the destructor unless released by the caller.
Definition closure.h:32
static void SetCurrentThreadName(const ThreadConfig &config)
Definition thread.cc:135
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
VkDevice device
Definition main.cc:69
#define FML_DCHECK(condition)
Definition logging.h:122
@ kEfficiency
Request CPU affinity for the efficiency cores.
bool RequestAffinity(CpuAffinity affinity)
Request the given affinity for the current thread.
std::function< void()> closure
Definition closure.h:14
bool Main(const fml::CommandLine &command_line)
static std::vector< vk::Fence > GetFencesForWaitSet(const WaitSet &set)
std::vector< std::shared_ptr< WaitSetEntry > > WaitSet
Definition ref_ptr.h:261
The ThreadConfig is the thread info include thread name, thread priority.
Definition thread.h:35
#define TRACE_EVENT0(category_group, name)
#define VALIDATION_LOG
Definition validation.h:91