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 "flutter/third_party/abseil-cpp/absl/container/flat_hash_map.h"
13 #include "fml/closure.h"
14 #include "impeller/base/thread.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 Register a cleanup callback that will be invokved with the
235  /// provided user data when the handle is destroyed.
236  ///
237  /// This operation is not guaranteed to run immediately. It will
238  /// complete in a finite amount of time on any thread as long as
239  /// there is a reactor worker and the reactor itself is not being
240  /// torn down.
241  ///
242  /// @param[in] handle The handle to attach the cleanup to.
243  /// @param[in] callback The cleanup callback to execute.
244  ///
245  /// @return If the operation was successfully queued for completion.
246  ///
247  bool RegisterCleanupCallback(const HandleGLES& handle,
248  const fml::closure& callback);
249 
250  //----------------------------------------------------------------------------
251  /// @brief Perform a reaction on the current thread if able.
252  ///
253  /// It is safe to call this simultaneously from multiple threads
254  /// at the same time.
255  ///
256  /// @return If a reaction was performed on the calling thread.
257  ///
258  [[nodiscard]] bool React();
259 
260  private:
261  /// @brief Storage for either a GL handle or sync fence.
262  struct GLStorage {
263  union {
264  GLuint handle;
265  GLsync sync;
266  uint64_t integer;
267  };
268  };
269  static_assert(sizeof(GLStorage) == sizeof(uint64_t));
270 
271  struct LiveHandle {
272  std::optional<GLStorage> name;
273  std::optional<std::string> pending_debug_label;
274  bool pending_collection = false;
275  fml::ScopedCleanupClosure callback = {};
276 
277  LiveHandle() = default;
278 
279  explicit LiveHandle(std::optional<GLStorage> p_name) : name(p_name) {}
280 
281  constexpr bool IsLive() const { return name.has_value(); }
282  };
283 
284  std::unique_ptr<ProcTableGLES> proc_table_;
285 
286  mutable Mutex ops_mutex_;
287  std::map<std::thread::id, std::vector<Operation>> ops_ IPLR_GUARDED_BY(
288  ops_mutex_);
289 
290  using LiveHandles = absl::flat_hash_map<const HandleGLES,
291  LiveHandle,
292  HandleGLES::Hash,
293  HandleGLES::Equal>;
294  mutable RWMutex handles_mutex_;
295  LiveHandles handles_ IPLR_GUARDED_BY(handles_mutex_);
296  int32_t handles_to_collect_count_ IPLR_GUARDED_BY(handles_mutex_) = 0;
297 
298  mutable Mutex workers_mutex_;
299  mutable std::map<WorkerID, std::weak_ptr<Worker>> workers_ IPLR_GUARDED_BY(
300  workers_mutex_);
301 
302  bool can_set_debug_labels_ = false;
303  bool is_valid_ = false;
304 
305  bool ReactOnce();
306 
307  bool HasPendingOperations() const;
308 
309  bool CanReactOnCurrentThread() const;
310 
311  bool ConsolidateHandles();
312 
313  bool FlushOps();
314 
315  void SetupDebugGroups();
316 
317  std::optional<GLStorage> GetHandle(const HandleGLES& handle) const;
318 
319  static std::optional<GLStorage> CreateGLHandle(const ProcTableGLES& gl,
320  HandleType type);
321 
322  static bool CollectGLHandle(const ProcTableGLES& gl,
324  GLStorage handle);
325 
326  ReactorGLES(const ReactorGLES&) = delete;
327 
328  ReactorGLES& operator=(const ReactorGLES&) = delete;
329 };
330 
331 } // namespace impeller
332 
333 #endif // FLUTTER_IMPELLER_RENDERER_BACKEND_GLES_REACTOR_GLES_H_
GLenum type
Represents a handle to an underlying OpenGL object. Unlike OpenGL object handles, these handles can b...
Definition: handle_gles.h:37
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
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