mediapipe/mediapipe/gpu/gl_context.h
Camillo Lugaresi a8b7761022 Define a kUtilityFramebuffer context attachment
A framebuffer object is often needed to render to a texture or read data from it. Currently we create one in each GlCalculatorHelper, but that is redundant (we only need one per context, and multiple calculators can share the same context). Other times, the code that needs to use this doesn't own a helper. For both reasons, this should be attached to the context.

We could just make this a member of GlContext since it's so common. However, I figured we might as well use the attachment system.

PiperOrigin-RevId: 490160214
2022-11-21 23:29:27 -08:00

493 lines
18 KiB
Objective-C

// Copyright 2019 The MediaPipe Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MEDIAPIPE_GPU_GL_CONTEXT_H_
#define MEDIAPIPE_GPU_GL_CONTEXT_H_
#include <pthread.h>
#include <atomic>
#include <functional>
#include <memory>
#include "absl/container/flat_hash_map.h"
#include "absl/synchronization/mutex.h"
#include "mediapipe/framework/executor.h"
#include "mediapipe/framework/mediapipe_profiling.h"
#include "mediapipe/framework/port/status.h"
#include "mediapipe/framework/port/statusor.h"
#include "mediapipe/framework/port/threadpool.h"
#include "mediapipe/framework/timestamp.h"
#include "mediapipe/gpu/attachments.h"
#include "mediapipe/gpu/gl_base.h"
#include "mediapipe/gpu/gpu_buffer_format.h"
#ifdef __APPLE__
#include <CoreVideo/CoreVideo.h>
#include "mediapipe/objc/CFHolder.h"
#if TARGET_OS_OSX
#ifdef __OBJC__
@class NSOpenGLContext;
@class NSOpenGLPixelFormat;
#else
struct NSOpenGLContext;
struct NSOpenGLPixelFormat;
#endif // __OBJC___
#else
#ifdef __OBJC__
@class EAGLSharegroup;
@class EAGLContext;
#else
struct EAGLSharegroup;
struct EAGLContext;
#endif // __OBJC___
#endif // TARGET_OS_OSX
#else
#endif // __APPLE__
namespace mediapipe {
typedef std::function<void()> GlVoidFunction;
typedef std::function<absl::Status()> GlStatusFunction;
class GlContext;
// Generic interface for synchronizing access to a shared resource from a
// different context. This is an abstract class to keep users from
// depending on its contents. The implementation may differ depending on
// the capabilities of the GL context.
class GlSyncPoint {
public:
explicit GlSyncPoint(const std::shared_ptr<GlContext>& gl_context)
: gl_context_(gl_context) {}
virtual ~GlSyncPoint() {}
// Waits until the GPU has executed all commands up to the sync point.
// This blocks the CPU, and ensures the commands are complete from the
// point of view of all threads and contexts.
virtual void Wait() = 0;
// Ensures that the following commands on the current OpenGL context will
// not be executed until the sync point has been reached.
// This does not block the CPU, and only affects the current OpenGL context.
virtual void WaitOnGpu() { Wait(); }
// Returns whether the sync point has been reached. Does not block.
virtual bool IsReady() = 0;
// Returns the GlContext object associated with this sync point, if any.
const std::shared_ptr<GlContext>& GetContext() { return gl_context_; }
protected:
std::shared_ptr<GlContext> gl_context_;
};
// Combines sync points for multiple contexts.
class GlMultiSyncPoint : public GlSyncPoint {
public:
GlMultiSyncPoint() : GlSyncPoint(nullptr) {}
// Adds a new sync to the multisync.
// If we already have a sync from the same context, overwrite it.
// Commands on the same context are serialized, and we only care about
// when the last one is done.
void Add(std::shared_ptr<GlSyncPoint> new_sync);
void Wait() override;
void WaitOnGpu() override;
bool IsReady() override;
private:
std::vector<std::shared_ptr<GlSyncPoint>> syncs_;
};
// TODO: remove.
typedef std::shared_ptr<GlSyncPoint> GlSyncToken;
#if defined(__EMSCRIPTEN__)
typedef EMSCRIPTEN_WEBGL_CONTEXT_HANDLE PlatformGlContext;
constexpr PlatformGlContext kPlatformGlContextNone = 0;
#elif HAS_EGL
typedef EGLContext PlatformGlContext;
constexpr PlatformGlContext kPlatformGlContextNone = EGL_NO_CONTEXT;
#elif HAS_EAGL
typedef EAGLContext* PlatformGlContext;
constexpr PlatformGlContext kPlatformGlContextNone = nil;
#elif HAS_NSGL
typedef NSOpenGLContext* PlatformGlContext;
constexpr PlatformGlContext kPlatformGlContextNone = nil;
#endif // defined(__EMSCRIPTEN__)
// This class provides a common API for creating and managing GL contexts.
//
// It handles the following responsibilities:
// - Providing a cross-platform interface over platform-specific APIs like EGL
// and EAGL.
// - Managing the interaction between threads and GL contexts.
// - Managing synchronization between different GL contexts.
//
class GlContext : public std::enable_shared_from_this<GlContext> {
public:
using StatusOrGlContext = absl::StatusOr<std::shared_ptr<GlContext>>;
// Creates a GlContext.
//
// The first argument (which can be a GlContext, or a platform-specific type)
// indicates a context with which to share resources (e.g. textures).
// Resources will be shared amongst all contexts linked in this way. You can
// pass null if sharing is not desired.
//
// If create_thread is true, the context will create a thread and run all
// OpenGL tasks on it.
static StatusOrGlContext Create(std::nullptr_t nullp, bool create_thread);
static StatusOrGlContext Create(const GlContext& share_context,
bool create_thread);
static StatusOrGlContext Create(PlatformGlContext share_context,
bool create_thread);
#if HAS_EAGL
static StatusOrGlContext Create(EAGLSharegroup* sharegroup,
bool create_thread);
#endif // HAS_EAGL
// Returns the GlContext that is current on this thread. May return nullptr.
static std::shared_ptr<GlContext> GetCurrent();
GlContext(const GlContext&) = delete;
GlContext& operator=(const GlContext&) = delete;
~GlContext();
// Initializes this GlContext with the graph tracing and profiling interface.
// Also initializes the GlProfilingHelper object for this GlContext if the
// GlProfilingHelper is uninitialized. This ensures that the GlProfilingHelper
// is unique to and only initialized once per GlContext object.
void SetProfilingContext(
std::shared_ptr<mediapipe::ProfilingContext> profiling_context);
// Executes a function in the GL context. Waits for the
// function's execution to be complete before returning to the caller.
absl::Status Run(GlStatusFunction gl_func, int node_id = -1,
Timestamp input_timestamp = Timestamp::Unset());
// Like Run, but does not wait.
void RunWithoutWaiting(GlVoidFunction gl_func);
// Returns a synchronization token.
// This should not be called outside of the GlContext thread.
std::shared_ptr<GlSyncPoint> CreateSyncToken();
// If another part of the framework calls glFinish, it should call this
// method to let the context know that it has done so. The context can use
// that information to avoid inserting additional glFinish calls in some
// cases.
void GlFinishCalled();
// Ensures that the changes to shared resources covered by the token are
// visible in the current context.
// This should only be called outside a job.
void WaitSyncToken(const std::shared_ptr<GlSyncPoint>& token);
// Checks whether the token's sync point has been reached. Returns true
// iff WaitSyncToken would not have to wait.
// This is thread-safe.
bool SyncTokenIsReady(const std::shared_ptr<GlSyncPoint>& token);
#if defined(__EMSCRIPTEN__)
// Returns the EMSCRIPTEN_WEBGL_CONTEXT_HANDLE for our context.
EMSCRIPTEN_WEBGL_CONTEXT_HANDLE webgl_context() const { return context_; }
EmscriptenWebGLContextAttributes webgl_attributes() const { return attrs_; }
#elif HAS_EGL
// Returns the EGLDisplay used by our context.
EGLDisplay egl_display() const { return display_; }
// Returns the EGLConfig used to create our context.
EGLConfig egl_config() const { return config_; }
// Returns our EGLContext.
EGLContext egl_context() const { return context_; }
#elif HAS_EAGL
EAGLContext* eagl_context() const { return context_; }
CVOpenGLESTextureCacheRef cv_texture_cache() const { return *texture_cache_; }
#elif HAS_NSGL
NSOpenGLContext* nsgl_context() const { return context_; }
NSOpenGLPixelFormat* nsgl_pixel_format() const { return pixel_format_; }
CVOpenGLTextureCacheRef cv_texture_cache() const { return *texture_cache_; }
#endif // HAS_EGL
// Returns whatever the current platform's native context handle is.
// Prefer the explicit *_context methods above, unless you're going to use
// this in a context that you are sure will work with whatever definition of
// PlatformGlContext is in use.
PlatformGlContext native_context() const { return context_; }
// Check if the context is current on this thread. Mainly for test purposes.
bool IsCurrent() const;
GLint gl_major_version() const { return gl_major_version_; }
GLint gl_minor_version() const { return gl_minor_version_; }
static bool ParseGlVersion(absl::string_view version_string, GLint* major,
GLint* minor);
// Returns a GlVersion code used with GpuBufferFormat.
// TODO: make this more generally applicable.
GlVersion GetGlVersion() const;
// Simple query for GL extension support; only valid after GlContext has
// finished its initialization successfully.
bool HasGlExtension(absl::string_view extension) const;
int64_t gl_finish_count() { return gl_finish_count_; }
// Used by GlFinishSyncPoint. The count_to_pass cannot exceed the current
// gl_finish_count_ (but it can be equal).
void WaitForGlFinishCountPast(int64_t count_to_pass);
// Convenience version of Run for arguments with a void result type.
// Waits for the function to finish executing before returning.
//
// Implementation note: we cannot use a std::function<void(void)> argument
// here, because that would break passing in a lambda that returns a status;
// e.g.:
// RunInGlContext([]() -> absl::Status { ... });
//
// The reason is that std::function<void(...)> allows the implicit conversion
// of a callable with any result type, as long as the argument types match.
// As a result, the above lambda would be implicitly convertible to both
// std::function<absl::Status(void)> and std::function<void(void)>, and
// the invocation would be ambiguous.
//
// Therefore, instead of using std::function<void(void)>, we use a template
// that only accepts arguments with a void result type.
template <typename T, typename = typename std::enable_if<std::is_void<
typename std::result_of<T()>::type>::value>::type>
void Run(T f) {
Run([f] {
f();
return absl::OkStatus();
}).IgnoreError();
}
// Sets default texture filtering parameters.
void SetStandardTextureParams(GLenum target, GLint internal_format);
using AttachmentBase = internal::AttachmentBase<GlContext>;
template <class T>
using Attachment = internal::Attachment<GlContext, T>;
// TOOD: const result?
template <class T>
T& GetCachedAttachment(const Attachment<T>& attachment) {
DCHECK(IsCurrent());
internal::AttachmentPtr<void>& entry = attachments_[&attachment];
if (entry == nullptr) {
entry = attachment.factory()(*this);
}
return *static_cast<T*>(entry.get());
}
// Returns true if any GL context, including external contexts not managed by
// the GlContext class, is current.
static bool IsAnyContextCurrent();
// Returns the current native context, whether managed by this class or not.
// Useful as a cross-platform way to get the current PlatformGlContext.
static PlatformGlContext GetCurrentNativeContext();
// Creates a synchronization token for the current, non-GlContext-owned
// context. This can be passed to MediaPipe so it can synchronize with the
// commands issued in the external context up to this point.
// Note: if the current context does not support sync fences, this calls
// glFinish and returns nullptr.
// TODO: return GlNopSyncPoint instead?
static std::shared_ptr<GlSyncPoint> CreateSyncTokenForCurrentExternalContext(
const std::shared_ptr<GlContext>& delegate_graph_context);
// These are used for testing specific SyncToken implementations. Do not use
// outside of tests.
enum class SyncTokenTypeForTest {
kGlFinish,
};
std::shared_ptr<GlSyncPoint> TestOnly_CreateSpecificSyncToken(
SyncTokenTypeForTest type);
private:
GlContext();
bool ShouldUseFenceSync() const;
#if defined(__EMSCRIPTEN__)
absl::Status CreateContext(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE share_context);
absl::Status CreateContextInternal(
EMSCRIPTEN_WEBGL_CONTEXT_HANDLE share_context, int webgl_version);
EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context_ = 0;
EmscriptenWebGLContextAttributes attrs_;
#elif HAS_EGL
absl::Status CreateContext(EGLContext share_context);
absl::Status CreateContextInternal(EGLContext share_context, int gl_version);
EGLDisplay display_ = EGL_NO_DISPLAY;
EGLConfig config_;
EGLSurface surface_ = EGL_NO_SURFACE;
EGLContext context_ = EGL_NO_CONTEXT;
#elif HAS_EAGL
absl::Status CreateContext(EAGLSharegroup* sharegroup);
EAGLContext* context_;
CFHolder<CVOpenGLESTextureCacheRef> texture_cache_;
#elif HAS_NSGL
absl::Status CreateContext(NSOpenGLContext* share_context);
NSOpenGLContext* context_;
NSOpenGLPixelFormat* pixel_format_;
CFHolder<CVOpenGLTextureCacheRef> texture_cache_;
#endif // defined(__EMSCRIPTEN__)
class DedicatedThread;
// A context binding represents the minimal set of information needed to make
// a context current on a thread. Its contents depend on the platform.
struct ContextBinding {
// The context_object is null if this binding refers to a context not
// managed by GlContext.
std::weak_ptr<GlContext> context_object;
#if defined(__EMSCRIPTEN__)
EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context = 0;
#elif HAS_EGL
EGLDisplay display = EGL_NO_DISPLAY;
EGLSurface draw_surface = EGL_NO_SURFACE;
EGLSurface read_surface = EGL_NO_SURFACE;
EGLContext context = EGL_NO_CONTEXT;
#elif HAS_EAGL
EAGLContext* context = nullptr;
#elif HAS_NSGL
NSOpenGLContext* context = nullptr;
#endif // HAS_EGL
};
absl::Status FinishInitialization(bool create_thread);
// This wraps a thread_local.
static std::weak_ptr<GlContext>& CurrentContext();
static absl::Status SwitchContext(ContextBinding* saved_context,
const ContextBinding& new_context);
absl::Status EnterContext(ContextBinding* saved_context);
absl::Status ExitContext(const ContextBinding* saved_context);
void DestroyContext();
bool HasContext() const;
// This function clears out any tripped gl Errors and just logs them. This
// is used by code that needs to check glGetError() to know if it succeeded,
// but can't rely on the existing state to be 'clean'.
void ForceClearExistingGlErrors();
// Returns true if there were any GL errors. Note that this may be a no-op
// for performance reasons in some contexts (specifically Emscripten opt).
bool CheckForGlErrors();
// Same as `CheckForGLErrors()` but with the option of forcing the check
// even if we would otherwise skip for performance reasons.
bool CheckForGlErrors(bool force);
void LogUncheckedGlErrors(bool had_gl_errors);
absl::Status GetGlExtensions();
absl::Status GetGlExtensionsCompat();
// Make the context current, run gl_func, and restore the previous context.
// Internal helper only; callers should use Run or RunWithoutWaiting instead,
// which delegates to the dedicated thread if required.
absl::Status SwitchContextAndRun(GlStatusFunction gl_func);
// The following ContextBinding functions have platform-specific
// implementations.
// A binding that can be used to make this GlContext current.
ContextBinding ThisContextBinding();
// Fill in platform-specific fields. Must _not_ set context_obj.
ContextBinding ThisContextBindingPlatform();
// Fills in a ContextBinding with platform-specific information about which
// context is current on this thread.
static void GetCurrentContextBinding(ContextBinding* binding);
// Makes the context described by new_context current on this thread.
static absl::Status SetCurrentContextBinding(
const ContextBinding& new_binding);
// If not null, a dedicated thread used to execute tasks on this context.
// Used on Android due to expensive context switching on some configurations.
std::unique_ptr<DedicatedThread> thread_;
GLint gl_major_version_ = 0;
GLint gl_minor_version_ = 0;
// glGetString and glGetStringi both return pointers to static strings,
// so we should be fine storing the extension pieces as string_view's.
std::set<absl::string_view> gl_extensions_;
// Used by SetStandardTextureParams. Do we want several of these bools, or a
// better mechanism?
bool can_linear_filter_float_textures_;
absl::flat_hash_map<const AttachmentBase*, internal::AttachmentPtr<void>>
attachments_;
// Number of glFinish calls completed on the GL thread.
// Changes should be guarded by mutex_. However, we use simple atomic
// loads for efficiency on the fast path.
std::atomic<int64_t> gl_finish_count_ = ATOMIC_VAR_INIT(0);
std::atomic<int64_t> gl_finish_count_target_ = ATOMIC_VAR_INIT(0);
GlContext* context_waiting_on_ ABSL_GUARDED_BY(mutex_) = nullptr;
// This mutex is held by a thread while this GL context is current on that
// thread. Since it may be held for extended periods of time, it should not
// be used for other pieces of status.
absl::Mutex context_use_mutex_;
// This mutex is used to guard a few different members and condition
// variables. It should only be held for a short time.
absl::Mutex mutex_;
absl::CondVar wait_for_gl_finish_cv_ ABSL_GUARDED_BY(mutex_);
std::unique_ptr<mediapipe::GlProfilingHelper> profiling_helper_ = nullptr;
bool destructing_ = false;
};
// A framebuffer that the framework can use to attach textures for rendering
// etc.
// This could just be a member of GlContext, but it serves as a basic example
// of an attachment.
ABSL_CONST_INIT extern const GlContext::Attachment<GLuint> kUtilityFramebuffer;
// For backward compatibility. TODO: migrate remaining callers.
ABSL_DEPRECATED(
"Prefer passing an explicit GlVersion argument (use "
"GlContext::GetGlVersion)")
const GlTextureInfo& GlTextureInfoForGpuBufferFormat(GpuBufferFormat format,
int plane);
} // namespace mediapipe
#endif // MEDIAPIPE_GPU_GL_CONTEXT_H_