Skip to content

Commit

Permalink
Add EglSync wrapper.
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 692252113
  • Loading branch information
MediaPipe Team authored and copybara-github committed Nov 1, 2024
1 parent cff4666 commit b303d4f
Show file tree
Hide file tree
Showing 6 changed files with 398 additions and 0 deletions.
32 changes: 32 additions & 0 deletions mediapipe/gpu/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -841,6 +841,38 @@ cc_library(
}),
)

cc_library(
name = "egl_errors",
srcs = ["egl_errors.cc"],
hdrs = ["egl_errors.h"],
visibility = ["//visibility:public"],
deps = [
":gl_base",
"@com_google_absl//absl/status",
"@com_google_absl//absl/strings",
],
)

cc_library(
name = "egl_sync",
srcs = ["egl_sync.cc"],
hdrs = ["egl_sync.h"],
visibility = ["//visibility:public"],
deps = [
":egl_errors",
":gl_base",
"//mediapipe/framework/deps:no_destructor",
"//mediapipe/framework/formats:unique_fd",
"//mediapipe/framework/port:ret_check",
"//mediapipe/framework/port:status",
"@com_google_absl//absl/cleanup",
"@com_google_absl//absl/log:absl_log",
"@com_google_absl//absl/status",
"@com_google_absl//absl/status:statusor",
"@com_google_absl//absl/strings",
],
)

cc_library(
name = "gl_texture_util",
srcs = ["gl_texture_util.cc"],
Expand Down
72 changes: 72 additions & 0 deletions mediapipe/gpu/egl_errors.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#include "mediapipe/gpu/egl_errors.h"

#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "mediapipe/gpu/gl_base.h"

namespace mediapipe {

absl::Status GetEglError() {
EGLint error = eglGetError();
switch (error) {
case EGL_SUCCESS:
return absl::OkStatus();
case EGL_NOT_INITIALIZED:
return absl::InternalError(
"EGL is not initialized, or could not be initialized, for the "
"specified EGL display connection.");
case EGL_BAD_ACCESS:
return absl::InternalError(
"EGL cannot access a requested resource (for example a context is "
"bound in another thread).");
case EGL_BAD_ALLOC:
return absl::InternalError(
"EGL failed to allocate resources for the requested operation.");
case EGL_BAD_ATTRIBUTE:
return absl::InternalError(
"An unrecognized attribute or attribute value was passed in the "
"attribute list.");
case EGL_BAD_CONTEXT:
return absl::InternalError(
"An EGLContext argument does not name a valid EGL rendering "
"context.");
case EGL_BAD_CONFIG:
return absl::InternalError(
"An EGLConfig argument does not name a valid EGL frame buffer "
"configuration.");
case EGL_BAD_CURRENT_SURFACE:
return absl::InternalError(
"The current surface of the calling thread is a window, pixel buffer "
"or pixmap that is no longer valid.");
case EGL_BAD_DISPLAY:
return absl::InternalError(
"An EGLDisplay argument does not name a valid EGL display "
"connection.");
case EGL_BAD_SURFACE:
return absl::InternalError(
"An EGLSurface argument does not name a valid surface (window, pixel "
"buffer or pixmap) configured for GL rendering.");
case EGL_BAD_MATCH:
return absl::InternalError(
"Arguments are inconsistent (for example, a valid context requires "
"buffers not supplied by a valid surface).");
case EGL_BAD_PARAMETER:
return absl::InternalError("One or more argument values are invalid.");
case EGL_BAD_NATIVE_PIXMAP:
return absl::InternalError(
"A NativePixmapType argument does not refer to a valid native "
"pixmap.");
case EGL_BAD_NATIVE_WINDOW:
return absl::InternalError(
"A NativeWindowType argument does not refer to a valid native "
"window.");
case EGL_CONTEXT_LOST:
return absl::InternalError(
"A power management event has occurred. The application must destroy "
"all contexts and reinitialize OpenGL ES state and objects to "
"continue rendering.");
}
return absl::UnknownError(absl::StrCat("EGL error: ", error));
}

} // namespace mediapipe
13 changes: 13 additions & 0 deletions mediapipe/gpu/egl_errors.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#ifndef MEDIAPIPE_GPU_EGL_ERRORS_H_
#define MEDIAPIPE_GPU_EGL_ERRORS_H_

#include "absl/status/status.h"

namespace mediapipe {

// Returns the error of the last called EGL function in the current thread.
absl::Status GetEglError();

} // namespace mediapipe

#endif // MEDIAPIPE_GPU_EGL_ERRORS_H_
214 changes: 214 additions & 0 deletions mediapipe/gpu/egl_sync.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
#include "mediapipe/gpu/egl_sync.h"

#include <unistd.h>

#include <cstring>
#include <utility>

#include "absl/cleanup/cleanup.h"
#include "absl/log/absl_log.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "mediapipe/framework/deps/no_destructor.h"
#include "mediapipe/framework/formats/unique_fd.h"
#include "mediapipe/framework/port/ret_check.h"
#include "mediapipe/framework/port/status_macros.h"
#include "mediapipe/gpu/egl_errors.h"
#include "mediapipe/gpu/gl_base.h"

namespace mediapipe {

namespace {

PFNEGLCREATESYNCKHRPROC eglCreateSyncKHR;
PFNEGLWAITSYNCKHRPROC eglWaitSyncKHR;
PFNEGLCLIENTWAITSYNCKHRPROC eglClientWaitSyncKHR;
PFNEGLDESTROYSYNCKHRPROC eglDestroySyncKHR;
PFNEGLDUPNATIVEFENCEFDANDROIDPROC eglDupNativeFenceFDANDROID;
PFNEGLGETSYNCATTRIBKHRPROC eglGetSyncAttribKHR;

bool HasExtension(EGLDisplay display, const char* extension) {
const char* extensions = eglQueryString(display, EGL_EXTENSIONS);
return extensions && std::strstr(extensions, extension);
}

absl::Status CheckEglFenceSyncSupported(EGLDisplay display) {
static bool supported = HasExtension(display, "EGL_KHR_fence_sync");
if (supported) {
return absl::OkStatus();
}
return absl::UnavailableError("EGL_KHR_fence_sync unavailable.");
}

absl::Status CheckEglWaitSyncSupported(EGLDisplay display) {
static bool supported = HasExtension(display, "EGL_KHR_wait_sync");
if (supported) {
return absl::OkStatus();
}
return absl::UnavailableError("EGL_KHR_wait_sync unavailable.");
}

absl::Status CheckEglAndroidNativeSyncSupported(EGLDisplay display) {
static bool supported =
HasExtension(display, "EGL_ANDROID_native_fence_sync");
if (supported) {
return absl::OkStatus();
}
return absl::UnavailableError("EGL_ANDROID_native_fence_sync unavailable.");
}

absl::Status CheckEglSyncSupported(EGLDisplay egl_display) {
static NoDestructor<absl::Status> support_status([&]() -> absl::Status {
MP_RETURN_IF_ERROR(CheckEglFenceSyncSupported(egl_display));
MP_RETURN_IF_ERROR(CheckEglWaitSyncSupported(egl_display));

RET_CHECK(eglCreateSyncKHR = reinterpret_cast<PFNEGLCREATESYNCKHRPROC>(
eglGetProcAddress("eglCreateSyncKHR")));
RET_CHECK(eglWaitSyncKHR = reinterpret_cast<PFNEGLWAITSYNCKHRPROC>(
eglGetProcAddress("eglWaitSyncKHR")));
RET_CHECK(eglClientWaitSyncKHR =
reinterpret_cast<PFNEGLCLIENTWAITSYNCKHRPROC>(
eglGetProcAddress("eglClientWaitSyncKHR")));
RET_CHECK(eglDestroySyncKHR = reinterpret_cast<PFNEGLDESTROYSYNCKHRPROC>(
eglGetProcAddress("eglDestroySyncKHR")));
RET_CHECK(eglGetSyncAttribKHR =
reinterpret_cast<PFNEGLGETSYNCATTRIBKHRPROC>(
eglGetProcAddress("eglGetSyncAttribKHR")));
return absl::OkStatus();
}());
return *support_status;
}

absl::Status CheckEglNativeSyncSupported(EGLDisplay egl_display) {
static NoDestructor<absl::Status> support_status([&]() -> absl::Status {
MP_RETURN_IF_ERROR(CheckEglAndroidNativeSyncSupported(egl_display));
RET_CHECK(eglDupNativeFenceFDANDROID =
reinterpret_cast<PFNEGLDUPNATIVEFENCEFDANDROIDPROC>(
eglGetProcAddress("eglDupNativeFenceFDANDROID")));
return absl::OkStatus();
}());
return *support_status;
}

} // namespace

absl::StatusOr<EglSync> EglSync::Create(EGLDisplay display) {
MP_RETURN_IF_ERROR(CheckEglSyncSupported(display));

const EGLSyncKHR egl_sync =
eglCreateSyncKHR(display, EGL_SYNC_FENCE_KHR, nullptr);
RET_CHECK_NE(egl_sync, EGL_NO_SYNC_KHR)
<< "Create/eglCreateSyncKHR failed: " << GetEglError();
return EglSync(display, egl_sync);
}

absl::StatusOr<EglSync> EglSync::CreateNative(EGLDisplay display) {
MP_RETURN_IF_ERROR(CheckEglSyncSupported(display));
MP_RETURN_IF_ERROR(CheckEglNativeSyncSupported(display));

const EGLSyncKHR egl_sync =
eglCreateSyncKHR(display, EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr);
RET_CHECK_NE(egl_sync, EGL_NO_SYNC_KHR)
<< "CreateNative/eglCreateSyncKHR failed: " << GetEglError();
return EglSync(display, egl_sync);
}

absl::StatusOr<EglSync> EglSync::CreateNative(EGLDisplay display,
const UniqueFd& native_fence_fd) {
RET_CHECK(native_fence_fd.IsValid());
MP_RETURN_IF_ERROR(CheckEglSyncSupported(display));
MP_RETURN_IF_ERROR(CheckEglNativeSyncSupported(display));

MP_ASSIGN_OR_RETURN(UniqueFd fd_for_egl, native_fence_fd.Dup());
// NOTE: it looks like one could rely on `UniqueFd` for the cleanup, but
// there's some clashing on ownership of the FD when passing it to
// eglCreateSyncKHR and then using `UniqueFd::Release`, hence relying on
// absl::Cleanup.
const int fd = fd_for_egl.Release();
absl::Cleanup fd_cleanup = [fd]() { close(fd); };
const EGLint sync_attribs[] = {EGL_SYNC_NATIVE_FENCE_FD_ANDROID,
static_cast<EGLint>(fd), EGL_NONE};
const EGLSyncKHR egl_sync =
eglCreateSyncKHR(display, EGL_SYNC_NATIVE_FENCE_ANDROID, sync_attribs);
RET_CHECK_NE(egl_sync, EGL_NO_SYNC_KHR) << absl::StrCat(
"CreateNative/eglCreateSyncKHR with original FD: ", native_fence_fd.Get(),
" and dup FD: ", fd, " - failed: ", GetEglError());
// EGL took ownership of the passed FD as eglCreateSyncKHR succeeded, so
// cancelling the cleanup.
std::move(fd_cleanup).Cancel();

return EglSync(display, egl_sync);
}

EglSync::EglSync(EglSync&& sync) { *this = std::move(sync); }

EglSync& EglSync::operator=(EglSync&& sync) {
if (this != &sync) {
Invalidate();

using std::swap;
sync_ = std::exchange(sync.sync_, EGL_NO_SYNC_KHR);
display_ = std::exchange(sync.display_, EGL_NO_DISPLAY);
}
return *this;
}

void EglSync::Invalidate() {
if (sync_ == EGL_NO_SYNC_KHR || display_ == EGL_NO_DISPLAY) {
return;
}

const absl::Status egl_sync_support = CheckEglSyncSupported(display_);
if (!egl_sync_support.ok()) {
ABSL_LOG(DFATAL) << "Attempt to destroy an EGL sync: " << egl_sync_support;
return;
}

// Needs extension: EGL_KHR_fence_sync (EGL) / GL_OES_EGL_sync (OpenGL ES).
// Note: we're doing nothing when the function pointer is nullptr, or the
// call returns EGL_FALSE.
const EGLBoolean result = eglDestroySyncKHR(display_, sync_);
if (result == EGL_FALSE) {
ABSL_LOG(DFATAL) << "eglDestroySyncKHR failed: " << GetEglError();
}
sync_ = EGL_NO_SYNC_KHR;
}

absl::Status EglSync::WaitOnGpu() {
MP_RETURN_IF_ERROR(CheckEglSyncSupported(display_));

const EGLint result = eglWaitSyncKHR(display_, sync_, 0);
RET_CHECK_EQ(result, EGL_TRUE) << "eglWaitSyncKHR failed: " << GetEglError();
return absl::OkStatus();
}

absl::Status EglSync::Wait() {
MP_RETURN_IF_ERROR(CheckEglSyncSupported(display_));

const EGLint result = eglClientWaitSyncKHR(
display_, sync_, EGL_SYNC_FLUSH_COMMANDS_BIT_KHR, EGL_FOREVER_KHR);
RET_CHECK_EQ(result, EGL_CONDITION_SATISFIED_KHR)
<< "eglClientWaitSyncKHR failed: " << GetEglError();
return absl::OkStatus();
}

absl::StatusOr<UniqueFd> EglSync::DupNativeFd() {
MP_RETURN_IF_ERROR(CheckEglNativeSyncSupported(display_));

const int fd = eglDupNativeFenceFDANDROID(display_, sync_);
RET_CHECK_NE(fd, EGL_NO_NATIVE_FENCE_FD_ANDROID)
<< "eglDupNativeFenceFDANDROID failed: " << GetEglError();
return UniqueFd(fd);
}

absl::StatusOr<bool> EglSync::IsSignaled() {
EGLint status;
const EGLBoolean success =
eglGetSyncAttribKHR(display_, sync_, EGL_SYNC_STATUS_KHR, &status);
RET_CHECK_EQ(success, EGL_TRUE)
<< "eglGetSyncAttribKHR failed: " << GetEglError();
return status == EGL_SIGNALED_KHR;
}

} // namespace mediapipe
Loading

0 comments on commit b303d4f

Please sign in to comment.