| 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
5 #include "impeller/renderer/backend/vulkan/fence_waiter_vk.h"
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"
14 #include "impeller/base/validation.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
60 FenceWaiterVK::~FenceWaiterVK() {
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
206 void FenceWaiterVK::Terminate() {
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
impeller::FenceWaiterVK::Terminate
void Terminate()
Definition: fence_waiter_vk.cc:206
impeller::FenceWaiterVK::~FenceWaiterVK
~FenceWaiterVK()
Definition: fence_waiter_vk.cc:60
impeller::FenceWaiterVK::AddFence
bool AddFence(vk::UniqueFence fence, const fml::closure &callback)
Definition: fence_waiter_vk.cc:64
impeller::WaitSetEntry
Definition: fence_waiter_vk.cc:18
impeller::WaitSetEntry::GetFence
const vk::Fence & GetFence() const
Definition: fence_waiter_vk.cc:33
impeller::WaitSetEntry::Create
static std::shared_ptr< WaitSetEntry > Create(vk::UniqueFence p_fence, const fml::closure &p_callback)
Definition: fence_waiter_vk.cc:20
impeller::WaitSetEntry::UpdateSignalledStatus
void UpdateSignalledStatus(const vk::Device &device)
Definition: fence_waiter_vk.cc:26
impeller::WaitSetEntry::IsSignalled
bool IsSignalled() const
Definition: fence_waiter_vk.cc:35
fence_waiter_vk.h
fml
Definition: metal_screenshot.h:16
impeller
Definition: allocation.cc:12
impeller::Main
bool Main(const fml::CommandLine &command_line)
Definition: shader_archive_main.cc:13
impeller::GetFencesForWaitSet
static std::vector< vk::Fence > GetFencesForWaitSet(const WaitSet &set)
Definition: fence_waiter_vk.cc:81
impeller::WaitSet
std::vector< std::shared_ptr< WaitSetEntry > > WaitSet
Definition: fence_waiter_vk.h:21
std
Definition: comparable.h:95
validation.h
VALIDATION_LOG
#define VALIDATION_LOG
Definition: validation.h:91