From 2f1a673513752a5137bb9fab0b202b755ee638aa Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sun, 4 Oct 2026 22:37:25 -0400 Subject: [PATCH] [Fix] (BackendLoader, DirectGLES): check EGL extensions before resolving damage swap entry points --- MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp | 2 + .../BackendLoader/BackendLoaderTest.cpp | 70 ++++++++++++++++--- .../MG_Util/BackendLoaders/OpenGL/Loader.cpp | 49 ++++++------- .../MG_Util/BackendLoaders/OpenGL/Loader.h | 8 +-- 4 files changed, 93 insertions(+), 36 deletions(-) diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index b4ad8cc5..a2f6f4f1 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -17767,6 +17767,8 @@ namespace MobileGL::MG_Backend::DirectGLES { MGLOG_E_ONCE("DirectGLES::MakeCurrent failed: native eglMakeCurrent returned error 0x%04x", error); return false; } + g_EGLFuncs.eglSwapBuffersWithDamageEXT = + MG_Util::BackendLoader::ResolveSwapBuffersWithDamage(g_EGLFuncs); InvalidateEglVerifiedStamp(); t_boundNativeContext = tuple.Context; t_boundNativeDraw = session.Draw; diff --git a/MobileGL/MG_Test/BackendLoader/BackendLoaderTest.cpp b/MobileGL/MG_Test/BackendLoader/BackendLoaderTest.cpp index 716d2f4a..08d85e2e 100644 --- a/MobileGL/MG_Test/BackendLoader/BackendLoaderTest.cpp +++ b/MobileGL/MG_Test/BackendLoader/BackendLoaderTest.cpp @@ -1590,6 +1590,19 @@ TEST(ComputeWorkGroupCapabilities, TakesEveryAxisFromTheIndexedQuery) { } namespace { + const char* g_damageExtensions = nullptr; + EGLDisplay g_damageDisplay = EGL_NO_DISPLAY; + bool g_damageExtAvailable = false; + bool g_damageKhrAvailable = false; + std::vector g_damageQueries; + + EGLDisplay FakeDamageDisplay() { return g_damageDisplay; } + const char* FakeDamageQueryString(EGLDisplay display, EGLint name) { + EXPECT_EQ(display, g_damageDisplay); + EXPECT_EQ(name, EGL_EXTENSIONS); + return g_damageExtensions; + } + EGLBoolean FakeSwapBuffersWithDamage(EGLDisplay dpy, EGLSurface surface, const EGLint* rects, EGLint nRects) { (void)dpy; (void)surface; @@ -1597,6 +1610,14 @@ namespace { (void)nRects; return EGL_TRUE; } + + __eglMustCastToProperFunctionPointerType FakeDamageGetProcAddress(const char* name) { + g_damageQueries.emplace_back(name); + const bool available = std::string(name) == "eglSwapBuffersWithDamageEXT" + ? g_damageExtAvailable : g_damageKhrAvailable; + return available ? reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&FakeSwapBuffersWithDamage) + : nullptr; + } } // namespace // EXT -> KHR -> nothing, the whole policy behind the loader's single damage-present slot. @@ -1609,13 +1630,46 @@ namespace { // call to either spelling is defined to be equivalent to that anyway. TEST(SwapBuffersWithDamageResolution, PrefersExtThenKhrThenAnswersNothing) { using namespace MobileGL; - MG_External::EGL::eglSwapBuffersWithDamageEXT_PTR ext = &FakeSwapBuffersWithDamage; - MG_External::EGL::eglSwapBuffersWithDamageKHR_PTR khr = &FakeSwapBuffersWithDamage; + MG_External::EGLFunctionsTable funcs{}; + funcs.eglGetCurrentDisplay = &FakeDamageDisplay; + funcs.eglQueryString = &FakeDamageQueryString; + funcs.eglGetProcAddress = &FakeDamageGetProcAddress; + g_damageDisplay = reinterpret_cast(1); + g_damageExtensions = "EGL_EXT_swap_buffers_with_damage EGL_KHR_swap_buffers_with_damage"; + g_damageExtAvailable = g_damageKhrAvailable = true; + g_damageQueries.clear(); + EXPECT_EQ(MG_Util::BackendLoader::ResolveSwapBuffersWithDamage(funcs), &FakeSwapBuffersWithDamage); + EXPECT_EQ(g_damageQueries, (std::vector{"eglSwapBuffersWithDamageEXT"})); - EXPECT_EQ(MG_Util::BackendLoader::ResolveSwapBuffersWithDamage(ext, khr), ext) - << "a driver that exports both spellings is answered with the EXT one"; - EXPECT_EQ(MG_Util::BackendLoader::ResolveSwapBuffersWithDamage(nullptr, khr), khr) - << "the KHR spelling alone must still fill the slot"; - EXPECT_EQ(MG_Util::BackendLoader::ResolveSwapBuffersWithDamage(nullptr, nullptr), nullptr) - << "neither spelling stays null, so the caller keeps eglSwapBuffers"; + g_damageExtAvailable = false; + g_damageQueries.clear(); + EXPECT_EQ(MG_Util::BackendLoader::ResolveSwapBuffersWithDamage(funcs), &FakeSwapBuffersWithDamage); + EXPECT_EQ(g_damageQueries, (std::vector{"eglSwapBuffersWithDamageEXT", "eglSwapBuffersWithDamageKHR"})); + + g_damageExtensions = "EGL_KHR_swap_buffers_with_damage"; + g_damageQueries.clear(); + EXPECT_EQ(MG_Util::BackendLoader::ResolveSwapBuffersWithDamage(funcs), &FakeSwapBuffersWithDamage); + EXPECT_EQ(g_damageQueries, (std::vector{"eglSwapBuffersWithDamageKHR"})); + + g_damageKhrAvailable = false; + EXPECT_EQ(MG_Util::BackendLoader::ResolveSwapBuffersWithDamage(funcs), nullptr); + + g_damageExtensions = "EGL_EXT_swap_buffers_with_damage"; + g_damageQueries.clear(); + g_damageKhrAvailable = true; + EXPECT_EQ(MG_Util::BackendLoader::ResolveSwapBuffersWithDamage(funcs), nullptr); + EXPECT_EQ(g_damageQueries, (std::vector{"eglSwapBuffersWithDamageEXT"})); + + g_damageExtAvailable = true; + for (const char* extensions : {"", "EGL_EXT_swap_buffers_with_damage_extra", static_cast(nullptr)}) { + g_damageExtensions = extensions; + g_damageQueries.clear(); + EXPECT_EQ(MG_Util::BackendLoader::ResolveSwapBuffersWithDamage(funcs), nullptr); + EXPECT_TRUE(g_damageQueries.empty()); + } + g_damageExtensions = "EGL_EXT_swap_buffers_with_damage EGL_KHR_swap_buffers_with_damage"; + g_damageDisplay = EGL_NO_DISPLAY; + g_damageQueries.clear(); + EXPECT_EQ(MG_Util::BackendLoader::ResolveSwapBuffersWithDamage(funcs), nullptr); + EXPECT_TRUE(g_damageQueries.empty()); } diff --git a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp index 7fe4fc73..9006fd07 100644 --- a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp +++ b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp @@ -181,12 +181,30 @@ namespace MobileGL::MG_Util::BackendLoader { } MG_External::EGL::eglSwapBuffersWithDamageEXT_PTR - ResolveSwapBuffersWithDamage(MG_External::EGL::eglSwapBuffersWithDamageEXT_PTR ext, - MG_External::EGL::eglSwapBuffersWithDamageKHR_PTR khr) { - if (ext != nullptr) { - return ext; + ResolveSwapBuffersWithDamage(const MG_External::EGLFunctionsTable& funcs) { + if (!funcs.eglGetCurrentDisplay || !funcs.eglQueryString || !funcs.eglGetProcAddress) { + return nullptr; } - return (MG_External::EGL::eglSwapBuffersWithDamageEXT_PTR)khr; + const EGLDisplay display = funcs.eglGetCurrentDisplay(); + if (display == EGL_NO_DISPLAY) { + return nullptr; + } + const char* extensions = funcs.eglQueryString(display, EGL_EXTENSIONS); + if (HasExtensionWord(extensions, "EGL_EXT_swap_buffers_with_damage")) { + auto ext = reinterpret_cast( + funcs.eglGetProcAddress("eglSwapBuffersWithDamageEXT")); + if (ext != nullptr) { + return ext; + } + } + if (HasExtensionWord(extensions, "EGL_KHR_swap_buffers_with_damage")) { + auto khr = reinterpret_cast( + funcs.eglGetProcAddress("eglSwapBuffersWithDamageKHR")); + if (khr != nullptr) { + return khr; + } + } + return nullptr; } void AcquireGLESFunctions(MG_External::GLESFunctionsTable& funcs, @@ -817,25 +835,8 @@ namespace MobileGL::MG_Util::BackendLoader { INIT_EGL_FUNC(eglWaitSync) } - { - // One slot, two names. EGL_EXT_swap_buffers_with_damage and - // EGL_KHR_swap_buffers_with_damage define the same entry point with the same - // signature, and a driver may expose either, both or neither - Adreno exposes only - // the KHR spelling - so the winner of the EXT -> KHR chain lands in the EXT slot. - // Null when the driver offers neither, which is the normal "no damage-based present - // here" answer: the caller keeps plain eglSwapBuffers (and note that a call with - // n_rects == 0 is defined to be equivalent to eglSwapBuffers anyway). - funcs.eglSwapBuffersWithDamageEXT = ResolveSwapBuffersWithDamage( - reinterpret_cast( - resolveEGLProc("eglSwapBuffersWithDamageEXT")), - reinterpret_cast( - resolveEGLProc("eglSwapBuffersWithDamageKHR"))); - if (funcs.eglSwapBuffersWithDamageEXT == nullptr) { - MGLOG_I_ONCE("This driver has no damage-based present entry point (neither " - "eglSwapBuffersWithDamageEXT nor eglSwapBuffersWithDamageKHR); " - "present calls fall back to eglSwapBuffers"); - } - } + // Display extensions can only be checked after the display is initialized. + funcs.eglSwapBuffersWithDamageEXT = nullptr; } // Detects whether indirect draws leak the command's baseInstance word ("reserved, must diff --git a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h index 838b4495..e890a5f1 100644 --- a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h +++ b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h @@ -124,7 +124,7 @@ namespace MobileGL { EGL_FUNC_DECL(eglSurfaceAttrib) EGL_FUNC_DECL(eglSwapBuffers) // Holds whichever spelling of the damage-based present the driver provided, EXT - // preferred; null when it provides neither (see AcquireEGLFunctions). + // preferred; resolved after making the display current. EGL_FUNC_DECL(eglSwapBuffersWithDamageEXT) EGL_FUNC_DECL(eglSwapInterval) EGL_FUNC_DECL(eglTerminate) @@ -1369,14 +1369,14 @@ namespace MobileGL { void AcquireGLESFunctions(MG_External::GLESFunctionsTable& funcs, MG_External::EGL::eglGetProcAddress_PTR procAddress); void AcquireEGLFunctions(MG_External::EGLFunctionsTable& funcs); - // The EXT -> KHR -> nothing chain for the damage-based present entry point. A null + // Query the current display's extensions before resolving EXT -> KHR -> nothing. + // Requires a current, initialized EGL display. A null // result is not a failure: it means this driver offers neither spelling, and the // caller presents with plain eglSwapBuffers, which a zero-rectangle call to either // spelling is defined to be equivalent to anyway. Exposed so MG_Test can pin the // preference order without a driver. MG_External::EGL::eglSwapBuffersWithDamageEXT_PTR - ResolveSwapBuffersWithDamage(MG_External::EGL::eglSwapBuffersWithDamageEXT_PTR ext, - MG_External::EGL::eglSwapBuffersWithDamageKHR_PTR khr); + ResolveSwapBuffersWithDamage(const MG_External::EGLFunctionsTable& funcs); Bool FillInGLESCapabilities(MG_External::GLESCapabilities& caps, const MG_External::GLESFunctionsTable& glesFuncs); // Detects whether indirect draws leak the command's baseInstance word into