33d683c671
GitOrigin-RevId: 373e3ac1e5839befd95bf7d73ceff3c5f1171969
880 lines
29 KiB
C++
880 lines
29 KiB
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.
|
|
|
|
#include "mediapipe/gpu/gl_context.h"
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <cmath>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <utility>
|
|
|
|
#include "absl/base/dynamic_annotations.h"
|
|
#include "absl/memory/memory.h"
|
|
#include "absl/synchronization/mutex.h"
|
|
#include "mediapipe/framework/port/logging.h"
|
|
#include "mediapipe/framework/port/ret_check.h"
|
|
#include "mediapipe/framework/port/status.h"
|
|
#include "mediapipe/framework/port/status_builder.h"
|
|
#include "mediapipe/gpu/gl_context_internal.h"
|
|
#include "mediapipe/gpu/gpu_buffer_format.h"
|
|
|
|
#ifndef __EMSCRIPTEN__
|
|
#include "absl/debugging/leak_check.h"
|
|
#include "mediapipe/gpu/gl_thread_collector.h"
|
|
#endif
|
|
|
|
#ifndef GL_MAJOR_VERSION
|
|
#define GL_MAJOR_VERSION 0x821B
|
|
#endif
|
|
|
|
#ifndef GL_MINOR_VERSION
|
|
#define GL_MINOR_VERSION 0x821C
|
|
#endif
|
|
|
|
namespace mediapipe {
|
|
|
|
static void SetThreadName(const char* name) {
|
|
#if defined(__GLIBC_PREREQ)
|
|
#define LINUX_STYLE_SETNAME_NP __GLIBC_PREREQ(2, 12)
|
|
#elif defined(__BIONIC__)
|
|
#define LINUX_STYLE_SETNAME_NP 1
|
|
#endif // __GLIBC_PREREQ
|
|
#if LINUX_STYLE_SETNAME_NP
|
|
char thread_name[16]; // Linux requires names (with nul) fit in 16 chars
|
|
strncpy(thread_name, name, sizeof(thread_name));
|
|
thread_name[sizeof(thread_name) - 1] = '\0';
|
|
int res = pthread_setname_np(pthread_self(), thread_name);
|
|
if (res != 0) {
|
|
LOG_FIRST_N(INFO, 1) << "Can't set pthread names: name: \"" << name
|
|
<< "\"; error: " << res;
|
|
}
|
|
#elif __APPLE__
|
|
pthread_setname_np(name);
|
|
#endif
|
|
ANNOTATE_THREAD_NAME(name);
|
|
}
|
|
|
|
GlContext::DedicatedThread::DedicatedThread() {
|
|
CHECK_EQ(pthread_create(&gl_thread_id_, nullptr, ThreadBody, this), 0);
|
|
}
|
|
|
|
GlContext::DedicatedThread::~DedicatedThread() {
|
|
if (IsCurrentThread()) {
|
|
CHECK(self_destruct_);
|
|
CHECK_EQ(pthread_detach(gl_thread_id_), 0);
|
|
} else {
|
|
// Give an invalid job to signal termination.
|
|
PutJob({});
|
|
CHECK_EQ(pthread_join(gl_thread_id_, nullptr), 0);
|
|
}
|
|
}
|
|
|
|
void GlContext::DedicatedThread::SelfDestruct() {
|
|
self_destruct_ = true;
|
|
// Give an invalid job to signal termination.
|
|
PutJob({});
|
|
}
|
|
|
|
GlContext::DedicatedThread::Job GlContext::DedicatedThread::GetJob() {
|
|
absl::MutexLock lock(&mutex_);
|
|
while (jobs_.empty()) {
|
|
has_jobs_cv_.Wait(&mutex_);
|
|
}
|
|
Job job = std::move(jobs_.front());
|
|
jobs_.pop_front();
|
|
return job;
|
|
}
|
|
|
|
void GlContext::DedicatedThread::PutJob(Job job) {
|
|
absl::MutexLock lock(&mutex_);
|
|
jobs_.push_back(std::move(job));
|
|
has_jobs_cv_.SignalAll();
|
|
}
|
|
|
|
void* GlContext::DedicatedThread::ThreadBody(void* instance) {
|
|
DedicatedThread* thread = static_cast<DedicatedThread*>(instance);
|
|
thread->ThreadBody();
|
|
return nullptr;
|
|
}
|
|
|
|
#ifdef __APPLE__
|
|
#define AUTORELEASEPOOL @autoreleasepool
|
|
#else
|
|
#define AUTORELEASEPOOL
|
|
#endif // __APPLE__
|
|
|
|
void GlContext::DedicatedThread::ThreadBody() {
|
|
SetThreadName("mediapipe_gl_runner");
|
|
|
|
#ifndef __EMSCRIPTEN__
|
|
GlThreadCollector::ThreadStarting();
|
|
#endif
|
|
// The dedicated GL thread is not meant to be used on Apple platforms, but
|
|
// in case it is, the use of an autorelease pool here will reap each task's
|
|
// temporary allocations.
|
|
while (true) AUTORELEASEPOOL {
|
|
Job job = GetJob();
|
|
// Lack of a job means termination. Or vice versa.
|
|
if (!job) {
|
|
break;
|
|
}
|
|
job();
|
|
}
|
|
if (self_destruct_) {
|
|
delete this;
|
|
}
|
|
#ifndef __EMSCRIPTEN__
|
|
GlThreadCollector::ThreadEnding();
|
|
#endif
|
|
}
|
|
|
|
absl::Status GlContext::DedicatedThread::Run(GlStatusFunction gl_func) {
|
|
// Neither ENDO_SCOPE nor ENDO_TASK seem to work here.
|
|
if (IsCurrentThread()) {
|
|
return gl_func();
|
|
}
|
|
bool done = false; // Guarded by mutex_ after initialization.
|
|
absl::Status status;
|
|
PutJob([this, gl_func, &done, &status]() {
|
|
status = gl_func();
|
|
absl::MutexLock lock(&mutex_);
|
|
done = true;
|
|
gl_job_done_cv_.SignalAll();
|
|
});
|
|
|
|
absl::MutexLock lock(&mutex_);
|
|
while (!done) {
|
|
gl_job_done_cv_.Wait(&mutex_);
|
|
}
|
|
return status;
|
|
}
|
|
|
|
void GlContext::DedicatedThread::RunWithoutWaiting(GlVoidFunction gl_func) {
|
|
// Note: this is invoked by GlContextExecutor. To avoid starvation of
|
|
// non-calculator tasks in the presence of GL source calculators, calculator
|
|
// tasks must always be scheduled as new tasks, or another solution needs to
|
|
// be set up to avoid starvation. See b/78522434.
|
|
CHECK(gl_func);
|
|
PutJob(std::move(gl_func));
|
|
}
|
|
|
|
bool GlContext::DedicatedThread::IsCurrentThread() {
|
|
return pthread_equal(gl_thread_id_, pthread_self());
|
|
}
|
|
|
|
bool GlContext::ParseGlVersion(absl::string_view version_string, GLint* major,
|
|
GLint* minor) {
|
|
size_t pos = version_string.find('.');
|
|
if (pos == absl::string_view::npos || pos < 1) {
|
|
return false;
|
|
}
|
|
// GL_VERSION is supposed to start with the version number; see, e.g.,
|
|
// https://www.khronos.org/registry/OpenGL-Refpages/es3/html/glGetString.xhtml
|
|
// https://www.khronos.org/opengl/wiki/OpenGL_Context#OpenGL_version_number
|
|
// However, in rare cases one will encounter non-conforming configurations
|
|
// that have some prefix before the number. To deal with that, we walk
|
|
// backwards from the dot.
|
|
size_t start = pos - 1;
|
|
while (start > 0 && isdigit(version_string[start - 1])) --start;
|
|
if (!absl::SimpleAtoi(version_string.substr(start, (pos - start)), major)) {
|
|
return false;
|
|
}
|
|
auto rest = version_string.substr(pos + 1);
|
|
pos = rest.find(' ');
|
|
size_t pos2 = rest.find('.');
|
|
if (pos == absl::string_view::npos ||
|
|
(pos2 != absl::string_view::npos && pos2 < pos)) {
|
|
pos = pos2;
|
|
}
|
|
if (!absl::SimpleAtoi(rest.substr(0, pos), minor)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
GlVersion GlContext::GetGlVersion() const {
|
|
#ifdef GL_ES_VERSION_2_0 // This actually means "is GLES available".
|
|
return gl_major_version() < 3 ? GlVersion::kGLES2 : GlVersion::kGLES3;
|
|
#else // This is the "desktop GL" case.
|
|
return GlVersion::kGL;
|
|
#endif
|
|
}
|
|
|
|
bool GlContext::HasGlExtension(absl::string_view extension) const {
|
|
return gl_extensions_.find(extension) != gl_extensions_.end();
|
|
}
|
|
|
|
// Function for GL3.0+ to query for and store all of our available GL extensions
|
|
// in an easily-accessible set. The glGetString call is actually *not* required
|
|
// to work with GL_EXTENSIONS for newer GL versions, so we must maintain both
|
|
// variations of this function.
|
|
absl::Status GlContext::GetGlExtensions() {
|
|
// RET_CHECK logs by default, but here we just want to check the precondition;
|
|
// we'll fall back to the alternative implementation for older versions.
|
|
RET_CHECK(gl_major_version_ >= 3).SetNoLogging();
|
|
gl_extensions_.clear();
|
|
// glGetStringi only introduced in GL 3.0+; so we exit out this function if
|
|
// we don't have that function defined, regardless of version number reported.
|
|
// The function itself is also fully stubbed out if we're linking against an
|
|
// API version without a glGetStringi declaration. Although Emscripten
|
|
// sometimes provides this function, its default library implementation
|
|
// appears to only provide glGetString, so we skip this for Emscripten
|
|
// platforms to avoid possible undefined symbol or runtime errors.
|
|
#if (GL_VERSION_3_0 || GL_ES_VERSION_3_0) && !defined(__EMSCRIPTEN__)
|
|
if (!SymbolAvailable(&glGetStringi)) {
|
|
LOG(ERROR) << "GL major version > 3.0 indicated, but glGetStringi not "
|
|
<< "defined. Falling back to deprecated GL extensions querying "
|
|
<< "method.";
|
|
return absl::InternalError("glGetStringi not defined, but queried");
|
|
}
|
|
int num_extensions = 0;
|
|
glGetIntegerv(GL_NUM_EXTENSIONS, &num_extensions);
|
|
if (glGetError() != 0) {
|
|
return absl::InternalError("Error querying for number of extensions");
|
|
}
|
|
|
|
for (int i = 0; i < num_extensions; ++i) {
|
|
const GLubyte* res = glGetStringi(GL_EXTENSIONS, i);
|
|
if (glGetError() != 0 || res == nullptr) {
|
|
return absl::InternalError("Error querying for an extension by index");
|
|
}
|
|
const char* signed_res = reinterpret_cast<const char*>(res);
|
|
gl_extensions_.insert(signed_res);
|
|
}
|
|
|
|
return absl::OkStatus();
|
|
#else
|
|
return absl::InternalError("GL version mismatch in GlGetExtensions");
|
|
#endif // (GL_VERSION_3_0 || GL_ES_VERSION_3_0) && !defined(__EMSCRIPTEN__)
|
|
}
|
|
|
|
// Same as GetGlExtensions() above, but for pre-GL3.0, where glGetStringi did
|
|
// not exist.
|
|
absl::Status GlContext::GetGlExtensionsCompat() {
|
|
gl_extensions_.clear();
|
|
|
|
const GLubyte* res = glGetString(GL_EXTENSIONS);
|
|
if (glGetError() != 0 || res == nullptr) {
|
|
LOG(ERROR) << "Error querying for GL extensions";
|
|
return absl::InternalError("Error querying for GL extensions");
|
|
}
|
|
const char* signed_res = reinterpret_cast<const char*>(res);
|
|
gl_extensions_ = absl::StrSplit(signed_res, ' ');
|
|
|
|
return absl::OkStatus();
|
|
}
|
|
|
|
absl::Status GlContext::FinishInitialization(bool create_thread) {
|
|
if (create_thread) {
|
|
thread_ = absl::make_unique<GlContext::DedicatedThread>();
|
|
MP_RETURN_IF_ERROR(thread_->Run([this] { return EnterContext(nullptr); }));
|
|
}
|
|
|
|
return Run([this]() -> absl::Status {
|
|
// Clear any GL errors at this point: as this is a fresh context
|
|
// there shouldn't be any, but if we adopted an existing context (e.g. in
|
|
// some Emscripten cases), there might be some existing tripped error.
|
|
ForceClearExistingGlErrors();
|
|
|
|
absl::string_view version_string(
|
|
reinterpret_cast<const char*>(glGetString(GL_VERSION)));
|
|
|
|
// We will decide later whether we want to use the version numbers we query
|
|
// for, or instead derive that information from the context creation result,
|
|
// which we cache here.
|
|
GLint gl_major_version_from_context_creation = gl_major_version_;
|
|
|
|
// Let's try getting the numeric version if possible.
|
|
glGetIntegerv(GL_MAJOR_VERSION, &gl_major_version_);
|
|
GLenum err = glGetError();
|
|
if (err == GL_NO_ERROR) {
|
|
glGetIntegerv(GL_MINOR_VERSION, &gl_minor_version_);
|
|
} else {
|
|
// GL_MAJOR_VERSION is not supported on GL versions below 3. We have to
|
|
// parse the version std::string.
|
|
if (!ParseGlVersion(version_string, &gl_major_version_,
|
|
&gl_minor_version_)) {
|
|
LOG(WARNING) << "invalid GL_VERSION format: '" << version_string
|
|
<< "'; assuming 2.0";
|
|
gl_major_version_ = 2;
|
|
gl_minor_version_ = 0;
|
|
}
|
|
}
|
|
|
|
// If our platform-specific CreateContext already set a major GL version,
|
|
// then we use that. Otherwise, we use the queried-for result. We do this
|
|
// as a workaround for a Swiftshader on Android bug where the ES2 context
|
|
// can report major version 3 instead of 2 when queried. Therefore we trust
|
|
// the result from context creation more than from query. See b/152519932
|
|
// for more details.
|
|
if (gl_major_version_from_context_creation > 0 &&
|
|
gl_major_version_ != gl_major_version_from_context_creation) {
|
|
LOG(WARNING) << "Requested a context with major GL version "
|
|
<< gl_major_version_from_context_creation
|
|
<< " but context reports major version " << gl_major_version_
|
|
<< ". Setting to " << gl_major_version_from_context_creation
|
|
<< ".0";
|
|
gl_major_version_ = gl_major_version_from_context_creation;
|
|
gl_minor_version_ = 0;
|
|
}
|
|
|
|
LOG(INFO) << "GL version: " << gl_major_version_ << "." << gl_minor_version_
|
|
<< " (" << glGetString(GL_VERSION) << ")";
|
|
{
|
|
auto status = GetGlExtensions();
|
|
if (!status.ok()) {
|
|
status = GetGlExtensionsCompat();
|
|
}
|
|
MP_RETURN_IF_ERROR(status);
|
|
}
|
|
|
|
#if GL_ES_VERSION_2_0 // This actually means "is GLES available".
|
|
// No linear float filtering by default, check extensions.
|
|
can_linear_filter_float_textures_ =
|
|
HasGlExtension("OES_texture_float_linear");
|
|
#else
|
|
// Desktop GL should always allow linear filtering.
|
|
can_linear_filter_float_textures_ = true;
|
|
#endif // GL_ES_VERSION_2_0
|
|
|
|
return absl::OkStatus();
|
|
});
|
|
}
|
|
|
|
GlContext::GlContext() {}
|
|
|
|
GlContext::~GlContext() {
|
|
// Note: on Apple platforms, this object contains Objective-C objects.
|
|
// The destructor will release them, but ARC must be on.
|
|
#ifdef __OBJC__
|
|
#if !__has_feature(objc_arc)
|
|
#error This file must be built with ARC.
|
|
#endif
|
|
#endif // __OBJC__
|
|
|
|
if (thread_) {
|
|
auto status = thread_->Run([this] {
|
|
if (profiling_helper_) {
|
|
profiling_helper_->LogAllTimestamps();
|
|
}
|
|
return ExitContext(nullptr);
|
|
});
|
|
LOG_IF(ERROR, !status.ok())
|
|
<< "Failed to deactivate context on thread: " << status;
|
|
if (thread_->IsCurrentThread()) {
|
|
thread_.release()->SelfDestruct();
|
|
}
|
|
}
|
|
DestroyContext();
|
|
}
|
|
|
|
void GlContext::SetProfilingContext(
|
|
std::shared_ptr<mediapipe::ProfilingContext> profiling_context) {
|
|
// Create the GlProfilingHelper if it is uninitialized.
|
|
if (!profiling_helper_ && profiling_context) {
|
|
profiling_helper_ = profiling_context->CreateGlProfilingHelper();
|
|
}
|
|
}
|
|
|
|
absl::Status GlContext::SwitchContextAndRun(GlStatusFunction gl_func) {
|
|
ContextBinding saved_context;
|
|
MP_RETURN_IF_ERROR(EnterContext(&saved_context)) << " (entering GL context)";
|
|
auto status = gl_func();
|
|
LogUncheckedGlErrors(CheckForGlErrors());
|
|
MP_RETURN_IF_ERROR(ExitContext(&saved_context)) << " (exiting GL context)";
|
|
return status;
|
|
}
|
|
|
|
absl::Status GlContext::Run(GlStatusFunction gl_func, int node_id,
|
|
Timestamp input_timestamp) {
|
|
absl::Status status;
|
|
if (profiling_helper_) {
|
|
gl_func = [=] {
|
|
profiling_helper_->MarkTimestamp(node_id, input_timestamp,
|
|
/*is_finish=*/false);
|
|
auto status = gl_func();
|
|
profiling_helper_->MarkTimestamp(node_id, input_timestamp,
|
|
/*is_finish=*/true);
|
|
return status;
|
|
};
|
|
}
|
|
if (thread_) {
|
|
bool had_gl_errors = false;
|
|
status = thread_->Run([this, gl_func, &had_gl_errors] {
|
|
auto status = gl_func();
|
|
had_gl_errors = CheckForGlErrors();
|
|
return status;
|
|
});
|
|
LogUncheckedGlErrors(had_gl_errors);
|
|
} else {
|
|
status = SwitchContextAndRun(gl_func);
|
|
}
|
|
return status;
|
|
}
|
|
|
|
void GlContext::RunWithoutWaiting(GlVoidFunction gl_func) {
|
|
if (thread_) {
|
|
// Add ref to keep the context alive while the task is executing.
|
|
auto context = shared_from_this();
|
|
thread_->RunWithoutWaiting([this, context, gl_func] {
|
|
gl_func();
|
|
LogUncheckedGlErrors(CheckForGlErrors());
|
|
});
|
|
} else {
|
|
// TODO: queue up task instead.
|
|
auto status = SwitchContextAndRun([gl_func] {
|
|
gl_func();
|
|
return absl::OkStatus();
|
|
});
|
|
if (!status.ok()) {
|
|
LOG(ERROR) << "Error in RunWithoutWaiting: " << status;
|
|
}
|
|
}
|
|
}
|
|
|
|
std::weak_ptr<GlContext>& GlContext::CurrentContext() {
|
|
// Workaround for b/67878799.
|
|
#ifndef __EMSCRIPTEN__
|
|
absl::LeakCheckDisabler disable_leak_check;
|
|
#endif
|
|
ABSL_CONST_INIT thread_local std::weak_ptr<GlContext> current_context;
|
|
return current_context;
|
|
}
|
|
|
|
absl::Status GlContext::SwitchContext(ContextBinding* saved_context,
|
|
const ContextBinding& new_context)
|
|
ABSL_NO_THREAD_SAFETY_ANALYSIS {
|
|
std::shared_ptr<GlContext> old_context_obj = CurrentContext().lock();
|
|
std::shared_ptr<GlContext> new_context_obj =
|
|
new_context.context_object.lock();
|
|
if (saved_context) {
|
|
saved_context->context_object = old_context_obj;
|
|
GetCurrentContextBinding(saved_context);
|
|
}
|
|
// Check that the context object is consistent with the native context.
|
|
if (old_context_obj && saved_context) {
|
|
DCHECK(old_context_obj->context_ == saved_context->context);
|
|
}
|
|
if (new_context_obj) {
|
|
DCHECK(new_context_obj->context_ == new_context.context);
|
|
}
|
|
|
|
if (new_context_obj && (old_context_obj == new_context_obj)) {
|
|
return absl::OkStatus();
|
|
}
|
|
|
|
if (old_context_obj) {
|
|
// 1. Even if we cannot restore the new context, we want to get out of the
|
|
// old one (we may be deliberately trying to exit it).
|
|
// 2. We need to unset the old context before we unlock the old mutex,
|
|
// Therefore, we first unset the old one before setting the new one.
|
|
MP_RETURN_IF_ERROR(SetCurrentContextBinding({}));
|
|
old_context_obj->context_use_mutex_.Unlock();
|
|
CurrentContext().reset();
|
|
}
|
|
|
|
if (new_context_obj) {
|
|
new_context_obj->context_use_mutex_.Lock();
|
|
auto status = SetCurrentContextBinding(new_context);
|
|
if (status.ok()) {
|
|
CurrentContext() = new_context_obj;
|
|
} else {
|
|
new_context_obj->context_use_mutex_.Unlock();
|
|
}
|
|
return status;
|
|
} else {
|
|
return SetCurrentContextBinding(new_context);
|
|
}
|
|
}
|
|
|
|
absl::Status GlContext::EnterContext(ContextBinding* saved_context) {
|
|
DCHECK(HasContext());
|
|
return SwitchContext(saved_context, ThisContextBinding());
|
|
}
|
|
|
|
absl::Status GlContext::ExitContext(const ContextBinding* saved_context) {
|
|
ContextBinding no_context;
|
|
if (!saved_context) {
|
|
saved_context = &no_context;
|
|
}
|
|
return SwitchContext(nullptr, *saved_context);
|
|
}
|
|
|
|
std::shared_ptr<GlContext> GlContext::GetCurrent() {
|
|
return CurrentContext().lock();
|
|
}
|
|
|
|
void GlContext::GlFinishCalled() {
|
|
absl::MutexLock lock(&mutex_);
|
|
++gl_finish_count_;
|
|
wait_for_gl_finish_cv_.SignalAll();
|
|
}
|
|
|
|
class GlFinishSyncPoint : public GlSyncPoint {
|
|
public:
|
|
explicit GlFinishSyncPoint(const std::shared_ptr<GlContext>& gl_context)
|
|
: GlSyncPoint(gl_context),
|
|
gl_finish_count_(gl_context_->gl_finish_count()) {}
|
|
|
|
void Wait() override {
|
|
gl_context_->WaitForGlFinishCountPast(gl_finish_count_);
|
|
}
|
|
|
|
bool IsReady() override {
|
|
return gl_context_->gl_finish_count() > gl_finish_count_;
|
|
}
|
|
|
|
private:
|
|
// Number of glFinish calls done before the creation of this token.
|
|
int64_t gl_finish_count_ = -1;
|
|
};
|
|
|
|
class GlFenceSyncPoint : public GlSyncPoint {
|
|
public:
|
|
explicit GlFenceSyncPoint(const std::shared_ptr<GlContext>& gl_context)
|
|
: GlSyncPoint(gl_context) {
|
|
gl_context_->Run([this] {
|
|
sync_ = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
|
glFlush();
|
|
});
|
|
}
|
|
|
|
~GlFenceSyncPoint() {
|
|
if (sync_) {
|
|
GLsync sync = sync_;
|
|
gl_context_->RunWithoutWaiting([sync] { glDeleteSync(sync); });
|
|
}
|
|
}
|
|
|
|
GlFenceSyncPoint(const GlFenceSyncPoint&) = delete;
|
|
GlFenceSyncPoint& operator=(const GlFenceSyncPoint&) = delete;
|
|
|
|
void Wait() override {
|
|
if (!sync_) return;
|
|
gl_context_->Run([this] {
|
|
GLenum result =
|
|
glClientWaitSync(sync_, 0, std::numeric_limits<uint64_t>::max());
|
|
if (result == GL_ALREADY_SIGNALED || result == GL_CONDITION_SATISFIED) {
|
|
glDeleteSync(sync_);
|
|
sync_ = nullptr;
|
|
}
|
|
// TODO: do something if the wait fails?
|
|
});
|
|
}
|
|
|
|
void WaitOnGpu() override {
|
|
if (!sync_) return;
|
|
// TODO: do not wait if we are already on the same context?
|
|
// WebGL2 specifies a waitSync call, but since cross-context
|
|
// synchronization is not supported, it's actually a no-op. Firefox prints
|
|
// a warning when it's called, so let's just skip the call. See
|
|
// b/184637485 for details.
|
|
#ifndef __EMSCRIPTEN__
|
|
glWaitSync(sync_, 0, GL_TIMEOUT_IGNORED);
|
|
#endif
|
|
}
|
|
|
|
bool IsReady() override {
|
|
if (!sync_) return true;
|
|
bool ready = false;
|
|
// TODO: we should not block on the original context if possible.
|
|
gl_context_->Run([this, &ready] {
|
|
GLenum result = glClientWaitSync(sync_, 0, 0);
|
|
if (result == GL_ALREADY_SIGNALED || result == GL_CONDITION_SATISFIED) {
|
|
glDeleteSync(sync_);
|
|
sync_ = nullptr;
|
|
ready = true;
|
|
}
|
|
});
|
|
return ready;
|
|
}
|
|
|
|
private:
|
|
GLsync sync_;
|
|
};
|
|
|
|
void GlMultiSyncPoint::Add(std::shared_ptr<GlSyncPoint> new_sync) {
|
|
for (auto& sync : syncs_) {
|
|
if (sync->GetContext() == new_sync->GetContext()) {
|
|
sync = std::move(new_sync);
|
|
return;
|
|
}
|
|
}
|
|
syncs_.emplace_back(std::move(new_sync));
|
|
}
|
|
|
|
void GlMultiSyncPoint::Wait() {
|
|
for (auto& sync : syncs_) {
|
|
sync->Wait();
|
|
}
|
|
// At this point all the syncs have been reached, so clear them out.
|
|
syncs_.clear();
|
|
}
|
|
|
|
void GlMultiSyncPoint::WaitOnGpu() {
|
|
for (auto& sync : syncs_) {
|
|
sync->WaitOnGpu();
|
|
}
|
|
// TODO: when do we clear out these syncs?
|
|
}
|
|
|
|
bool GlMultiSyncPoint::IsReady() {
|
|
syncs_.erase(
|
|
std::remove_if(syncs_.begin(), syncs_.end(),
|
|
std::bind(&GlSyncPoint::IsReady, std::placeholders::_1)),
|
|
syncs_.end());
|
|
return syncs_.empty();
|
|
}
|
|
|
|
// Set this to 1 to disable syncing. This can be used to verify that a test
|
|
// correctly detects sync issues.
|
|
#define MEDIAPIPE_DISABLE_GL_SYNC_FOR_DEBUG 0
|
|
|
|
#if MEDIAPIPE_DISABLE_GL_SYNC_FOR_DEBUG
|
|
class GlNopSyncPoint : public GlSyncPoint {
|
|
public:
|
|
explicit GlNopSyncPoint(const std::shared_ptr<GlContext>& gl_context)
|
|
: GlSyncPoint(gl_context) {}
|
|
|
|
void Wait() override {}
|
|
|
|
bool IsReady() override { return true; }
|
|
};
|
|
#endif
|
|
|
|
std::shared_ptr<GlSyncPoint> GlContext::CreateSyncToken() {
|
|
std::shared_ptr<GlSyncPoint> token;
|
|
#if MEDIAPIPE_DISABLE_GL_SYNC_FOR_DEBUG
|
|
token.reset(new GlNopSyncPoint(shared_from_this()));
|
|
#else
|
|
|
|
#ifdef __EMSCRIPTEN__
|
|
// In Emscripten the glWaitSync function is non-null depending on linkopts,
|
|
// but only works in a WebGL2 context, so fall back to use Finish if it is a
|
|
// WebGL1/ES2 context.
|
|
// TODO: apply this more generally once b/152794517 is fixed.
|
|
bool useFenceSync = gl_major_version() > 2;
|
|
#else
|
|
bool useFenceSync = SymbolAvailable(&glWaitSync);
|
|
#endif // __EMSCRIPTEN__
|
|
if (useFenceSync) {
|
|
token.reset(new GlFenceSyncPoint(shared_from_this()));
|
|
} else {
|
|
token.reset(new GlFinishSyncPoint(shared_from_this()));
|
|
}
|
|
#endif
|
|
return token;
|
|
}
|
|
|
|
std::shared_ptr<GlSyncPoint> GlContext::TestOnly_CreateSpecificSyncToken(
|
|
SyncTokenTypeForTest type) {
|
|
std::shared_ptr<GlSyncPoint> token;
|
|
switch (type) {
|
|
case SyncTokenTypeForTest::kGlFinish:
|
|
token.reset(new GlFinishSyncPoint(shared_from_this()));
|
|
return token;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
// Atomically set var to the greater of its current value or target.
|
|
template <typename T>
|
|
static void assign_larger_value(std::atomic<T>* var, T target) {
|
|
T current = var->load();
|
|
while (current < target && !var->compare_exchange_weak(current, target)) {
|
|
}
|
|
}
|
|
|
|
// Note: this can get called from an arbitrary thread which is dealing with a
|
|
// GlFinishSyncPoint originating from this context.
|
|
void GlContext::WaitForGlFinishCountPast(int64_t count_to_pass) {
|
|
if (gl_finish_count_ > count_to_pass) return;
|
|
|
|
// If we've been asked to do a glFinish, note the count we need to reach and
|
|
// signal the context our thread may currently be blocked on.
|
|
{
|
|
absl::MutexLock lock(&mutex_);
|
|
assign_larger_value(&gl_finish_count_target_, count_to_pass + 1);
|
|
wait_for_gl_finish_cv_.SignalAll();
|
|
if (context_waiting_on_) {
|
|
context_waiting_on_->wait_for_gl_finish_cv_.SignalAll();
|
|
}
|
|
}
|
|
|
|
auto finish_task = [this, count_to_pass]() {
|
|
// When a GlFinishSyncToken is created it takes the current finish count
|
|
// from the GlContext, and we must wait for gl_finish_count_ to pass it.
|
|
// Therefore, we need to do at most one more glFinish call. This DCHECK
|
|
// is used for documentation and sanity-checking purposes.
|
|
DCHECK(gl_finish_count_ >= count_to_pass);
|
|
if (gl_finish_count_ == count_to_pass) {
|
|
glFinish();
|
|
GlFinishCalled();
|
|
}
|
|
};
|
|
|
|
if (IsCurrent()) {
|
|
// If we are already on the current context, we cannot call
|
|
// RunWithoutWaiting, since that task will not run until this function
|
|
// returns. Instead, call it directly.
|
|
finish_task();
|
|
return;
|
|
}
|
|
|
|
std::shared_ptr<GlContext> other = GetCurrent();
|
|
if (other) {
|
|
// If another context is current, make a note that it is blocked on us, so
|
|
// it can signal the right condition variable if it is asked to do a
|
|
// glFinish.
|
|
absl::MutexLock other_lock(&other->mutex_);
|
|
DCHECK(!other->context_waiting_on_);
|
|
other->context_waiting_on_ = this;
|
|
}
|
|
// We do not schedule this action using Run because we don't necessarily
|
|
// want to wait for it to complete. If another job calls GlFinishCalled
|
|
// sooner, we are done.
|
|
RunWithoutWaiting(std::move(finish_task));
|
|
{
|
|
absl::MutexLock lock(&mutex_);
|
|
while (gl_finish_count_ <= count_to_pass) {
|
|
if (other && other->gl_finish_count_ < other->gl_finish_count_target_) {
|
|
// If another context's dedicated thread is current, it is blocked
|
|
// waiting for this context to issue a glFinish call. But this context
|
|
// may also block waiting for the other context to do the same: this can
|
|
// happen when two contexts are handling each other's GlFinishSyncPoints
|
|
// (e.g. a producer and a consumer). To avoid a deadlock a context that
|
|
// is waiting on another context must still service Wait calls it may
|
|
// receive from its own GlFinishSyncPoints.
|
|
//
|
|
// We unlock this context's mutex to avoid holding both at the same
|
|
// time.
|
|
mutex_.Unlock();
|
|
{
|
|
glFinish();
|
|
other->GlFinishCalled();
|
|
}
|
|
mutex_.Lock();
|
|
// Because we temporarily unlocked mutex_, we cannot wait on the
|
|
// condition variable wait away; we need to go back to re-checking the
|
|
// condition. Otherwise we might miss a signal.
|
|
continue;
|
|
}
|
|
wait_for_gl_finish_cv_.Wait(&mutex_);
|
|
}
|
|
}
|
|
|
|
if (other) {
|
|
// The other context is no longer waiting on us.
|
|
absl::MutexLock other_lock(&other->mutex_);
|
|
other->context_waiting_on_ = nullptr;
|
|
}
|
|
}
|
|
|
|
void GlContext::WaitSyncToken(const std::shared_ptr<GlSyncPoint>& token) {
|
|
CHECK(token);
|
|
token->Wait();
|
|
}
|
|
|
|
bool GlContext::SyncTokenIsReady(const std::shared_ptr<GlSyncPoint>& token) {
|
|
CHECK(token);
|
|
return token->IsReady();
|
|
}
|
|
|
|
void GlContext::ForceClearExistingGlErrors() {
|
|
LogUncheckedGlErrors(CheckForGlErrors(/*force=*/true));
|
|
}
|
|
|
|
bool GlContext::CheckForGlErrors() { return CheckForGlErrors(false); }
|
|
|
|
bool GlContext::CheckForGlErrors(bool force) {
|
|
#if UNSAFE_EMSCRIPTEN_SKIP_GL_ERROR_HANDLING
|
|
if (!force) {
|
|
LOG_FIRST_N(WARNING, 1) << "OpenGL error checking is disabled";
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
if (!HasContext()) return false;
|
|
GLenum error;
|
|
bool had_error = false;
|
|
while ((error = glGetError()) != GL_NO_ERROR) {
|
|
had_error = true;
|
|
switch (error) {
|
|
case GL_INVALID_ENUM:
|
|
LOG(INFO) << "Found unchecked GL error: GL_INVALID_ENUM";
|
|
break;
|
|
case GL_INVALID_VALUE:
|
|
LOG(INFO) << "Found unchecked GL error: GL_INVALID_VALUE";
|
|
break;
|
|
case GL_INVALID_OPERATION:
|
|
LOG(INFO) << "Found unchecked GL error: GL_INVALID_OPERATION";
|
|
break;
|
|
case GL_INVALID_FRAMEBUFFER_OPERATION:
|
|
LOG(INFO)
|
|
<< "Found unchecked GL error: GL_INVALID_FRAMEBUFFER_OPERATION";
|
|
break;
|
|
case GL_OUT_OF_MEMORY:
|
|
LOG(INFO) << "Found unchecked GL error: GL_OUT_OF_MEMORY";
|
|
break;
|
|
default:
|
|
LOG(INFO) << "Found unchecked GL error: UNKNOWN ERROR";
|
|
break;
|
|
}
|
|
}
|
|
return had_error;
|
|
}
|
|
|
|
void GlContext::LogUncheckedGlErrors(bool had_gl_errors) {
|
|
if (had_gl_errors) {
|
|
// TODO: ideally we would print a backtrace here, or at least
|
|
// the name of the current calculator, to make it easier to find the
|
|
// culprit. In practice, getting a backtrace from Android without crashing
|
|
// is nearly impossible, so screw it. Just change this to LOG(FATAL) when
|
|
// you want to debug.
|
|
LOG(WARNING) << "Ignoring unchecked GL error.";
|
|
}
|
|
}
|
|
|
|
const GlTextureInfo& GlTextureInfoForGpuBufferFormat(GpuBufferFormat format,
|
|
int plane) {
|
|
std::shared_ptr<GlContext> ctx = GlContext::GetCurrent();
|
|
CHECK(ctx != nullptr);
|
|
return GlTextureInfoForGpuBufferFormat(format, plane, ctx->GetGlVersion());
|
|
}
|
|
|
|
void GlContext::SetStandardTextureParams(GLenum target, GLint internal_format) {
|
|
// Default to using linear filter everywhere. For float32 textures, fall back
|
|
// to GL_NEAREST if linear filtering unsupported.
|
|
GLint filter;
|
|
switch (internal_format) {
|
|
case GL_R32F:
|
|
case GL_RG32F:
|
|
case GL_RGBA32F:
|
|
// 32F (unlike 16f) textures do not always support texture filtering
|
|
// (According to OpenGL ES specification [TEXTURE IMAGE SPECIFICATION])
|
|
filter = can_linear_filter_float_textures_ ? GL_LINEAR : GL_NEAREST;
|
|
break;
|
|
default:
|
|
filter = GL_LINEAR;
|
|
}
|
|
glTexParameteri(target, GL_TEXTURE_MIN_FILTER, filter);
|
|
glTexParameteri(target, GL_TEXTURE_MAG_FILTER, filter);
|
|
glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
}
|
|
|
|
} // namespace mediapipe
|