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 
6 
7 #include <algorithm>
8 
9 #include "flutter/fml/trace_event.h"
10 #include "fml/closure.h"
11 #include "fml/logging.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) {
23  return std::nullopt;
25  gl.GenTextures(1u, &handle.handle);
26  return handle;
28  gl.GenBuffers(1u, &handle.handle);
29  return handle;
31  return GLStorage{.handle = gl.CreateProgram()};
33  gl.GenRenderbuffers(1u, &handle.handle);
34  return handle;
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,
47  ReactorGLES::GLStorage handle) {
48  switch (type) {
50  return false;
52  gl.DeleteTextures(1u, &handle.handle);
53  return true;
55  gl.DeleteBuffers(1u, &handle.handle);
56  return true;
58  gl.DeleteProgram(handle.handle);
59  return true;
61  gl.DeleteRenderbuffers(1u, &handle.handle);
62  return true;
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 
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 
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 
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 
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) {
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 
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 
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 
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 
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 
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 
269  switch (type) {
271  FML_UNREACHABLE();
274  case HandleType::kBuffer:
282  case HandleType::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 
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
GLenum type
Represents a handle to an underlying OpenGL object. Unlike OpenGL object handles, these handles can b...
Definition: handle_gles.h:37
constexpr bool IsDead() const
Determines if the handle is dead.
Definition: handle_gles.h:53
HandleType GetType() const
Definition: handle_gles.h:74
static HandleGLES DeadHandle()
Creates a dead handle.
Definition: handle_gles.h:44
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...
bool CanSetDebugLabels() const
Whether the device is capable of writing debug labels.
Definition: reactor_gles.cc:99
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...
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
HandleGLES CreateUntrackedHandle(HandleType type) const
Create a handle that is not managed by ReactorGLES.
std::optional< GLsync > GetGLFence(const HandleGLES &handle) const
void CollectHandle(HandleGLES handle)
Collect a reactor handle.
HandleGLES CreateHandle(HandleType type, GLuint external_handle=GL_NONE)
Create a reactor handle.
std::function< void(const ReactorGLES &reactor)> Operation
Definition: reactor_gles.h:214
bool React()
Perform a reaction on the current thread if able.
~ReactorGLES()
Destroy a reactor.
Definition: reactor_gles.cc:83
const ProcTableGLES & GetProcTable() const
Get the OpenGL proc. table the reactor uses to manage handles.
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.
bool RemoveWorker(WorkerID id)
Remove a previously added worker from the reactor. If the reactor has no workers, pending added opera...
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 ...
void SetDebugLabel(const HandleGLES &handle, std::string_view label)
Set the debug label on a reactor handle.
bool IsValid() const
If this is a valid reactor. Invalid reactors must be discarded immediately.
Definition: reactor_gles.cc:95
static DebugResourceType ToDebugResourceType(HandleType type)
Definition: comparable.h:95
#define VALIDATION_LOG
Definition: validation.h:91