Flutter Engine
The Flutter Engine
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
16namespace impeller {
17
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_;
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
55FenceWaiterVK::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 waiter_thread_->join();
63}
64
65bool FenceWaiterVK::AddFence(vk::UniqueFence fence,
66 const fml::closure& callback) {
67 if (!fence || !callback) {
68 return false;
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 false;
75 }
76 wait_set_.emplace_back(WaitSetEntry::Create(std::move(fence), callback));
77 }
78 wait_set_cv_.notify_one();
79 return true;
80}
81
82static std::vector<vk::Fence> GetFencesForWaitSet(const WaitSet& set) {
83 std::vector<vk::Fence> fences;
84 for (const auto& entry : set) {
85 if (!entry->IsSignalled()) {
86 fences.emplace_back(entry->GetFence());
87 }
88 }
89 return fences;
90}
91
92void FenceWaiterVK::Main() {
94 fml::Thread::ThreadConfig{"IplrVkFenceWait"});
95 // Since this thread mostly waits on fences, it doesn't need to be fast.
97
98 while (true) {
99 // We'll read the terminate_ flag within the lock below.
100 bool terminate = false;
101
102 {
103 std::unique_lock lock(wait_set_mutex_);
104
105 // If there are no fences to wait on, wait on the condition variable.
106 wait_set_cv_.wait(lock,
107 [&]() { return !wait_set_.empty() || terminate_; });
108
109 // Still under the lock, check if the waiter has been terminated.
110 terminate = terminate_;
111 }
112
113 if (terminate) {
114 WaitUntilEmpty();
115 break;
116 }
117
118 if (!Wait()) {
119 break;
120 }
121 }
122}
123
124void FenceWaiterVK::WaitUntilEmpty() {
125 // Note, there is no lock because once terminate_ is set to true, no other
126 // fence can be added to the wait set. Just in case, here's a FML_DCHECK:
127 FML_DCHECK(terminate_) << "Fence waiter must be terminated.";
128 while (!wait_set_.empty() && Wait()) {
129 // Intentionally empty.
130 }
131}
132
133bool FenceWaiterVK::Wait() {
134 // Snapshot the wait set and wait on the fences.
135 WaitSet wait_set;
136 {
137 std::scoped_lock lock(wait_set_mutex_);
138 wait_set = wait_set_;
139 }
140
141 using namespace std::literals::chrono_literals;
142
143 // Check if the context had died in the meantime.
144 auto device_holder = device_holder_.lock();
145 if (!device_holder) {
146 return false;
147 }
148
149 const auto& device = device_holder->GetDevice();
150 // Wait for one or more fences to be signaled. Any additional fences added
151 // to the waiter will be serviced in the next pass. If a fence that is going
152 // to be signaled at an abnormally long deadline is the only one in the set,
153 // a timeout will bail out the wait.
154 auto fences = GetFencesForWaitSet(wait_set);
155 if (fences.empty()) {
156 return true;
157 }
158
159 auto result = device.waitForFences(
160 /*fenceCount=*/fences.size(),
161 /*pFences=*/fences.data(),
162 /*waitAll=*/false,
163 /*timeout=*/std::chrono::nanoseconds{100ms}.count());
164 if (!(result == vk::Result::eSuccess || result == vk::Result::eTimeout)) {
165 VALIDATION_LOG << "Fence waiter encountered an unexpected error. Tearing "
166 "down the waiter thread.";
167 return false;
168 }
169
170 // One or more fences have been signaled. Find out which ones and update
171 // their signaled statuses.
172 {
173 TRACE_EVENT0("impeller", "CheckFenceStatus");
174 for (auto& entry : wait_set) {
175 entry->UpdateSignalledStatus(device);
176 }
177 wait_set.clear();
178 }
179
180 // Quickly acquire the wait set lock and erase signaled entries. Make sure
181 // the mutex is unlocked before calling the destructors of the erased
182 // entries. These might touch allocators.
183 WaitSet erased_entries;
184 {
185 static constexpr auto is_signalled = [](const auto& entry) {
186 return entry->IsSignalled();
187 };
188 std::scoped_lock lock(wait_set_mutex_);
189
190 // TODO(matanlurey): Iterate the list 1x by copying is_signaled into erased.
191 std::copy_if(wait_set_.begin(), wait_set_.end(),
192 std::back_inserter(erased_entries), is_signalled);
193 wait_set_.erase(
194 std::remove_if(wait_set_.begin(), wait_set_.end(), is_signalled),
195 wait_set_.end());
196 }
197
198 {
199 TRACE_EVENT0("impeller", "ClearSignaledFences");
200 // Erase the erased entries which will invoke callbacks.
201 erased_entries.clear(); // Bit redundant because of scope but hey.
202 }
203
204 return true;
205}
206
208 {
209 std::scoped_lock lock(wait_set_mutex_);
210 terminate_ = true;
211 }
212 wait_set_cv_.notify_one();
213}
214
215} // 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
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)
VkDevice device
Definition: main.cc:53
FlKeyEvent uint64_t FlKeyResponderAsyncCallback callback
GAsyncResult * result
#define FML_DCHECK(condition)
Definition: logging.h:103
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not set
Definition: switches.h:76
Definition: ascii_trie.cc:9
@ kEfficiency
Request CPU affinity for the efficiency cores.
bool RequestAffinity(CpuAffinity affinity)
Request the given affinity for the current thread.
Definition: cpu_affinity.cc:26
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:256
The ThreadConfig is the thread info include thread name, thread priority.
Definition: thread.h:35
#define TRACE_EVENT0(category_group, name)
Definition: trace_event.h:131
#define VALIDATION_LOG
Definition: validation.h:73