From 9db4265e15497ad9925b857037f577a942342edc Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Wed, 18 Oct 2023 13:22:37 -0700 Subject: [PATCH 1/2] [Impeller] Document ReactorGLES. --- impeller/renderer/backend/gles/reactor_gles.h | 168 +++++++++++++++++- 1 file changed, 166 insertions(+), 2 deletions(-) diff --git a/impeller/renderer/backend/gles/reactor_gles.h b/impeller/renderer/backend/gles/reactor_gles.h index 801542b4fb27a..15dca231608dd 100644 --- a/impeller/renderer/backend/gles/reactor_gles.h +++ b/impeller/renderer/backend/gles/reactor_gles.h @@ -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 consumed on any thread with +/// relatively few restrictions. However, OpenGL resources can only +/// be created, used, and consumed 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; + //---------------------------------------------------------------------------- + /// @brief Create a new reactor. There are expensive and only 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 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); - 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 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 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; + + //---------------------------------------------------------------------------- + /// @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: From c61376882e2ff7adbbf3c6ba13e6ef9bdccc27d4 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Wed, 18 Oct 2023 14:36:39 -0700 Subject: [PATCH 2/2] PR comments. --- impeller/renderer/backend/gles/reactor_gles.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/impeller/renderer/backend/gles/reactor_gles.h b/impeller/renderer/backend/gles/reactor_gles.h index 15dca231608dd..3715e47a14fd8 100644 --- a/impeller/renderer/backend/gles/reactor_gles.h +++ b/impeller/renderer/backend/gles/reactor_gles.h @@ -20,10 +20,10 @@ namespace impeller { /// easier to reason about. /// /// In the other Impeller backends (like Metal and Vulkan), -/// resources can be created, used, and consumed on any thread with +/// resources can be created, used, and deleted on any thread with /// relatively few restrictions. However, OpenGL resources can only -/// be created, used, and consumed on a thread on which an OpenGL -/// context (or one in the same sharegroup) is current. +/// 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 @@ -86,7 +86,7 @@ class ReactorGLES { using Ref = std::shared_ptr; //---------------------------------------------------------------------------- - /// @brief Create a new reactor. There are expensive and only per + /// @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 @@ -139,7 +139,7 @@ class ReactorGLES { const ProcTableGLES& GetProcTable() const; //---------------------------------------------------------------------------- - /// @brief Returns the OpenGL handle for reactor handle if one is + /// @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`. ///