Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 166 additions & 2 deletions impeller/renderer/backend/gles/reactor_gles.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,43 +15,207 @@

namespace impeller {

//------------------------------------------------------------------------------
/// @brief 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.
///
class ReactorGLES {
public:
using WorkerID = UniqueID;

//----------------------------------------------------------------------------
/// @brief 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.
///
class Worker {
public:
virtual ~Worker() = default;

//--------------------------------------------------------------------------
/// @brief Determines the ability of the worker to service a reaction
/// on the current thread. The OpenGL context must be current on
/// the thread if the worker says it is able to service a
/// reaction.
///
/// @param[in] reactor The reactor
///
/// @return If the worker is able to service a reaction. The reactor
/// assumes the context is already current if true.
///
virtual bool CanReactorReactOnCurrentThreadNow(
const ReactorGLES& reactor) const = 0;
};

using Ref = std::shared_ptr<ReactorGLES>;

//----------------------------------------------------------------------------
/// @brief Create a new reactor. There are expensive and only one per
/// application instance is necessary.
///
/// @param[in] gl The proc table for GL access. This is necessary for the
/// reactor to be able to create and collect OpenGL handles.
///
explicit ReactorGLES(std::unique_ptr<ProcTableGLES> gl);

//----------------------------------------------------------------------------
/// @brief Destroy a reactor.
///
~ReactorGLES();

//----------------------------------------------------------------------------
/// @brief If this is a valid reactor. Invalid reactors must be discarded
/// immediately.
///
/// @return If this reactor is valid.
///
bool IsValid() const;

//----------------------------------------------------------------------------
/// @brief 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.
///
/// @param[in] worker The worker
///
/// @return The worker identifier. This identifier can be used to remove
/// the worker from the reactor later.
///
WorkerID AddWorker(std::weak_ptr<Worker> worker);

bool RemoveWorker(WorkerID);

//----------------------------------------------------------------------------
/// @brief Remove a previously added worker from the reactor. If the
/// reactor has no workers, pending added operations will never
/// run.
///
/// @param[in] id The worker identifier previously returned by `AddWorker`.
///
/// @return If a worker with the given identifer was successfully removed
/// from the reactor.
///
bool RemoveWorker(WorkerID id);

//----------------------------------------------------------------------------
/// @brief Get the OpenGL proc. table the reactor uses to manage handles.
///
/// @return The proc table.
///
const ProcTableGLES& GetProcTable() const;

//----------------------------------------------------------------------------
/// @brief 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.
///
/// @param[in] handle The reactor handle.
///
/// @return The OpenGL handle if the reactor has had a chance to react.
/// `std::nullopt` otherwise.
///
std::optional<GLuint> GetGLHandle(const HandleGLES& handle) const;

//----------------------------------------------------------------------------
/// @brief Create a reactor handle.
///
/// This can be called on any thread. Even one that doesn't have
/// an OpenGL context.
///
/// @param[in] type The type of handle to create.
///
/// @return The reactor handle.
///
HandleGLES CreateHandle(HandleType type);

//----------------------------------------------------------------------------
/// @brief Collect a reactor handle.
///
/// This can be called on any thread. Even one that doesn't have
/// an OpenGL context.
///
/// @param[in] handle The reactor handle handle
///
void CollectHandle(HandleGLES handle);

//----------------------------------------------------------------------------
/// @brief 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.
///
/// @param[in] handle The handle
/// @param[in] label The label
///
void SetDebugLabel(const HandleGLES& handle, std::string label);

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

//----------------------------------------------------------------------------
/// @brief 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.
///
/// @param[in] operation The operation
///
/// @return If the operation was successfully queued for completion.
///
[[nodiscard]] bool AddOperation(Operation operation);

//----------------------------------------------------------------------------
/// @brief Perform a reaction on the current thread if able.
///
/// It is safe to call this simultaneously from multiple threads
/// at the same time.
///
/// @return If a reaction was performed on the calling thread.
///
[[nodiscard]] bool React();

private:
Expand Down