476c7efc18
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
493 lines
18 KiB
Objective-C
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_
|