Flutter Engine
 
Loading...
Searching...
No Matches
impeller::ReactorGLES Class Referenceabstract

The reactor attempts to make thread-safe usage of OpenGL ES easier to reason about. More...

#include <reactor_gles.h>

Inheritance diagram for impeller::ReactorGLES:
impeller::testing::TestReactorGLES impeller::testing::TestReactorGLES

Classes

class  Worker
 A delegate implemented by a thread on which an OpenGL context is current. There may be multiple workers for the reactor to perform reactions on. In that case, it is the workers responsibility to ensure that all of them use either the same OpenGL context or multiple OpenGL contexts in the same sharegroup. More...
 

Public Types

using WorkerID = UniqueID
 
using Operation = std::function< void(const ReactorGLES &reactor)>
 

Public Member Functions

 ReactorGLES (std::unique_ptr< ProcTableGLES > gl)
 Create a new reactor. There are expensive and only one per application instance is necessary.
 
 ~ReactorGLES ()
 Destroy a reactor.
 
bool IsValid () const
 If this is a valid reactor. Invalid reactors must be discarded immediately.
 
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 the other workers in the reactor or in the same sharegroup.
 
bool RemoveWorker (WorkerID id)
 Remove a previously added worker from the reactor. If the reactor has no workers, pending added operations will never run.
 
const ProcTableGLESGetProcTable () const
 Get the OpenGL proc. table the reactor uses to manage handles.
 
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 call within a reaction. That is, within a ReactorGLES::Operation.
 
std::optional< GLsync > GetGLFence (const HandleGLES &handle) const
 
HandleGLES CreateHandle (HandleType type, GLuint external_handle=GL_NONE)
 Create a reactor handle.
 
HandleGLES CreateUntrackedHandle (HandleType type) const
 Create a handle that is not managed by ReactorGLES.
 
void CollectHandle (HandleGLES handle)
 Collect a reactor handle.
 
void SetDebugLabel (const HandleGLES &handle, std::string_view label)
 Set the debug label on a reactor handle.
 
bool CanSetDebugLabels () const
 Whether the device is capable of writing debug labels.
 
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 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 destroyed.
 
bool React ()
 Perform a reaction on the current thread if able.
 

Detailed Description

The reactor attempts to make thread-safe usage of OpenGL ES easier to reason about.

In the other Impeller backends (like Metal and Vulkan), resources can be created, used, and deleted on any thread with relatively few restrictions. However, OpenGL resources can only be created, used, and deleted on a thread on which an OpenGL context (or one in the same sharegroup) is current.

There aren't too many OpenGL contexts to go around and making the caller reason about the timing and threading requirement only when the OpenGL backend is in use is tedious. To work around this tedium, there is an abstraction between the resources and their handles in OpenGL. The reactor is this abstraction.

The reactor is thread-safe and can created, used, and collected on any thread.

Reactor handles HandleGLES can be created, used, and collected on any thread. These handles can be to textures, buffers, etc..

Operations added to the reactor are guaranteed to run on a worker within a finite amount of time unless the reactor itself is torn down or there are no workers. These operations may run on the calling thread immediately if a worker is active on the current thread and can perform reactions. The operations are guaranteed to run with an OpenGL context current and all reactor handles having live OpenGL handle counterparts.

Creating a handle in the reactor doesn't mean an OpenGL handle is created immediately. OpenGL handles become live before the next reaction. Similarly, dropping the last reference to a reactor handle means that the OpenGL handle will be deleted at some point in the near future.

Definition at line 57 of file reactor_gles.h.

Member Typedef Documentation

◆ Operation

using impeller::ReactorGLES::Operation = std::function<void(const ReactorGLES& reactor)>

Definition at line 214 of file reactor_gles.h.

◆ WorkerID

Definition at line 59 of file reactor_gles.h.

Constructor & Destructor Documentation

◆ ReactorGLES()

impeller::ReactorGLES::ReactorGLES ( std::unique_ptr< ProcTableGLES gl)
explicit

Create a new reactor. There are expensive and only one per application instance is necessary.

Parameters
[in]glThe proc table for GL access. This is necessary for the reactor to be able to create and collect OpenGL handles.

Definition at line 73 of file reactor_gles.cc.

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}
#define VALIDATION_LOG
Definition validation.h:91

References VALIDATION_LOG.

◆ ~ReactorGLES()

impeller::ReactorGLES::~ReactorGLES ( )

Destroy a reactor.

Definition at line 83 of file reactor_gles.cc.

83 {
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}

Member Function Documentation

◆ AddOperation()

bool impeller::ReactorGLES::AddOperation ( Operation  operation,
bool  defer = false 
)

Adds an operation that the reactor runs on a worker that ensures that an OpenGL context is current.

This operation is not guaranteed to run immediately. It will complete in a finite amount of time on any thread as long as there is a reactor worker and the reactor itself is not being torn down.

Parameters
[in]operationThe operation
[in]deferIf false, the reactor attempts to React after adding this operation.
Returns
If the operation was successfully queued for completion.

Definition at line 173 of file reactor_gles.cc.

173 {
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}
bool React()
Perform a reaction on the current thread if able.

References React().

◆ AddWorker()

ReactorGLES::WorkerID impeller::ReactorGLES::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 the other workers in the reactor or in the same sharegroup.

Parameters
[in]workerThe worker
Returns
The worker identifier. This identifier can be used to remove the worker from the reactor later.

Definition at line 103 of file reactor_gles.cc.

103 {
104 Lock lock(workers_mutex_);
105 auto id = WorkerID{};
106 workers_[id] = std::move(worker);
107 return id;
108}
const uintptr_t id

References id.

◆ CanSetDebugLabels()

bool impeller::ReactorGLES::CanSetDebugLabels ( ) const

Whether the device is capable of writing debug labels.

        This function is useful for short circuiting expensive debug
        labeling. 

Definition at line 99 of file reactor_gles.cc.

99 {
100 return can_set_debug_labels_;
101}

◆ CollectHandle()

void impeller::ReactorGLES::CollectHandle ( HandleGLES  handle)

Collect a reactor handle.

        This can be called on any thread. Even one that doesn't have
        an OpenGL context.
Parameters
[in]handleThe reactor handle handle

Definition at line 235 of file reactor_gles.cc.

235 {
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}

References impeller::HandleGLES::IsDead().

◆ CreateHandle()

HandleGLES impeller::ReactorGLES::CreateHandle ( HandleType  type,
GLuint  external_handle = GL_NONE 
)

Create a reactor handle.

        This can be called on any thread. Even one that doesn't have
        an OpenGL context.
Parameters
[in]typeThe type of handle to create.
[in]external_handleAn already created GL handle if one exists.
Returns
The reactor handle.

Definition at line 214 of file reactor_gles.cc.

214 {
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}
GLenum type
static HandleGLES DeadHandle()
Creates a dead handle.
Definition handle_gles.h:44
const ProcTableGLES & GetProcTable() const
Get the OpenGL proc. table the reactor uses to manage handles.

References impeller::HandleGLES::DeadHandle(), GetProcTable(), impeller::kUnknown, and type.

◆ CreateUntrackedHandle()

HandleGLES impeller::ReactorGLES::CreateUntrackedHandle ( HandleType  type) const

Create a handle that is not managed by ReactorGLES.

This behaves just like CreateHandle but it doesn't add the handle to ReactorGLES::handles_ and the creation is executed synchronously, so it must be called from a proper thread. The benefit of this is that it avoid synchronization and hash table lookups when creating/accessing the handle.

Parameters
typeThe type of handle to create.
Returns
The reactor handle.

Definition at line 203 of file reactor_gles.cc.

203 {
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}
#define FML_DCHECK(condition)
Definition logging.h:122

References FML_DCHECK, GetProcTable(), and type.

Referenced by impeller::EncodeCommandsInReactor().

◆ GetGLFence()

std::optional< GLsync > impeller::ReactorGLES::GetGLFence ( const HandleGLES handle) const

Definition at line 162 of file reactor_gles.cc.

162 {
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}

References impeller::HandleGLES::GetType(), and impeller::kFence.

◆ GetGLHandle()

std::optional< GLuint > impeller::ReactorGLES::GetGLHandle ( const HandleGLES handle) const

Returns the OpenGL handle for a reactor handle if one is available. This is typically only safe to call within a reaction. That is, within a ReactorGLES::Operation.

Asking for the OpenGL handle before the reactor has a chance to reactor will return std::nullopt.

This can be called on any thread but is typically useless outside of a reaction since the handle is useless outside of a reactor operation.

Parameters
[in]handleThe reactor handle.
Returns
The OpenGL handle if the reactor has had a chance to react. std::nullopt otherwise.

Definition at line 151 of file reactor_gles.cc.

151 {
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}

References impeller::HandleGLES::GetType(), and impeller::kFence.

Referenced by impeller::EncodeCommandsInReactor(), and impeller::LinkProgram().

◆ GetProcTable()

const ProcTableGLES & impeller::ReactorGLES::GetProcTable ( ) const

Get the OpenGL proc. table the reactor uses to manage handles.

Returns
The proc table.

Definition at line 122 of file reactor_gles.cc.

122 {
124 return *proc_table_;
125}
bool IsValid() const
If this is a valid reactor. Invalid reactors must be discarded immediately.

References FML_DCHECK, and IsValid().

Referenced by CreateHandle(), CreateUntrackedHandle(), impeller::BlitCopyBufferToTextureCommandGLES::Encode(), impeller::BlitCopyTextureToTextureCommandGLES::Encode(), impeller::BlitCopyTextureToBufferCommandGLES::Encode(), impeller::BlitResizeTextureCommandGLES::Encode(), impeller::EncodeCommandsInReactor(), impeller::EncodeCommandsInReactor(), impeller::LinkProgram(), and SetDebugLabel().

◆ IsValid()

bool impeller::ReactorGLES::IsValid ( ) const

If this is a valid reactor. Invalid reactors must be discarded immediately.

Returns
If this reactor is valid.

Definition at line 95 of file reactor_gles.cc.

95 {
96 return is_valid_;
97}

Referenced by GetProcTable().

◆ React()

bool impeller::ReactorGLES::React ( )

Perform a reaction on the current thread if able.

        It is safe to call this simultaneously from multiple threads
        at the same time.
Returns
If a reaction was performed on the calling thread.

Definition at line 255 of file reactor_gles.cc.

255 {
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}
#define TRACE_EVENT0(category_group, name)

References TRACE_EVENT0.

Referenced by AddOperation(), and impeller::testing::TEST().

◆ RegisterCleanupCallback()

bool impeller::ReactorGLES::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 destroyed.

This operation is not guaranteed to run immediately. It will complete in a finite amount of time on any thread as long as there is a reactor worker and the reactor itself is not being torn down.

Parameters
[in]handleThe handle to attach the cleanup to.
[in]callbackThe cleanup callback to execute.
Returns
If the operation was successfully queued for completion.

Definition at line 189 of file reactor_gles.cc.

190 {
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}
Wraps a closure that is invoked in the destructor unless released by the caller.
Definition closure.h:32
FlutterDesktopBinaryReply callback

References callback, FML_DCHECK, and impeller::HandleGLES::IsDead().

◆ RemoveWorker()

bool impeller::ReactorGLES::RemoveWorker ( WorkerID  id)

Remove a previously added worker from the reactor. If the reactor has no workers, pending added operations will never run.

Parameters
[in]idThe worker identifier previously returned by AddWorker.
Returns
If a worker with the given identifer was successfully removed from the reactor.

Definition at line 110 of file reactor_gles.cc.

110 {
111 Lock lock(workers_mutex_);
112 return workers_.erase(worker) == 1;
113}

◆ SetDebugLabel()

void impeller::ReactorGLES::SetDebugLabel ( const HandleGLES handle,
std::string_view  label 
)

Set the debug label on a reactor handle.

        This call ensures that the OpenGL debug label is propagated to
        even the OpenGL handle hasn't been created at the time the
        caller sets the label.
Parameters
[in]handleThe handle
[in]labelThe label

Definition at line 390 of file reactor_gles.cc.

391 {
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}
static DebugResourceType ToDebugResourceType(HandleType type)

References FML_DCHECK, GetProcTable(), impeller::HandleGLES::GetType(), impeller::HandleGLES::IsDead(), impeller::kFence, and impeller::ToDebugResourceType().


The documentation for this class was generated from the following files: