[Feature] (EGL+GL): an opt-in OpenGL ES identity for ES contexts with BGRA textures, and contexts take the dispatcher's API, so Chrome's GPU process runs on ANGLE.

This commit is contained in:
2026-10-03 05:21:52 -04:00
parent e5f097f9e1
commit 30977c181f
8 changed files with 219 additions and 3 deletions
+10
View File
@@ -48,6 +48,8 @@
namespace MobileGL::MG_Impl::EGLImpl {
namespace {
// SetDispatcherCurrentApiHook.
std::atomic<EGLenum (*)()> g_dispatcherCurrentApi{nullptr};
using EGLStateContext = MG_State::EGLState::EGLContext;
EGLStateContext* GetState() {
@@ -674,6 +676,12 @@ namespace MobileGL::MG_Impl::EGLImpl {
if (!state) {
return EGL_NO_CONTEXT;
}
// The API the context is for, as the dispatcher reports it (SetDispatcherCurrentApiHook):
// an ES context made without an eglBindAPI - or with one of the default - is an ES context.
if (const auto currentApi = g_dispatcherCurrentApi.load()) {
const EGLenum api = currentApi();
if (api == EGL_OPENGL_API || api == EGL_OPENGL_ES_API) state->SetBoundAPI(api);
}
#if MOBILEGL_BUILD_DISAGGREGATED
// AFTER A DEVICE LOSS THIS IS WHERE AN APPLICATION REBUILDS, so this is where a fresh
// session comes up (RecoverLostSession) - before the context's GL state is built, because
@@ -1416,6 +1424,8 @@ namespace MobileGL::MG_Impl::EGLImpl {
return state->GetSyncAttrib(dpy, sync, attribute, value) ? EGL_TRUE : EGL_FALSE;
}
void SetDispatcherCurrentApiHook(EGLenum (*hook)()) { g_dispatcherCurrentApi.store(hook); }
Bool SharedImagesAvailable() {
#if MOBILEGL_BUILD_DISAGGREGATED
// A split client's backend is the remote one, which forwards the shared-image verbs to
+5
View File
@@ -66,6 +66,11 @@ namespace MobileGL::MG_Impl::EGLImpl {
// a split client's does, a monolith's does not. Decides the dma-buf EGL extensions, the
// Wayland linux-dmabuf presentation and GL_OES_EGL_image.
Bool SharedImagesAvailable();
// Under a dispatching libEGL the dispatcher, not this library, owns eglBindAPI's state: it does
// not forward a bind of the API it already reports (EGL_OPENGL_ES_API, EGL's default), so a
// context would otherwise be created for whatever this library assumed. The glvnd vendor
// installs the dispatcher's own answer here; eglCreateContext asks it.
void SetDispatcherCurrentApiHook(EGLenum (*hook)());
// The shared image an EGLImage names (EGL_LINUX_DMA_BUF_EXT), for glEGLImageTargetTexture2DOES.
Bool LookupSharedImage(EGLImage image, Uint64* id, EGLint* width, EGLint* height);
// glEGLImageTargetTexture2DOES bound a shared image to a texture: from now on glFlush/glFinish
@@ -352,6 +352,12 @@ extern "C" MOBILEGL_EGL_API EGLBoolean __egl_Main(uint32_t version, const __EGLa
return EGL_FALSE;
}
g_eglExports = exports;
if (exports->getCurrentApi != nullptr) {
MobileGL::MG_Impl::EGLImpl::SetDispatcherCurrentApiHook([]() -> EGLenum {
return g_eglExports != nullptr && g_eglExports->getCurrentApi != nullptr ? g_eglExports->getCurrentApi()
: EGL_NONE;
});
}
imports->getPlatformDisplay = VendorGetPlatformDisplay;
imports->getSupportsAPI = VendorGetSupportsAPI;
+40 -3
View File
@@ -603,12 +603,26 @@ namespace MobileGL::MG_Impl::GLImpl {
// Robust buffer ACCESS is a separate promise (GL_CONTEXT_ROBUST_ACCESS) and is not
// implied by any of them.
// One list for glGetString and glGetStringi, so the two forms always agree.
Bool EsIdentityActive();
const Vector<const char*>& ClientSideExtensions() {
static const Vector<const char*> withImages = {"GL_OES_EGL_image", "GL_KHR_robustness",
"GL_ARB_robustness", "GL_EXT_robustness"};
static const Vector<const char*> withoutImages = {"GL_KHR_robustness", "GL_ARB_robustness",
"GL_EXT_robustness"};
return MG_Impl::EGLImpl::SharedImagesAvailable() ? withImages : withoutImages;
// Under the ES identity (EsIdentityActive) the ES names of what desktop GL has in core:
// BGRA textures, render targets and read-backs. GL_BGRA_EXT / GL_BGRA8_EXT as internal
// formats are taken as RGBA storage (TextureEnumConverter), the byte order being the
// pixel transfer format's, as on desktop GL.
static const Vector<const char*> esWithImages = {
"GL_OES_EGL_image", "GL_KHR_robustness", "GL_ARB_robustness",
"GL_EXT_robustness", "GL_EXT_texture_format_BGRA8888", "GL_EXT_read_format_bgra"};
static const Vector<const char*> esWithoutImages = {"GL_KHR_robustness", "GL_ARB_robustness",
"GL_EXT_robustness", "GL_EXT_texture_format_BGRA8888",
"GL_EXT_read_format_bgra"};
const Bool images = MG_Impl::EGLImpl::SharedImagesAvailable();
if (EsIdentityActive()) return images ? esWithImages : esWithoutImages;
return images ? withImages : withoutImages;
}
// Robust buffer access as GL 4.5 / KHR_robust_buffer_access_behavior define it: the backend
@@ -624,6 +638,22 @@ namespace MobileGL::MG_Impl::GLImpl {
extensions.end();
}
// MOBILEGL_ES_CONTEXT_IDENTITY=1: a context created for the OpenGL ES API names itself OpenGL
// ES 3.2 (GL_VERSION, GL_SHADING_LANGUAGE_VERSION, GL_MAJOR/MINOR_VERSION) instead of the
// desktop version every context reports by default. Opt-in, for an application that
// decides what it may use from that identity: ANGLE's GL-on-EGL backend offers
// GL_OES_EGL_image - which Chrome's GPU process presents through - only over a native
// driver that calls itself OpenGL ES, and reads the desktop identity's extension names
// otherwise.
Bool EsIdentityActive() {
static const Bool requested = [] {
const char* value = std::getenv("MOBILEGL_ES_CONTEXT_IDENTITY");
return value != nullptr && value[0] == '1';
}();
return requested && MG_State::pEGLContext &&
MG_State::pEGLContext->GetCurrentContextClientAPI() == EGL_OPENGL_ES_API;
}
Bool CurrentContextHasRobustAccess() {
return MG_State::pEGLContext && MG_State::pEGLContext->IsCurrentContextRobustAccessRequested() &&
BackendProvidesRobustBufferAccess();
@@ -683,6 +713,12 @@ namespace MobileGL::MG_Impl::GLImpl {
if (const auto at = backendName.find("OpenGL ES"); at != String::npos) {
backendName.replace(at, sizeof("OpenGL ES") - 1, "GLES");
}
if (EsIdentityActive()) {
versionStr = std::format("OpenGL ES 3.2 {} {}, {} Backend, GIT@" GIT_COMMIT_HASH_SHORT, MG_Config::ProjectName,
MG_Config::CoreVersion.toFormattedString(MG_Config::DefaultVersionStringFormatAttrib),
backendName);
return (const GLubyte*)versionStr.c_str();
}
versionStr =
std::format("{} {} {}, {} Backend, GIT@" GIT_COMMIT_HASH_SHORT,
rendererInfo.RendererGLInfo.TargetGLVersion.toString(), MG_Config::ProjectName,
@@ -699,6 +735,7 @@ namespace MobileGL::MG_Impl::GLImpl {
return (const GLubyte*)rendererString.c_str();
}
case GL_SHADING_LANGUAGE_VERSION:
if (EsIdentityActive()) return (const GLubyte*)"OpenGL ES GLSL ES 3.20";
shadingLanguageVersion =
std::format("{} {}", rendererInfo.RendererGLInfo.TargetGLSLVersion.toString({true, false}),
MG_Config::ProjectName);
@@ -2583,7 +2620,7 @@ namespace MobileGL::MG_Impl::GLImpl {
GetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_SIZE, 2, &params[2]);
break;
case GL_MAJOR_VERSION:
*params = rendererInfo.RendererGLInfo.TargetGLVersion.Major;
*params = EsIdentityActive() ? 3 : rendererInfo.RendererGLInfo.TargetGLVersion.Major;
break;
case GL_MAX_3D_TEXTURE_SIZE:
*params = dynamicParameters.Max3DTextureSize;
@@ -2870,7 +2907,7 @@ namespace MobileGL::MG_Impl::GLImpl {
*params = static_cast<GLint>(RenderStateParameters::MAX_VIEWPORTS);
break;
case GL_MINOR_VERSION:
*params = rendererInfo.RendererGLInfo.TargetGLVersion.Minor;
*params = EsIdentityActive() ? 2 : rendererInfo.RendererGLInfo.TargetGLVersion.Minor;
break;
case GL_NUM_EXTENSIONS:
*params = static_cast<Int>(rendererInfo.RendererGLInfo.Extensions.size() + ClientSideExtensions().size());
+10
View File
@@ -956,6 +956,16 @@ namespace MobileGL {
return ctx ? ctx->ResetNotificationStrategy : EGL_NO_RESET_NOTIFICATION;
}
EGLenum EGLContext::GetCurrentContextClientAPI() const {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
auto currentIt = m_threadCurrents.find(CurrentThreadKey());
if (currentIt == m_threadCurrents.end()) {
return EGL_OPENGL_API;
}
const auto* ctx = TryGetContext(currentIt->second.Context);
return ctx ? ctx->ClientAPI : EGL_OPENGL_API;
}
Bool EGLContext::IsCurrentContextRobustAccessRequested() const {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
auto currentIt = m_threadCurrents.find(CurrentThreadKey());
+3
View File
@@ -74,6 +74,9 @@ namespace MobileGL {
// robust buffer access.
EGLint GetCurrentContextResetNotificationStrategy() const;
Bool IsCurrentContextRobustAccessRequested() const;
// The client API the calling thread's current context was created for
// (EGL_OPENGL_API with no context current).
EGLenum GetCurrentContextClientAPI() const;
// P14 S1. The wire identity of a context, and of the context the CALLING THREAD
// has current (0 when it has none - the release edge's value, and the value a
@@ -44,6 +44,7 @@
#include <mutex>
extern "C" {
void glTexStorage2D(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height);
const GLubyte* glGetString(GLenum name);
const GLubyte* glGetStringi(GLenum name, GLuint index);
void glGetIntegerv(GLenum pname, GLint* data);
@@ -922,6 +923,102 @@ namespace {
}
};
// THE ES IDENTITY (MOBILEGL_ES_CONTEXT_IDENTITY=1), what Chrome's GPU process runs with: ANGLE
// offers GL_OES_EGL_image - the GPU process's dma-buf images - only over a native driver that
// calls itself OpenGL ES, and then creates BGRA textures with the ES spelling. And the API a
// dispatching libEGL reports wins over the one this library was told (glvnd never forwards a
// bind of the API it already reports, EGL's default ES).
struct EsIdentityReport {
Int32 initialized = 0;
Int32 dispatcherApiWins = -1;
char esVersion[96] = {};
char esGlsl[64] = {};
Int32 esMajor = 0;
Int32 esMinor = 0;
Int32 bgraAdvertised = -1;
Int32 bgraIndexed = -1;
Uint32 bgraImageError = 0xFFFFFFFFu;
Uint32 bgraStorageError = 0xFFFFFFFFu;
Uint32 bgraFboStatus = 0;
Uint32 bgraPixel = 0;
char desktopVersion[96] = {};
char note[256] = {};
};
void Note(EsIdentityReport& r, const char* text) {
if (r.note[0] == '\0') std::snprintf(r.note, sizeof(r.note), "%s", text);
}
EGLenum g_dispatcherApi = EGL_OPENGL_ES_API;
void RunEsIdentityClient(EsIdentityReport& r) {
::setenv("MOBILEGL_ES_CONTEXT_IDENTITY", "1", 1);
Egl egl;
RecoveryReport bring{};
if (!egl.Bring(bring)) return Note(r, bring.note);
// The dispatcher says ES although this library was told GL last (Bring binds the GL API).
EGL::SetDispatcherCurrentApiHook([]() -> EGLenum { return g_dispatcherApi; });
const EGLContext es = EGL::CreateContext(egl.dpy, egl.config, EGL_NO_CONTEXT, kAngleNativeAttribs);
g_dispatcherApi = EGL_OPENGL_API;
const EGLContext desktop = EGL::CreateContext(egl.dpy, egl.config, EGL_NO_CONTEXT, kRobustAttribs);
EGL::SetDispatcherCurrentApiHook(nullptr);
if (es == EGL_NO_CONTEXT || desktop == EGL_NO_CONTEXT) return Note(r, "the contexts were not created");
EGLint type = 0;
(void)EGL::QueryContext(egl.dpy, es, EGL_CONTEXT_CLIENT_TYPE, &type);
r.dispatcherApiWins = type == EGL_OPENGL_ES_API ? 1 : 0;
if (EGL::MakeCurrent(egl.dpy, egl.surface, egl.surface, es) != EGL_TRUE)
return Note(r, "the ES context did not go current");
r.initialized = 1;
std::snprintf(r.esVersion, sizeof(r.esVersion), "%s", reinterpret_cast<const char*>(glGetString(GL_VERSION)));
std::snprintf(r.esGlsl, sizeof(r.esGlsl), "%s",
reinterpret_cast<const char*>(glGetString(GL_SHADING_LANGUAGE_VERSION)));
glGetIntegerv(GL_MAJOR_VERSION, &r.esMajor);
glGetIntegerv(GL_MINOR_VERSION, &r.esMinor);
r.bgraAdvertised = HasToken(reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS)),
"GL_EXT_texture_format_BGRA8888")
? 1
: 0;
GLint count = 0;
glGetIntegerv(GL_NUM_EXTENSIONS, &count);
r.bgraIndexed = 0;
for (GLint i = 0; i < count; ++i) {
const auto* name = reinterpret_cast<const char*>(glGetStringi(GL_EXTENSIONS, static_cast<GLuint>(i)));
if (name != nullptr && std::string(name) == "GL_EXT_texture_format_BGRA8888") r.bgraIndexed = 1;
}
while (glGetError() != GL_NO_ERROR) {
}
// BGRA as EXT_texture_format_BGRA8888 spells it: GL_BGRA_EXT as the internal format, and
// GL_BGRA8_EXT through glTexStorage2D; a BGRA texel reads back in RGBA order.
const Uint8 bgra[4] = {0x10, 0x20, 0x30, 0xFF}; // B, G, R, A
GLuint textures[2] = {};
glGenTextures(2, textures);
glBindTexture(GL_TEXTURE_2D, textures[0]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_BGRA, 1, 1, 0, GL_BGRA, GL_UNSIGNED_BYTE, bgra);
r.bgraImageError = glGetError();
glBindTexture(GL_TEXTURE_2D, textures[1]);
glTexStorage2D(GL_TEXTURE_2D, 1, 0x93A1 /* GL_BGRA8_EXT */, 1, 1);
r.bgraStorageError = glGetError();
GLuint framebuffer = 0;
glGenFramebuffers(1, &framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textures[0], 0);
r.bgraFboStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER);
r.bgraPixel = ReadPixel();
glBindFramebuffer(GL_FRAMEBUFFER, 0);
(void)EGL::MakeCurrent(egl.dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
if (EGL::MakeCurrent(egl.dpy, egl.surface, egl.surface, desktop) == EGL_TRUE) {
std::snprintf(r.desktopVersion, sizeof(r.desktopVersion), "%s",
reinterpret_cast<const char*>(glGetString(GL_VERSION)));
(void)EGL::MakeCurrent(egl.dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
}
(void)EGL::DestroyContext(egl.dpy, es);
(void)EGL::DestroyContext(egl.dpy, desktop);
(void)EGL::DestroySurface(egl.dpy, egl.surface);
(void)EGL::Terminate(egl.dpy);
}
RecoveryReport RunClient(const ServerProcess& server, Route route, const std::string& clientLogBase,
EGLenum api = EGL_OPENGL_API, const EGLint* attribs = kRobustAttribs) {
return RunInClient<RecoveryReport>(server, clientLogBase, [route, api, attribs](RecoveryReport& r) {
@@ -1219,3 +1316,45 @@ TEST_F(ClientRecovery, ThePidTargetedKnobLosesOnlyThatClientAndOnlyOnce) {
EXPECT_EQ(Count(log, "reaped exit=0"), 2u) << log;
server.Stop();
}
// THE ES IDENTITY (RunEsIdentityClient): an ES context names itself OpenGL ES 3.2 and offers BGRA
// the ES way, a desktop context of the same process does not change, and the dispatcher's API wins.
TEST_F(ClientRecovery, AnEsContextUnderTheEsIdentityIsOpenGlEs32WithBgraTextures) {
ServerProcess server;
ASSERT_TRUE(LaunchUnixInProcessServer(&server, "esid")) << server.Log();
ClientLog client("esid");
const EsIdentityReport r = RunInClient<EsIdentityReport>(server, client.base, &RunEsIdentityClient);
const std::string diagnostics = "server:\n" + server.Log() + "\nclient:\n" + client.Text();
EXPECT_EQ(r.dispatcherApiWins, 1) << "the context was not created for the API the dispatcher reports";
ASSERT_EQ(r.initialized, 1) << r.note << "\n" << diagnostics;
EXPECT_EQ(std::string(r.esVersion).rfind("OpenGL ES 3.2 ", 0), 0u) << r.esVersion;
EXPECT_STREQ(r.esGlsl, "OpenGL ES GLSL ES 3.20");
EXPECT_EQ(r.esMajor, 3);
EXPECT_EQ(r.esMinor, 2);
EXPECT_EQ(r.bgraAdvertised, 1);
EXPECT_EQ(r.bgraIndexed, 1);
EXPECT_EQ(r.bgraImageError, static_cast<Uint32>(GL_NO_ERROR)) << std::hex << r.bgraImageError;
EXPECT_EQ(r.bgraStorageError, static_cast<Uint32>(GL_NO_ERROR)) << std::hex << r.bgraStorageError;
EXPECT_EQ(r.bgraFboStatus, static_cast<Uint32>(GL_FRAMEBUFFER_COMPLETE)) << std::hex << r.bgraFboStatus;
EXPECT_EQ(r.bgraPixel, 0xFF102030u) << "a BGRA texel did not read back in RGBA order: " << std::hex << r.bgraPixel;
EXPECT_EQ(std::string(r.desktopVersion).find("OpenGL ES"), std::string::npos)
<< "a desktop context took the ES identity: " << r.desktopVersion;
EXPECT_NE(std::string(r.desktopVersion), "") << diagnostics;
server.Stop();
}
// Chrome's GPU process's own shape: ANGLE's native ES context, under the ES identity, told of the
// loss and recovering like any other.
TEST_F(ClientRecovery, AnglesNativeContextUnderTheEsIdentityRecovers) {
ServerProcess server;
ASSERT_TRUE(Launch(&server, "angle-es")) << server.Log();
ClientLog client("angle-es");
const RecoveryReport r = RunInClient<RecoveryReport>(server, client.base, [](RecoveryReport& report) {
::setenv("MOBILEGL_ES_CONTEXT_IDENTITY", "1", 1);
RunRecoveringClient(Route::NewContext, report, EGL_OPENGL_ES_API, kAngleNativeAttribs);
});
const std::string diagnostics = "server:\n" + server.Log() + "\nclient:\n" + client.Text();
ExpectLossReportedAndRecovered(r, diagnostics);
ExpectOneLossAndACleanSecondSession(server);
server.Stop();
}
@@ -153,6 +153,9 @@ namespace MobileGL {
return TextureInternalFormat::RGBA4;
case GL_RGB5_A1:
return TextureInternalFormat::RGB5A1;
// GL_BGRA8_EXT (EXT_texture_storage with EXT_texture_format_BGRA8888): stored as RGBA8;
// the BGRA byte order is the pixel transfer format's business, as on desktop GL.
case 0x93A1:
case GL_RGBA8:
return TextureInternalFormat::RGBA8;
case GL_RGBA8_SNORM:
@@ -330,6 +333,9 @@ namespace MobileGL {
return TextureInternalFormat::RG;
case GL_RGB:
return TextureInternalFormat::RGB;
// GL_BGRA_EXT as an unsized internal format (EXT_texture_format_BGRA8888, an ES
// application's spelling): the same storage as GL_RGBA.
case GL_BGRA:
case GL_RGBA:
return TextureInternalFormat::RGBA;
default: