[Fix] (BackendLoader, DirectGLES): check EGL extensions before resolving damage swap entry points

This commit is contained in:
2026-10-05 00:08:31 -04:00
parent 88bc35b711
commit 2f1a673513
4 changed files with 93 additions and 36 deletions
@@ -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;
@@ -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<std::string> 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<EGLDisplay>(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<std::string>{"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<std::string>{"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<std::string>{"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<std::string>{"eglSwapBuffersWithDamageEXT"}));
g_damageExtAvailable = true;
for (const char* extensions : {"", "EGL_EXT_swap_buffers_with_damage_extra", static_cast<const char*>(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());
}
@@ -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<MG_External::EGL::eglSwapBuffersWithDamageEXT_PTR>(
funcs.eglGetProcAddress("eglSwapBuffersWithDamageEXT"));
if (ext != nullptr) {
return ext;
}
}
if (HasExtensionWord(extensions, "EGL_KHR_swap_buffers_with_damage")) {
auto khr = reinterpret_cast<MG_External::EGL::eglSwapBuffersWithDamageKHR_PTR>(
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<MG_External::EGL::eglSwapBuffersWithDamageEXT_PTR>(
resolveEGLProc("eglSwapBuffersWithDamageEXT")),
reinterpret_cast<MG_External::EGL::eglSwapBuffersWithDamageKHR_PTR>(
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
@@ -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