blob: eff7c349819d9b75291e0514af9187045fa671d7 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef FLUTTER_IMPELLER_RENDERER_BACKEND_GLES_REACTOR_GLES_H_
#define FLUTTER_IMPELLER_RENDERER_BACKEND_GLES_REACTOR_GLES_H_
#include <functional>
#include <memory>
#include <vector>
#include "flutter/fml/macros.h"
#include "impeller/base/thread.h"
#include "impeller/renderer/backend/gles/handle_gles.h"
#include "impeller/renderer/backend/gles/proc_table_gles.h"
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);
//----------------------------------------------------------------------------
/// @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:
struct LiveHandle {
std::optional<GLuint> name;
std::optional<std::string> pending_debug_label;
bool pending_collection = false;
LiveHandle() = default;
explicit LiveHandle(std::optional<GLuint> p_name) : name(p_name) {}
constexpr bool IsLive() const { return name.has_value(); }
};
std::unique_ptr<ProcTableGLES> proc_table_;
Mutex ops_execution_mutex_;
mutable Mutex ops_mutex_;
std::vector<Operation> ops_ IPLR_GUARDED_BY(ops_mutex_);
// Make sure the container is one where erasing items during iteration doesn't
// invalidate other iterators.
using LiveHandles = std::unordered_map<HandleGLES,
LiveHandle,
HandleGLES::Hash,
HandleGLES::Equal>;
mutable RWMutex handles_mutex_;
LiveHandles handles_ IPLR_GUARDED_BY(handles_mutex_);
mutable Mutex workers_mutex_;
mutable std::map<WorkerID, std::weak_ptr<Worker>> workers_
IPLR_GUARDED_BY(workers_mutex_);
bool can_set_debug_labels_ = false;
bool is_valid_ = false;
bool ReactOnce() IPLR_REQUIRES(ops_execution_mutex_);
bool HasPendingOperations() const;
bool CanReactOnCurrentThread() const;
bool ConsolidateHandles();
bool FlushOps();
void SetupDebugGroups();
ReactorGLES(const ReactorGLES&) = delete;
ReactorGLES& operator=(const ReactorGLES&) = delete;
};
} // namespace impeller
#endif // FLUTTER_IMPELLER_RENDERER_BACKEND_GLES_REACTOR_GLES_H_