Flutter Impeller
reactor_gles.h
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 #ifndef FLUTTER_IMPELLER_RENDERER_BACKEND_GLES_REACTOR_GLES_H_
6 #define FLUTTER_IMPELLER_RENDERER_BACKEND_GLES_REACTOR_GLES_H_
7 
8 #include <functional>
9 #include <memory>
10 #include <vector>
11 
12 #include "fml/closure.h"
13 #include "impeller/base/thread.h"
16 #include "third_party/abseil-cpp/absl/container/flat_hash_map.h"
17 
18 namespace impeller {
19 
20 //------------------------------------------------------------------------------
21 /// @brief The reactor attempts to make thread-safe usage of OpenGL ES
22 /// easier to reason about.
23 ///
24 /// In the other Impeller backends (like Metal and Vulkan),
25 /// resources can be created, used, and deleted on any thread with
26 /// relatively few restrictions. However, OpenGL resources can only
27 /// be created, used, and deleted on a thread on which an OpenGL
28 /// context (or one in the same sharegroup) is current.
29 ///
30 /// There aren't too many OpenGL contexts to go around and making
31 /// the caller reason about the timing and threading requirement
32 /// only when the OpenGL backend is in use is tedious. To work
33 /// around this tedium, there is an abstraction between the
34 /// resources and their handles in OpenGL. The reactor is this
35 /// abstraction.
36 ///
37 /// The reactor is thread-safe and can created, used, and collected
38 /// on any thread.
39 ///
40 /// Reactor handles `HandleGLES` can be created, used, and collected
41 /// on any thread. These handles can be to textures, buffers, etc..
42 ///
43 /// Operations added to the reactor are guaranteed to run on a
44 /// worker within a finite amount of time unless the reactor itself
45 /// is torn down or there are no workers. These operations may run
46 /// on the calling thread immediately if a worker is active on the
47 /// current thread and can perform reactions. The operations are
48 /// guaranteed to run with an OpenGL context current and all reactor
49 /// handles having live OpenGL handle counterparts.
50 ///
51 /// Creating a handle in the reactor doesn't mean an OpenGL handle
52 /// is created immediately. OpenGL handles become live before the
53 /// next reaction. Similarly, dropping the last reference to a
54 /// reactor handle means that the OpenGL handle will be deleted at
55 /// some point in the near future.
56 ///
57 class ReactorGLES {
58  public:
59  using WorkerID = UniqueID;
60 
61  //----------------------------------------------------------------------------
62  /// @brief A delegate implemented by a thread on which an OpenGL context
63  /// is current. There may be multiple workers for the reactor to
64  /// perform reactions on. In that case, it is the workers
65  /// responsibility to ensure that all of them use either the same
66  /// OpenGL context or multiple OpenGL contexts in the same
67  /// sharegroup.
68  ///
69  class Worker {
70  public:
71  virtual ~Worker() = default;
72 
73  //--------------------------------------------------------------------------
74  /// @brief Determines the ability of the worker to service a reaction
75  /// on the current thread. The OpenGL context must be current on
76  /// the thread if the worker says it is able to service a
77  /// reaction.
78  ///
79  /// @param[in] reactor The reactor
80  ///
81  /// @return If the worker is able to service a reaction. The reactor
82  /// assumes the context is already current if true.
83  ///
85  const ReactorGLES& reactor) const = 0;
86  };
87 
88  //----------------------------------------------------------------------------
89  /// @brief Create a new reactor. There are expensive and only one per
90  /// application instance is necessary.
91  ///
92  /// @param[in] gl The proc table for GL access. This is necessary for the
93  /// reactor to be able to create and collect OpenGL handles.
94  ///
95  explicit ReactorGLES(std::unique_ptr<ProcTableGLES> gl);
96 
97  //----------------------------------------------------------------------------
98  /// @brief Destroy a reactor.
99  ///
100  ~ReactorGLES();
101 
102  //----------------------------------------------------------------------------
103  /// @brief If this is a valid reactor. Invalid reactors must be discarded
104  /// immediately.
105  ///
106  /// @return If this reactor is valid.
107  ///
108  bool IsValid() const;
109 
110  //----------------------------------------------------------------------------
111  /// @brief Adds a worker to the reactor. Each new worker must ensure that
112  /// the context it manages is the same as the other workers in the
113  /// reactor or in the same sharegroup.
114  ///
115  /// @param[in] worker The worker
116  ///
117  /// @return The worker identifier. This identifier can be used to remove
118  /// the worker from the reactor later.
119  ///
120  WorkerID AddWorker(std::weak_ptr<Worker> worker);
121 
122  //----------------------------------------------------------------------------
123  /// @brief Remove a previously added worker from the reactor. If the
124  /// reactor has no workers, pending added operations will never
125  /// run.
126  ///
127  /// @param[in] id The worker identifier previously returned by `AddWorker`.
128  ///
129  /// @return If a worker with the given identifer was successfully removed
130  /// from the reactor.
131  ///
132  bool RemoveWorker(WorkerID id);
133 
134  //----------------------------------------------------------------------------
135  /// @brief Get the OpenGL proc. table the reactor uses to manage handles.
136  ///
137  /// @return The proc table.
138  ///
139  const ProcTableGLES& GetProcTable() const;
140 
141  //----------------------------------------------------------------------------
142  /// @brief Returns the OpenGL handle for a reactor handle if one is
143  /// available. This is typically only safe to call within a
144  /// reaction. That is, within a `ReactorGLES::Operation`.
145  ///
146  /// Asking for the OpenGL handle before the reactor has a chance
147  /// to reactor will return `std::nullopt`.
148  ///
149  /// This can be called on any thread but is typically useless
150  /// outside of a reaction since the handle is useless outside of a
151  /// reactor operation.
152  ///
153  /// @param[in] handle The reactor handle.
154  ///
155  /// @return The OpenGL handle if the reactor has had a chance to react.
156  /// `std::nullopt` otherwise.
157  ///
158  std::optional<GLuint> GetGLHandle(const HandleGLES& handle) const;
159 
160  std::optional<GLsync> GetGLFence(const HandleGLES& handle) const;
161 
162  //----------------------------------------------------------------------------
163  /// @brief Create a reactor handle.
164  ///
165  /// This can be called on any thread. Even one that doesn't have
166  /// an OpenGL context.
167  ///
168  /// @param[in] type The type of handle to create.
169  /// @param[in] external_handle An already created GL handle if one exists.
170  ///
171  /// @return The reactor handle.
172  ///
173  HandleGLES CreateHandle(HandleType type, GLuint external_handle = GL_NONE);
174 
175  /// @brief Create a handle that is not managed by `ReactorGLES`.
176  /// @details This behaves just like `CreateHandle` but it doesn't add the
177  /// handle to ReactorGLES::handles_ and the creation is executed
178  /// synchronously, so it must be called from a proper thread. The benefit of
179  /// this is that it avoid synchronization and hash table lookups when
180  /// creating/accessing the handle.
181  /// @param type The type of handle to create.
182  /// @return The reactor handle.
184 
185  //----------------------------------------------------------------------------
186  /// @brief Collect a reactor handle.
187  ///
188  /// This can be called on any thread. Even one that doesn't have
189  /// an OpenGL context.
190  ///
191  /// @param[in] handle The reactor handle handle
192  ///
193  void CollectHandle(HandleGLES handle);
194 
195  //----------------------------------------------------------------------------
196  /// @brief Set the debug label on a reactor handle.
197  ///
198  /// This call ensures that the OpenGL debug label is propagated to
199  /// even the OpenGL handle hasn't been created at the time the
200  /// caller sets the label.
201  ///
202  /// @param[in] handle The handle
203  /// @param[in] label The label
204  ///
205  void SetDebugLabel(const HandleGLES& handle, std::string_view label);
206 
207  //----------------------------------------------------------------------------
208  /// @brief Whether the device is capable of writing debug labels.
209  ///
210  /// This function is useful for short circuiting expensive debug
211  /// labeling.
212  bool CanSetDebugLabels() const;
213 
214  using Operation = std::function<void(const ReactorGLES& reactor)>;
215 
216  //----------------------------------------------------------------------------
217  /// @brief Adds an operation that the reactor runs on a worker that
218  /// ensures that an OpenGL context is current.
219  ///
220  /// This operation is not guaranteed to run immediately. It will
221  /// complete in a finite amount of time on any thread as long as
222  /// there is a reactor worker and the reactor itself is not being
223  /// torn down.
224  ///
225  /// @param[in] operation The operation
226  /// @param[in] defer If false, the reactor attempts to React after
227  /// adding this operation.
228  ///
229  /// @return If the operation was successfully queued for completion.
230  ///
231  [[nodiscard]] bool AddOperation(Operation operation, bool defer = false);
232 
233  //----------------------------------------------------------------------------
234  /// @brief Whether a reaction can be performed on the current thread.
235  ///
236  /// If this returns false, queued operations remain valid but
237  /// cannot be flushed until a worker with a current OpenGL context
238  /// is available on this thread.
239  ///
240  [[nodiscard]] bool CanReactOnCurrentThread() const;
241 
242  //----------------------------------------------------------------------------
243  /// @brief Register a cleanup callback that will be invokved with the
244  /// provided user data when the handle is destroyed.
245  ///
246  /// This operation is not guaranteed to run immediately. It will
247  /// complete in a finite amount of time on any thread as long as
248  /// there is a reactor worker and the reactor itself is not being
249  /// torn down.
250  ///
251  /// @param[in] handle The handle to attach the cleanup to.
252  /// @param[in] callback The cleanup callback to execute.
253  ///
254  /// @return If the operation was successfully queued for completion.
255  ///
256  bool RegisterCleanupCallback(const HandleGLES& handle,
257  const fml::closure& callback);
258 
259  //----------------------------------------------------------------------------
260  /// @brief Perform a reaction on the current thread if able.
261  ///
262  /// It is safe to call this simultaneously from multiple threads
263  /// at the same time.
264  ///
265  /// @return If a reaction was performed on the calling thread.
266  ///
267  [[nodiscard]] bool React();
268 
269  private:
270  /// @brief Storage for either a GL handle or sync fence.
271  struct GLStorage {
272  union {
273  GLuint handle;
274  GLsync sync;
275  uint64_t integer;
276  };
277  };
278  static_assert(sizeof(GLStorage) == sizeof(uint64_t));
279 
280  struct LiveHandle {
281  std::optional<GLStorage> name;
282  std::optional<std::string> pending_debug_label;
283  bool pending_collection = false;
284  fml::ScopedCleanupClosure callback = {};
285 
286  LiveHandle() = default;
287 
288  explicit LiveHandle(std::optional<GLStorage> p_name) : name(p_name) {}
289 
290  constexpr bool IsLive() const { return name.has_value(); }
291  };
292 
293  std::unique_ptr<ProcTableGLES> proc_table_;
294 
295  mutable Mutex ops_mutex_;
296  std::map<std::thread::id, std::vector<Operation>> ops_
297  IPLR_GUARDED_BY(ops_mutex_);
298 
299  using LiveHandles = absl::flat_hash_map<const HandleGLES,
300  LiveHandle,
301  HandleGLES::Hash,
302  HandleGLES::Equal>;
303  mutable RWMutex handles_mutex_;
304  LiveHandles handles_ IPLR_GUARDED_BY(handles_mutex_);
305  int32_t handles_to_collect_count_ IPLR_GUARDED_BY(handles_mutex_) = 0;
306 
307  mutable Mutex workers_mutex_;
308  mutable std::map<WorkerID, std::weak_ptr<Worker>> workers_
309  IPLR_GUARDED_BY(workers_mutex_);
310 
311  bool can_set_debug_labels_ = false;
312  bool is_valid_ = false;
313 
314  bool ReactOnce();
315 
316  bool HasPendingOperations() const;
317 
318  bool ConsolidateHandles();
319 
320  bool FlushOps();
321 
322  void SetupDebugGroups();
323 
324  std::optional<GLStorage> GetHandle(const HandleGLES& handle) const;
325 
326  static std::optional<GLStorage> CreateGLHandle(const ProcTableGLES& gl,
327  HandleType type);
328 
329  static bool CollectGLHandle(const ProcTableGLES& gl,
330  HandleType type,
331  GLStorage handle);
332 
333  ReactorGLES(const ReactorGLES&) = delete;
334 
335  ReactorGLES& operator=(const ReactorGLES&) = delete;
336 };
337 
338 } // namespace impeller
339 
340 #endif // FLUTTER_IMPELLER_RENDERER_BACKEND_GLES_REACTOR_GLES_H_
Represents a handle to an underlying OpenGL object. Unlike OpenGL object handles, these handles can b...
Definition: handle_gles.h:42
A delegate implemented by a thread on which an OpenGL context is current. There may be multiple worke...
Definition: reactor_gles.h:69
virtual bool CanReactorReactOnCurrentThreadNow(const ReactorGLES &reactor) const =0
Determines the ability of the worker to service a reaction on the current thread. The OpenGL context ...
The reactor attempts to make thread-safe usage of OpenGL ES easier to reason about.
Definition: reactor_gles.h:57
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
bool CanReactOnCurrentThread() const
Whether a reaction can be performed on the current thread.
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
std::shared_ptr< ReactorGLES > reactor