From 30977c181fd2f7184daaec6d44bb5de50e30c43b Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sat, 3 Oct 2026 05:21:52 -0400 Subject: [PATCH] [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. --- MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp | 10 ++ MobileGL/MG_Impl/EGLImpl/EGLImpl.h | 5 + .../MG_Impl/EGLImpl/Exporting/GlvndVendor.cpp | 6 + MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp | 43 +++++- MobileGL/MG_State/EGLState/Core.cpp | 10 ++ MobileGL/MG_State/EGLState/Core.h | 3 + MobileGL/MG_Test/Wire/ClientRecoveryTest.cpp | 139 ++++++++++++++++++ .../GLToMG/TextureEnumConverter.cpp | 6 + 8 files changed, 219 insertions(+), 3 deletions(-) diff --git a/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp b/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp index 2a5c8d7a..95b33ef6 100644 --- a/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp +++ b/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp @@ -48,6 +48,8 @@ namespace MobileGL::MG_Impl::EGLImpl { namespace { + // SetDispatcherCurrentApiHook. + std::atomic 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 diff --git a/MobileGL/MG_Impl/EGLImpl/EGLImpl.h b/MobileGL/MG_Impl/EGLImpl/EGLImpl.h index e0a0a5fe..d98cbb89 100644 --- a/MobileGL/MG_Impl/EGLImpl/EGLImpl.h +++ b/MobileGL/MG_Impl/EGLImpl/EGLImpl.h @@ -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 diff --git a/MobileGL/MG_Impl/EGLImpl/Exporting/GlvndVendor.cpp b/MobileGL/MG_Impl/EGLImpl/Exporting/GlvndVendor.cpp index 9e607d3d..15f24fe4 100644 --- a/MobileGL/MG_Impl/EGLImpl/Exporting/GlvndVendor.cpp +++ b/MobileGL/MG_Impl/EGLImpl/Exporting/GlvndVendor.cpp @@ -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; diff --git a/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp b/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp index 4807f77c..4bf33f79 100644 --- a/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp +++ b/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp @@ -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& ClientSideExtensions() { static const Vector withImages = {"GL_OES_EGL_image", "GL_KHR_robustness", "GL_ARB_robustness", "GL_EXT_robustness"}; static const Vector 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 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 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, ¶ms[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(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(rendererInfo.RendererGLInfo.Extensions.size() + ClientSideExtensions().size()); diff --git a/MobileGL/MG_State/EGLState/Core.cpp b/MobileGL/MG_State/EGLState/Core.cpp index f36c75af..a4f91874 100644 --- a/MobileGL/MG_State/EGLState/Core.cpp +++ b/MobileGL/MG_State/EGLState/Core.cpp @@ -956,6 +956,16 @@ namespace MobileGL { return ctx ? ctx->ResetNotificationStrategy : EGL_NO_RESET_NOTIFICATION; } + EGLenum EGLContext::GetCurrentContextClientAPI() const { + const std::lock_guard 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 lock(m_mutex); auto currentIt = m_threadCurrents.find(CurrentThreadKey()); diff --git a/MobileGL/MG_State/EGLState/Core.h b/MobileGL/MG_State/EGLState/Core.h index 2c594ec6..ac641c0c 100644 --- a/MobileGL/MG_State/EGLState/Core.h +++ b/MobileGL/MG_State/EGLState/Core.h @@ -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 diff --git a/MobileGL/MG_Test/Wire/ClientRecoveryTest.cpp b/MobileGL/MG_Test/Wire/ClientRecoveryTest.cpp index 51673d4e..b915a56c 100644 --- a/MobileGL/MG_Test/Wire/ClientRecoveryTest.cpp +++ b/MobileGL/MG_Test/Wire/ClientRecoveryTest.cpp @@ -44,6 +44,7 @@ #include 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(glGetString(GL_VERSION))); + std::snprintf(r.esGlsl, sizeof(r.esGlsl), "%s", + reinterpret_cast(glGetString(GL_SHADING_LANGUAGE_VERSION))); + glGetIntegerv(GL_MAJOR_VERSION, &r.esMajor); + glGetIntegerv(GL_MINOR_VERSION, &r.esMinor); + r.bgraAdvertised = HasToken(reinterpret_cast(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(glGetStringi(GL_EXTENSIONS, static_cast(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(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(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(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(GL_NO_ERROR)) << std::hex << r.bgraImageError; + EXPECT_EQ(r.bgraStorageError, static_cast(GL_NO_ERROR)) << std::hex << r.bgraStorageError; + EXPECT_EQ(r.bgraFboStatus, static_cast(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(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(); +} diff --git a/MobileGL/MG_Util/Converters/GLToMG/TextureEnumConverter.cpp b/MobileGL/MG_Util/Converters/GLToMG/TextureEnumConverter.cpp index 8d6fb042..ee99c704 100644 --- a/MobileGL/MG_Util/Converters/GLToMG/TextureEnumConverter.cpp +++ b/MobileGL/MG_Util/Converters/GLToMG/TextureEnumConverter.cpp @@ -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: