#include "ui/gl/gl_context_egl.h"
#include <memory>
#include "base/command_line.h"
#include "base/logging.h"
#include "base/ranges/algorithm.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "third_party/khronos/EGL/egl.h"
#include "third_party/khronos/EGL/eglext.h"
#include "ui/gl/egl_util.h"
#include "ui/gl/gl_bindings.h"
#include "ui/gl/gl_display.h"
#include "ui/gl/gl_fence.h"
#include "ui/gl/gl_gl_api_implementation.h"
#include "ui/gl/gl_surface_egl.h"
#if BUILDFLAG(IS_MAC)
#include "base/mac/mac_util.h"
#endif
#ifndef EGL_CHROMIUM_create_context_bind_generates_resource
#define EGL_CHROMIUM_create_context_bind_generates_resource 1
#define EGL_CONTEXT_BIND_GENERATES_RESOURCE_CHROMIUM 0x33AD
#endif
#ifndef EGL_ANGLE_create_context_webgl_compatibility
#define EGL_ANGLE_create_context_webgl_compatibility 1
#define EGL_CONTEXT_WEBGL_COMPATIBILITY_ANGLE 0x33AC
#endif
#ifndef EGL_ANGLE_display_texture_share_group
#define EGL_ANGLE_display_texture_share_group 1
#define EGL_DISPLAY_TEXTURE_SHARE_GROUP_ANGLE 0x33AF
#endif
#ifndef EGL_ANGLE_display_semaphore_share_group
#define EGL_ANGLE_display_semaphore_share_group 1
#define EGL_DISPLAY_SEMAPHORE_SHARE_GROUP_ANGLE 0x348D
#endif
#ifndef EGL_ANGLE_external_context_and_surface
#define EGL_ANGLE_external_context_and_surface 1
#define EGL_EXTERNAL_CONTEXT_ANGLE 0x348E
#define EGL_EXTERNAL_CONTEXT_SAVE_STATE_ANGLE 0x3490
#endif
#ifndef EGL_ANGLE_create_context_client_arrays
#define EGL_ANGLE_create_context_client_arrays 1
#define EGL_CONTEXT_CLIENT_ARRAYS_ENABLED_ANGLE 0x3452
#endif
#ifndef EGL_ANGLE_robust_resource_initialization
#define EGL_ANGLE_robust_resource_initialization 1
#define EGL_ROBUST_RESOURCE_INITIALIZATION_ANGLE 0x3453
#endif
#ifndef EGL_ANGLE_create_context_backwards_compatible
#define EGL_ANGLE_create_context_backwards_compatible 1
#define EGL_CONTEXT_OPENGL_BACKWARDS_COMPATIBLE_ANGLE 0x3483
#endif
#ifndef EGL_CONTEXT_PRIORITY_LEVEL_IMG
#define EGL_CONTEXT_PRIORITY_LEVEL_IMG 0x3100
#define EGL_CONTEXT_PRIORITY_HIGH_IMG 0x3101
#define EGL_CONTEXT_PRIORITY_MEDIUM_IMG 0x3102
#define EGL_CONTEXT_PRIORITY_LOW_IMG 0x3103
#endif
#ifndef EGL_ANGLE_power_preference
#define EGL_ANGLE_power_preference 1
#define EGL_POWER_PREFERENCE_ANGLE 0x3482
#define EGL_LOW_POWER_ANGLE 0x0001
#define EGL_HIGH_POWER_ANGLE 0x0002
#endif
#ifndef EGL_NV_robustness_video_memory_purge
#define EGL_NV_robustness_video_memory_purge 1
#define EGL_GENERATE_RESET_ON_VIDEO_MEMORY_PURGE_NV 0x334C
#endif
#ifndef EGL_ANGLE_context_virtualization
#define EGL_ANGLE_context_virtualization 1
#define EGL_CONTEXT_VIRTUALIZATION_GROUP_ANGLE 0x3481
#endif
using ui::GetEGLErrorString;
using ui::GetLastEGLErrorString;
namespace gl {
namespace {
bool ChangeContextAttributes(std::vector<EGLint>& context_attributes,
EGLint attribute,
EGLint value) {
auto iter = base::ranges::find(context_attributes, attribute);
if (iter != context_attributes.end()) {
++iter;
if (iter != context_attributes.end()) {
*iter = value;
return true;
}
}
return false;
}
}
GLContextEGL::GLContextEGL(GLShareGroup* share_group)
: GLContextReal(share_group) {}
bool GLContextEGL::Initialize(GLSurface* compatible_surface,
const GLContextAttribs& attribs) {
DCHECK(compatible_surface);
DCHECK(!context_);
gl_display_ = static_cast<GLDisplayEGL*>(compatible_surface->GetGLDisplay());
DCHECK(gl_display_);
EGLint context_client_major_version = attribs.client_major_es_version;
EGLint context_client_minor_version = attribs.client_minor_es_version;
if (!gl_display_->ext->b_EGL_KHR_no_config_context) {
config_ = compatible_surface->GetConfig();
EGLint config_renderable_type = 0;
if (!eglGetConfigAttrib(gl_display_->GetDisplay(), config_,
EGL_RENDERABLE_TYPE, &config_renderable_type)) {
LOG(ERROR) << "eglGetConfigAttrib failed with error "
<< GetLastEGLErrorString();
return false;
}
if ((config_renderable_type & EGL_OPENGL_ES3_BIT) == 0 &&
context_client_major_version >= 3) {
context_client_major_version = 2;
context_client_minor_version = 0;
}
}
std::vector<EGLint> context_attributes;
if (attribs.can_skip_validation &&
GetGLImplementation() == kGLImplementationEGLANGLE) {
context_attributes.push_back(EGL_CONTEXT_OPENGL_NO_ERROR_KHR);
context_attributes.push_back(EGL_TRUE);
}
if (gl_display_->ext->b_EGL_KHR_create_context) {
context_attributes.push_back(EGL_CONTEXT_MAJOR_VERSION);
context_attributes.push_back(context_client_major_version);
context_attributes.push_back(EGL_CONTEXT_MINOR_VERSION);
context_attributes.push_back(context_client_minor_version);
} else {
context_attributes.push_back(EGL_CONTEXT_CLIENT_VERSION);
context_attributes.push_back(context_client_major_version);
DCHECK(context_client_minor_version == 0);
}
bool is_swangle = IsSoftwareGLImplementation(GetGLImplementationParts());
#if BUILDFLAG(IS_MAC)
if (is_swangle && attribs.webgl_compatibility_context &&
base::mac::GetCPUType() == base::mac::CPUType::kArm) {
DVLOG(1) << __FUNCTION__
<< ": Software WebGL contexts are not supported on ARM MacOS.";
return false;
}
#elif BUILDFLAG(IS_IOS)
if (is_swangle && attribs.webgl_compatibility_context) {
DVLOG(1) << __FUNCTION__
<< ": Software WebGL contexts are not supported on iOS.";
return false;
}
#endif
if (gl_display_->ext->b_EGL_EXT_create_context_robustness || is_swangle) {
DVLOG(1) << "EGL_EXT_create_context_robustness supported.";
context_attributes.push_back(EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT);
context_attributes.push_back(
(attribs.robust_buffer_access || is_swangle) ? EGL_TRUE : EGL_FALSE);
if (attribs.lose_context_on_reset) {
context_attributes.push_back(
EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT);
context_attributes.push_back(EGL_LOSE_CONTEXT_ON_RESET_EXT);
if (gl_display_->ext->b_EGL_NV_robustness_video_memory_purge) {
context_attributes.push_back(
EGL_GENERATE_RESET_ON_VIDEO_MEMORY_PURGE_NV);
context_attributes.push_back(EGL_TRUE);
}
}
} else {
DVLOG(1) << "EGL_EXT_create_context_robustness NOT supported.";
}
if (!eglBindAPI(EGL_OPENGL_ES_API)) {
LOG(ERROR) << "eglBindApi failed with error "
<< GetLastEGLErrorString();
return false;
}
if (gl_display_->ext->b_EGL_CHROMIUM_create_context_bind_generates_resource) {
context_attributes.push_back(EGL_CONTEXT_BIND_GENERATES_RESOURCE_CHROMIUM);
context_attributes.push_back(attribs.bind_generates_resource ? EGL_TRUE
: EGL_FALSE);
} else {
DCHECK(attribs.bind_generates_resource);
}
if (gl_display_->ext->b_EGL_ANGLE_create_context_webgl_compatibility) {
context_attributes.push_back(EGL_CONTEXT_WEBGL_COMPATIBILITY_ANGLE);
context_attributes.push_back(
attribs.webgl_compatibility_context ? EGL_TRUE : EGL_FALSE);
} else {
DCHECK(!attribs.webgl_compatibility_context);
}
if (gl_display_->IsEGLContextPrioritySupported()) {
if (attribs.context_priority == ContextPriorityLow) {
DVLOG(1) << __FUNCTION__ << ": setting ContextPriorityLow";
context_attributes.push_back(EGL_CONTEXT_PRIORITY_LEVEL_IMG);
context_attributes.push_back(EGL_CONTEXT_PRIORITY_LOW_IMG);
} else if (attribs.context_priority == ContextPriorityHigh) {
DVLOG(1) << __FUNCTION__ << ": setting ContextPriorityHigh";
context_attributes.push_back(EGL_CONTEXT_PRIORITY_LEVEL_IMG);
context_attributes.push_back(EGL_CONTEXT_PRIORITY_HIGH_IMG);
}
}
if (gl_display_->ext->b_EGL_ANGLE_display_texture_share_group) {
context_attributes.push_back(EGL_DISPLAY_TEXTURE_SHARE_GROUP_ANGLE);
context_attributes.push_back(
attribs.global_texture_share_group ? EGL_TRUE : EGL_FALSE);
} else {
DCHECK(!attribs.global_texture_share_group);
}
if (gl_display_->ext->b_EGL_ANGLE_display_semaphore_share_group) {
context_attributes.push_back(EGL_DISPLAY_SEMAPHORE_SHARE_GROUP_ANGLE);
context_attributes.push_back(
attribs.global_semaphore_share_group ? EGL_TRUE : EGL_FALSE);
} else {
DCHECK(!attribs.global_semaphore_share_group);
}
if (gl_display_->ext->b_EGL_ANGLE_create_context_client_arrays) {
context_attributes.push_back(EGL_CONTEXT_CLIENT_ARRAYS_ENABLED_ANGLE);
context_attributes.push_back(
attribs.angle_create_context_client_arrays ? EGL_TRUE : EGL_FALSE);
} else {
DCHECK(!attribs.angle_create_context_client_arrays);
}
if (gl_display_->ext->b_EGL_ANGLE_robust_resource_initialization ||
is_swangle) {
context_attributes.push_back(EGL_ROBUST_RESOURCE_INITIALIZATION_ANGLE);
context_attributes.push_back(
(attribs.robust_resource_initialization || is_swangle) ? EGL_TRUE
: EGL_FALSE);
} else {
DCHECK(!attribs.robust_resource_initialization);
}
if (gl_display_->ext->b_EGL_ANGLE_create_context_backwards_compatible) {
context_attributes.push_back(EGL_CONTEXT_OPENGL_BACKWARDS_COMPATIBLE_ANGLE);
context_attributes.push_back(EGL_FALSE);
}
if (gl_display_->ext->b_EGL_ANGLE_power_preference) {
GpuPreference pref = attribs.gpu_preference;
pref = GLSurface::AdjustGpuPreference(pref);
switch (pref) {
case GpuPreference::kDefault:
break;
case GpuPreference::kLowPower:
context_attributes.push_back(EGL_POWER_PREFERENCE_ANGLE);
context_attributes.push_back(EGL_LOW_POWER_ANGLE);
break;
case GpuPreference::kHighPerformance:
context_attributes.push_back(EGL_POWER_PREFERENCE_ANGLE);
context_attributes.push_back(EGL_HIGH_POWER_ANGLE);
break;
default:
NOTREACHED();
}
}
if (gl_display_->ext->b_EGL_ANGLE_external_context_and_surface) {
if (attribs.angle_create_from_external_context) {
context_attributes.push_back(EGL_EXTERNAL_CONTEXT_ANGLE);
context_attributes.push_back(EGL_TRUE);
}
if (attribs.angle_restore_external_context_state) {
context_attributes.push_back(EGL_EXTERNAL_CONTEXT_SAVE_STATE_ANGLE);
context_attributes.push_back(EGL_TRUE);
}
}
if (gl_display_->ext->b_EGL_ANGLE_context_virtualization) {
context_attributes.push_back(EGL_CONTEXT_VIRTUALIZATION_GROUP_ANGLE);
context_attributes.push_back(
static_cast<EGLint>(attribs.angle_context_virtualization_group_number));
}
context_attributes.push_back(EGL_NONE);
context_attributes.push_back(EGL_NONE);
context_ =
eglCreateContext(gl_display_->GetDisplay(), config_,
share_group() ? share_group()->GetHandle() : nullptr,
context_attributes.data());
if (context_) {
return true;
}
GLint error = eglGetError();
if (gl_display_->ext->b_EGL_KHR_no_config_context &&
(error == EGL_BAD_MATCH || error == EGL_BAD_ATTRIBUTE)) {
std::vector<std::pair<EGLint, EGLint>> candidate_versions;
if (context_client_major_version == 3 &&
context_client_minor_version == 1) {
candidate_versions.emplace_back(3, 0);
candidate_versions.emplace_back(2, 0);
} else if (context_client_major_version == 3 &&
context_client_minor_version == 0) {
candidate_versions.emplace_back(2, 0);
}
for (const auto& version : candidate_versions) {
if (!ChangeContextAttributes(context_attributes,
EGL_CONTEXT_MAJOR_VERSION, version.first) ||
!ChangeContextAttributes(context_attributes,
EGL_CONTEXT_MINOR_VERSION, version.second)) {
break;
}
context_ =
eglCreateContext(gl_display_->GetDisplay(), config_,
share_group() ? share_group()->GetHandle() : nullptr,
context_attributes.data());
if (context_) {
return true;
} else {
error = eglGetError();
}
}
}
LOG(ERROR) << "eglCreateContext failed with error "
<< GetEGLErrorString(error);
return false;
}
void GLContextEGL::Destroy() {
ReleaseBackpressureFences();
if (context_) {
if (!eglDestroyContext(gl_display_->GetDisplay(), context_)) {
LOG(ERROR) << "eglDestroyContext failed with error "
<< GetLastEGLErrorString();
}
context_ = nullptr;
}
}
void GLContextEGL::SetVisibility(bool visibility) {
if (gl_display_->ext->b_EGL_ANGLE_power_preference) {
if (visibility) {
eglReacquireHighPowerGPUANGLE(gl_display_->GetDisplay(), context_);
} else {
eglReleaseHighPowerGPUANGLE(gl_display_->GetDisplay(), context_);
}
}
}
GLDisplayEGL* GLContextEGL::GetGLDisplayEGL() {
return gl_display_;
}
void GLContextEGL::ReleaseBackpressureFences() {
#if BUILDFLAG(IS_APPLE)
bool has_backpressure_fences = HasBackpressureFences();
#else
bool has_backpressure_fences = false;
#endif
if (has_backpressure_fences) {
GLContext* current_context = GetRealCurrent();
if (current_context != this) {
SetCurrentGL(GetCurrentGL());
}
EGLContext current_egl_context = eglGetCurrentContext();
EGLSurface current_draw_surface = EGL_NO_SURFACE;
EGLSurface current_read_surface = EGL_NO_SURFACE;
if (context_ != current_egl_context) {
current_draw_surface = eglGetCurrentSurface(EGL_DRAW);
current_read_surface = eglGetCurrentSurface(EGL_READ);
if (!eglMakeCurrent(gl_display_->GetDisplay(), EGL_NO_SURFACE,
EGL_NO_SURFACE, context_)) {
LOG(ERROR) << "eglMakeCurrent failed with error "
<< GetLastEGLErrorString();
}
}
#if BUILDFLAG(IS_APPLE)
DestroyBackpressureFences();
#endif
if (current_context && current_context != this) {
SetCurrentGL(current_context->GetCurrentGL());
}
if (context_ != current_egl_context) {
if (!eglMakeCurrent(gl_display_->GetDisplay(), current_draw_surface,
current_read_surface, current_egl_context)) {
LOG(ERROR) << "eglMakeCurrent failed with error "
<< GetLastEGLErrorString();
}
}
}
}
bool GLContextEGL::MakeCurrentImpl(GLSurface* surface) {
DCHECK(context_);
if (lost_) {
LOG(ERROR) << "Failed to make context current since it is marked as lost";
return false;
}
if (IsCurrent(surface))
return true;
ScopedReleaseCurrent release_current;
TRACE_EVENT2("gpu", "GLContextEGL::MakeCurrent", "context",
static_cast<void*>(context_), "surface",
static_cast<void*>(surface));
if (unbind_fbo_on_makecurrent_ && GetCurrent()) {
glBindFramebufferEXT(GL_FRAMEBUFFER, 0);
}
if (!eglMakeCurrent(gl_display_->GetDisplay(), surface->GetHandle(),
surface->GetHandle(), context_)) {
LOG(ERROR) << "eglMakeCurrent failed with error "
<< GetLastEGLErrorString();
return false;
}
BindGLApi();
SetCurrent(surface);
InitializeDynamicBindings();
if (!surface->OnMakeCurrent(this)) {
LOG(ERROR) << "Could not make current.";
return false;
}
release_current.Cancel();
return true;
}
void GLContextEGL::SetUnbindFboOnMakeCurrent() {
unbind_fbo_on_makecurrent_ = true;
}
void GLContextEGL::ReleaseCurrent(GLSurface* surface) {
if (!IsCurrent(surface))
return;
if (unbind_fbo_on_makecurrent_)
glBindFramebufferEXT(GL_FRAMEBUFFER, 0);
SetCurrent(nullptr);
if (!eglMakeCurrent(gl_display_->GetDisplay(), EGL_NO_SURFACE, EGL_NO_SURFACE,
EGL_NO_CONTEXT)) {
LOG(ERROR) << "eglMakeCurrent failed to release current with error "
<< GetLastEGLErrorString();
lost_ = true;
}
DCHECK(!IsCurrent(nullptr));
}
bool GLContextEGL::IsCurrent(GLSurface* surface) {
DCHECK(context_);
if (lost_)
return false;
bool native_context_is_current = context_ == eglGetCurrentContext();
DCHECK(!native_context_is_current || (GetRealCurrent() == this));
if (!native_context_is_current)
return false;
if (surface) {
if (surface->GetHandle() != eglGetCurrentSurface(EGL_DRAW))
return false;
}
if (gl_display_) {
if (gl_display_->GetDisplay() != eglGetCurrentDisplay()) {
return false;
}
}
return true;
}
void* GLContextEGL::GetHandle() {
return context_;
}
unsigned int GLContextEGL::CheckStickyGraphicsResetStatusImpl() {
DCHECK(IsCurrent(nullptr));
DCHECK(g_current_gl_driver);
const ExtensionsGL& ext = g_current_gl_driver->ext;
if ((graphics_reset_status_ == GL_NO_ERROR) &&
gl_display_->ext->b_EGL_EXT_create_context_robustness &&
(ext.b_GL_KHR_robustness || ext.b_GL_EXT_robustness ||
ext.b_GL_ARB_robustness)) {
graphics_reset_status_ = glGetGraphicsResetStatusARB();
}
return graphics_reset_status_;
}
GLContextEGL::~GLContextEGL() {
Destroy();
}
}