| Flutter Impeller |

reactor_gles.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/gles/reactor_gles.h"
6
7 #include <algorithm>
8
9 #include "flutter/fml/trace_event.h"
10 #include "fml/closure.h"
11 #include "fml/logging.h"
12 #include "impeller/base/validation.h"
13
14 namespace impeller {
15
16 // static
17 std::optional<ReactorGLES::GLStorage> ReactorGLES::CreateGLHandle(
18 const ProcTableGLES& gl,
19 HandleType type) {
20 GLStorage handle = GLStorage{.handle = GL_NONE};
21 switch (type) {
22 case HandleType::kUnknown:
23 return std::nullopt;
24 case HandleType::kTexture:
25 gl.GenTextures(1u, &handle.handle);
26 return handle;
27 case HandleType::kBuffer:
28 gl.GenBuffers(1u, &handle.handle);
29 return handle;
30 case HandleType::kProgram:
31 return GLStorage{.handle = gl.CreateProgram()};
32 case HandleType::kRenderBuffer:
33 gl.GenRenderbuffers(1u, &handle.handle);
34 return handle;
35 case HandleType::kFrameBuffer:
36 gl.GenFramebuffers(1u, &handle.handle);
37 return handle;
38 case HandleType::kFence:
39 return GLStorage{.sync = gl.FenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0)};
40 }
41 return std::nullopt;
42 }
43
44 // static
45 bool ReactorGLES::CollectGLHandle(const ProcTableGLES& gl,
46 HandleType type,
47 ReactorGLES::GLStorage handle) {
48 switch (type) {
49 case HandleType::kUnknown:
50 return false;
51 case HandleType::kTexture:
52 gl.DeleteTextures(1u, &handle.handle);
53 return true;
54 case HandleType::kBuffer:
55 gl.DeleteBuffers(1u, &handle.handle);
56 return true;
57 case HandleType::kProgram:
58 gl.DeleteProgram(handle.handle);
59 return true;
60 case HandleType::kRenderBuffer:
61 gl.DeleteRenderbuffers(1u, &handle.handle);
62 return true;
63 case HandleType::kFrameBuffer:
64 gl.DeleteFramebuffers(1u, &handle.handle);
65 return true;
66 case HandleType::kFence:
67 gl.DeleteSync(handle.sync);
68 break;
69 }
70 return false;
71 }
72
73 ReactorGLES::ReactorGLES(std::unique_ptr<ProcTableGLES> gl)
74 : proc_table_(std::move(gl)) {
75 if (!proc_table_ || !proc_table_->IsValid()) {
76 VALIDATION_LOG << "Proc table was invalid.";
77 return;
78 }
79 can_set_debug_labels_ = proc_table_->GetDescription()->HasDebugExtension();
80 is_valid_ = true;
81 }
82
83 ReactorGLES::~ReactorGLES() {
84 if (CanReactOnCurrentThread()) {
85 for (auto& handle : handles_) {
86 if (handle.second.name.has_value()) {
87 CollectGLHandle(*proc_table_, handle.first.GetType(),
88 handle.second.name.value());
89 }
90 }
91 proc_table_->Flush();
92 }
93 }
94
95 bool ReactorGLES::IsValid() const {
96 return is_valid_;
97 }
98
99 bool ReactorGLES::CanSetDebugLabels() const {
100 return can_set_debug_labels_;
101 }
102
103 ReactorGLES::WorkerID ReactorGLES::AddWorker(std::weak_ptr<Worker> worker) {
104 Lock lock(workers_mutex_);
105 auto id = WorkerID{};
106 workers_[id] = std::move(worker);
107 return id;
108 }
109
110 bool ReactorGLES::RemoveWorker(WorkerID worker) {
111 Lock lock(workers_mutex_);
112 return workers_.erase(worker) == 1;
113 }
114
115 bool ReactorGLES::HasPendingOperations() const {
116 auto thread_id = std::this_thread::get_id();
117 Lock ops_lock(ops_mutex_);
118 auto it = ops_.find(thread_id);
119 return it != ops_.end() ? !it->second.empty() : false;
120 }
121
122 const ProcTableGLES& ReactorGLES::GetProcTable() const {
123 FML_DCHECK(IsValid());
124 return *proc_table_;
125 }
126
127 std::optional<ReactorGLES::GLStorage> ReactorGLES::GetHandle(
128 const HandleGLES& handle) const {
129 if (handle.untracked_id_.has_value()) {
130 return ReactorGLES::GLStorage{.integer = handle.untracked_id_.value()};
131 }
132
133 ReaderLock handles_lock(handles_mutex_);
134 if (auto found = handles_.find(handle); found != handles_.end()) {
135 if (found->second.pending_collection) {
136 VALIDATION_LOG
137 << "Attempted to acquire a handle that was pending collection.";
138 return std::nullopt;
139 }
140 std::optional<ReactorGLES::GLStorage> name = found->second.name;
141 if (!name.has_value()) {
142 VALIDATION_LOG << "Attempt to acquire a handle outside of an operation.";
143 return std::nullopt;
144 }
145 return name;
146 }
147 VALIDATION_LOG << "Attempted to acquire an invalid GL handle.";
148 return std::nullopt;
149 }
150
151 std::optional<GLuint> ReactorGLES::GetGLHandle(const HandleGLES& handle) const {
152 if (handle.GetType() == HandleType::kFence) {
153 return std::nullopt;
154 }
155 std::optional<ReactorGLES::GLStorage> gl_handle = GetHandle(handle);
156 if (gl_handle.has_value()) {
157 return gl_handle->handle;
158 }
159 return std::nullopt;
160 }
161
162 std::optional<GLsync> ReactorGLES::GetGLFence(const HandleGLES& handle) const {
163 if (handle.GetType() != HandleType::kFence) {
164 return std::nullopt;
165 }
166 std::optional<ReactorGLES::GLStorage> gl_handle = GetHandle(handle);
167 if (gl_handle.has_value()) {
168 return gl_handle->sync;
169 }
170 return std::nullopt;
171 }
172
173 bool ReactorGLES::AddOperation(Operation operation, bool defer) {
174 if (!operation) {
175 return false;
176 }
177 auto thread_id = std::this_thread::get_id();
178 {
179 Lock ops_lock(ops_mutex_);
180 ops_[thread_id].emplace_back(std::move(operation));
181 }
182 // Attempt a reaction if able but it is not an error if this isn't possible.
183 if (!defer) {
184 [[maybe_unused]] auto result = React();
185 }
186 return true;
187 }
188
189 bool ReactorGLES::RegisterCleanupCallback(const HandleGLES& handle,
190 const fml::closure& callback) {
191 if (handle.IsDead()) {
192 return false;
193 }
194 FML_DCHECK(!handle.untracked_id_.has_value());
195 WriterLock handles_lock(handles_mutex_);
196 if (auto found = handles_.find(handle); found != handles_.end()) {
197 found->second.callback = fml::ScopedCleanupClosure(callback);
198 return true;
199 }
200 return false;
201 }
202
203 HandleGLES ReactorGLES::CreateUntrackedHandle(HandleType type) const {
204 FML_DCHECK(CanReactOnCurrentThread());
205 auto new_handle = HandleGLES::Create(type);
206 std::optional<ReactorGLES::GLStorage> gl_handle =
207 CreateGLHandle(GetProcTable(), type);
208 if (gl_handle.has_value()) {
209 new_handle.untracked_id_ = gl_handle.value().integer;
210 }
211 return new_handle;
212 }
213
214 HandleGLES ReactorGLES::CreateHandle(HandleType type, GLuint external_handle) {
215 if (type == HandleType::kUnknown) {
216 return HandleGLES::DeadHandle();
217 }
218 auto new_handle = HandleGLES::Create(type);
219 if (new_handle.IsDead()) {
220 return HandleGLES::DeadHandle();
221 }
222
223 std::optional<ReactorGLES::GLStorage> gl_handle;
224 if (external_handle != GL_NONE) {
225 gl_handle = ReactorGLES::GLStorage{.handle = external_handle};
226 } else if (CanReactOnCurrentThread()) {
227 gl_handle = CreateGLHandle(GetProcTable(), type);
228 }
229
230 WriterLock handles_lock(handles_mutex_);
231 handles_[new_handle] = LiveHandle{gl_handle};
232 return new_handle;
233 }
234
235 void ReactorGLES::CollectHandle(HandleGLES handle) {
236 if (handle.IsDead()) {
237 return;
238 }
239 if (handle.untracked_id_.has_value()) {
240 LiveHandle live_handle(GLStorage{.integer = handle.untracked_id_.value()});
241 live_handle.pending_collection = true;
242 WriterLock handles_lock(handles_mutex_);
243 handles_[handle] = std::move(live_handle);
244 } else {
245 WriterLock handles_lock(handles_mutex_);
246 if (auto found = handles_.find(handle); found != handles_.end()) {
247 if (!found->second.pending_collection) {
248 handles_to_collect_count_ += 1;
249 }
250 found->second.pending_collection = true;
251 }
252 }
253 }
254
255 bool ReactorGLES::React() {
256 if (!CanReactOnCurrentThread()) {
257 return false;
258 }
259 TRACE_EVENT0("impeller", "ReactorGLES::React");
260 while (HasPendingOperations()) {
261 if (!ReactOnce()) {
262 return false;
263 }
264 }
265 return true;
266 }
267
268 static DebugResourceType ToDebugResourceType(HandleType type) {
269 switch (type) {
270 case HandleType::kUnknown:
271 FML_UNREACHABLE();
272 case HandleType::kTexture:
273 return DebugResourceType::kTexture;
274 case HandleType::kBuffer:
275 return DebugResourceType::kBuffer;
276 case HandleType::kProgram:
277 return DebugResourceType::kProgram;
278 case HandleType::kRenderBuffer:
279 return DebugResourceType::kRenderBuffer;
280 case HandleType::kFrameBuffer:
281 return DebugResourceType::kFrameBuffer;
282 case HandleType::kFence:
283 return DebugResourceType::kFence;
284 }
285 FML_UNREACHABLE();
286 }
287
288 bool ReactorGLES::ReactOnce() {
289 if (!IsValid()) {
290 return false;
291 }
292 TRACE_EVENT0("impeller", __FUNCTION__);
293 return ConsolidateHandles() && FlushOps();
294 }
295
296 bool ReactorGLES::ConsolidateHandles() {
297 TRACE_EVENT0("impeller", __FUNCTION__);
298 const auto& gl = GetProcTable();
299 std::vector<std::tuple<HandleGLES, std::optional<GLStorage>>>
300 handles_to_delete;
301 std::vector<std::tuple<DebugResourceType, GLint, std::string>>
302 handles_to_name;
303 {
304 WriterLock handles_lock(handles_mutex_);
305 handles_to_delete.reserve(handles_to_collect_count_);
306 handles_to_collect_count_ = 0;
307 for (auto& handle : handles_) {
308 // Collect dead handles.
309 if (handle.second.pending_collection) {
310 handles_to_delete.emplace_back(
311 std::make_tuple(handle.first, handle.second.name));
312 continue;
313 }
314 // Create live handles.
315 if (!handle.second.name.has_value()) {
316 auto gl_handle = CreateGLHandle(gl, handle.first.GetType());
317 if (!gl_handle) {
318 VALIDATION_LOG << "Could not create GL handle.";
319 return false;
320 }
321 handle.second.name = gl_handle;
322 }
323 // Set pending debug labels.
324 if (handle.second.pending_debug_label.has_value() &&
325 handle.first.GetType() != HandleType::kFence) {
326 handles_to_name.emplace_back(std::make_tuple(
327 ToDebugResourceType(handle.first.GetType()),
328 handle.second.name.value().handle,
329 std::move(handle.second.pending_debug_label.value())));
330 handle.second.pending_debug_label = std::nullopt;
331 }
332 }
333 for (const auto& handle_to_delete : handles_to_delete) {
334 handles_.erase(std::get<0>(handle_to_delete));
335 }
336 }
337
338 for (const auto& handle : handles_to_name) {
339 gl.SetDebugLabel(std::get<0>(handle), std::get<1>(handle),
340 std::get<2>(handle));
341 }
342 for (const auto& handle : handles_to_delete) {
343 const std::optional<GLStorage>& storage = std::get<1>(handle);
344 // This could be false if the handle was created and collected without
345 // use. We still need to get rid of map entry.
346 if (storage.has_value()) {
347 CollectGLHandle(gl, std::get<0>(handle).GetType(), storage.value());
348 }
349 }
350
351 return true;
352 }
353
354 bool ReactorGLES::FlushOps() {
355 TRACE_EVENT0("impeller", __FUNCTION__);
356
357 #ifdef IMPELLER_DEBUG
358 // glDebugMessageControl sometimes must be called before glPushDebugGroup:
359 // https://github.com/flutter/flutter/issues/135715#issuecomment-1740153506
360 SetupDebugGroups();
361 #endif
362
363 // Do NOT hold the ops or handles locks while performing operations in case
364 // the ops enqueue more ops.
365 decltype(ops_)::mapped_type ops;
366 auto thread_id = std::this_thread::get_id();
367 {
368 Lock ops_lock(ops_mutex_);
369 std::swap(ops_[thread_id], ops);
370 }
371 for (const auto& op : ops) {
372 TRACE_EVENT0("impeller", "ReactorGLES::Operation");
373 op(*this);
374 }
375 return true;
376 }
377
378 void ReactorGLES::SetupDebugGroups() {
379 // Setup of a default active debug group: Filter everything in.
380 if (can_set_debug_labels_) {
381 proc_table_->DebugMessageControlKHR(GL_DONT_CARE, // source
382 GL_DONT_CARE, // type
383 GL_DONT_CARE, // severity
384 0, // count
385 nullptr, // ids
386 GL_TRUE); // enabled
387 }
388 }
389
390 void ReactorGLES::SetDebugLabel(const HandleGLES& handle,
391 std::string_view label) {
392 FML_DCHECK(handle.GetType() != HandleType::kFence);
393 if (!can_set_debug_labels_) {
394 return;
395 }
396 if (handle.IsDead()) {
397 return;
398 }
399 if (handle.untracked_id_.has_value()) {
400 FML_DCHECK(CanReactOnCurrentThread());
401 const auto& gl = GetProcTable();
402 gl.SetDebugLabel(ToDebugResourceType(handle.GetType()),
403 handle.untracked_id_.value(), label);
404 } else {
405 WriterLock handles_lock(handles_mutex_);
406 if (auto found = handles_.find(handle); found != handles_.end()) {
407 found->second.pending_debug_label = label;
408 }
409 }
410 }
411
412 bool ReactorGLES::CanReactOnCurrentThread() const {
413 std::vector<WorkerID> dead_workers;
414 Lock lock(workers_mutex_);
415 for (const auto& worker : workers_) {
416 auto worker_ptr = worker.second.lock();
417 if (!worker_ptr) {
418 dead_workers.push_back(worker.first);
419 continue;
420 }
421 if (worker_ptr->CanReactorReactOnCurrentThreadNow(*this)) {
422 return true;
423 }
424 }
425 for (const auto& worker_id : dead_workers) {
426 workers_.erase(worker_id);
427 }
428 return false;
429 }
430
431 } // namespace impeller
type
GLenum type
Definition: blit_command_gles.cc:153
impeller::HandleGLES
Represents a handle to an underlying OpenGL object. Unlike OpenGL object handles, these handles can b...
Definition: handle_gles.h:37
impeller::HandleGLES::IsDead
constexpr bool IsDead() const
Determines if the handle is dead.
Definition: handle_gles.h:53
impeller::HandleGLES::GetType
HandleType GetType() const
Definition: handle_gles.h:74
impeller::HandleGLES::DeadHandle
static HandleGLES DeadHandle()
Creates a dead handle.
Definition: handle_gles.h:44
impeller::Lock
Definition: thread.h:72
impeller::ProcTableGLES
Definition: proc_table_gles.h:282
impeller::ReactorGLES::RegisterCleanupCallback
bool RegisterCleanupCallback(const HandleGLES &handle, const fml::closure &callback)
Register a cleanup callback that will be invokved with the provided user data when the handle is dest...
Definition: reactor_gles.cc:189
impeller::ReactorGLES::CanSetDebugLabels
bool CanSetDebugLabels() const
Whether the device is capable of writing debug labels.
Definition: reactor_gles.cc:99
impeller::ReactorGLES::GetGLHandle
std::optional< GLuint > GetGLHandle(const HandleGLES &handle) const
Returns the OpenGL handle for a reactor handle if one is available. This is typically only safe to ca...
Definition: reactor_gles.cc:151
impeller::ReactorGLES::ReactorGLES
ReactorGLES(std::unique_ptr< ProcTableGLES > gl)
Create a new reactor. There are expensive and only one per application instance is necessary.
Definition: reactor_gles.cc:73
impeller::ReactorGLES::CreateUntrackedHandle
HandleGLES CreateUntrackedHandle(HandleType type) const
Create a handle that is not managed by ReactorGLES.
Definition: reactor_gles.cc:203
impeller::ReactorGLES::GetGLFence
std::optional< GLsync > GetGLFence(const HandleGLES &handle) const
Definition: reactor_gles.cc:162
impeller::ReactorGLES::CollectHandle
void CollectHandle(HandleGLES handle)
Collect a reactor handle.
Definition: reactor_gles.cc:235
impeller::ReactorGLES::CreateHandle
HandleGLES CreateHandle(HandleType type, GLuint external_handle=GL_NONE)
Create a reactor handle.
Definition: reactor_gles.cc:214
impeller::ReactorGLES::Operation
std::function< void(const ReactorGLES &reactor)> Operation
Definition: reactor_gles.h:214
impeller::ReactorGLES::React
bool React()
Perform a reaction on the current thread if able.
Definition: reactor_gles.cc:255
impeller::ReactorGLES::~ReactorGLES
~ReactorGLES()
Destroy a reactor.
Definition: reactor_gles.cc:83
impeller::ReactorGLES::GetProcTable
const ProcTableGLES & GetProcTable() const
Get the OpenGL proc. table the reactor uses to manage handles.
Definition: reactor_gles.cc:122
impeller::ReactorGLES::AddOperation
bool AddOperation(Operation operation, bool defer=false)
Adds an operation that the reactor runs on a worker that ensures that an OpenGL context is current.
Definition: reactor_gles.cc:173
impeller::ReactorGLES::RemoveWorker
bool RemoveWorker(WorkerID id)
Remove a previously added worker from the reactor. If the reactor has no workers, pending added opera...
Definition: reactor_gles.cc:110
impeller::ReactorGLES::AddWorker
WorkerID AddWorker(std::weak_ptr< Worker > worker)
Adds a worker to the reactor. Each new worker must ensure that the context it manages is the same as ...
Definition: reactor_gles.cc:103
impeller::ReactorGLES::SetDebugLabel
void SetDebugLabel(const HandleGLES &handle, std::string_view label)
Set the debug label on a reactor handle.
Definition: reactor_gles.cc:390
impeller::ReactorGLES::IsValid
bool IsValid() const
If this is a valid reactor. Invalid reactors must be discarded immediately.
Definition: reactor_gles.cc:95
impeller::WriterLock
Definition: thread.h:113
impeller
Definition: allocation.cc:12
impeller::ToDebugResourceType
static DebugResourceType ToDebugResourceType(HandleType type)
Definition: reactor_gles.cc:268
impeller::DebugResourceType
DebugResourceType
Definition: proc_table_gles.h:272
impeller::DebugResourceType::kBuffer
@ kBuffer
impeller::DebugResourceType::kProgram
@ kProgram
impeller::DebugResourceType::kTexture
@ kTexture
impeller::DebugResourceType::kRenderBuffer
@ kRenderBuffer
impeller::DebugResourceType::kFence
@ kFence
impeller::DebugResourceType::kFrameBuffer
@ kFrameBuffer
impeller::HandleType
HandleType
Definition: handle_gles.h:18
impeller::HandleType::kBuffer
@ kBuffer
impeller::HandleType::kProgram
@ kProgram
impeller::HandleType::kTexture
@ kTexture
impeller::HandleType::kUnknown
@ kUnknown
impeller::HandleType::kRenderBuffer
@ kRenderBuffer
impeller::HandleType::kFence
@ kFence
impeller::HandleType::kFrameBuffer
@ kFrameBuffer
std
Definition: comparable.h:95
reactor_gles.h
impeller::UniqueID
Definition: comparable.h:16
validation.h
VALIDATION_LOG
#define VALIDATION_LOG
Definition: validation.h:91