mediapipe/mediapipe/gpu/gl_context.h
MediaPipe Team 476c7efc18 Remove uses of ATOMIC_VAR_INIT
ATOMIC_VAR_INIT has a trivial definition
`#define ATOMIC_VAR_INIT(value) (value)`,
is deprecated in C17/C++20, and will be removed in newer standards in
newer GCC/Clang (e.g. https://reviews.llvm.org/D144196).

PiperOrigin-RevId: 525534393
2023-04-19 13:26:47 -07: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_ = 0;
std::atomic<int64_t> gl_finish_count_target_ = 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_